I have below Hibernate relations in a spring boot application.
#Entity
#Table(name = "projects")
public class Project {
#Embedded
private ProjectParameters parameters;
}
#Embeddable
public class ProjectParameters {
#Column(name = "hvacConfigs")
#ElementCollection(targetClass = HVACUserConfigModel.class)
#CollectionTable(uniqueConstraints = #UniqueConstraint(columnNames = {}), foreignKey = ForeignKey(name = "project_hvacConfig_fk"))
private Set<HVACUserConfigModel> hvacConfigs;
}
#Entity
#Table(name = "hvacuserconfig")
public class HVACUserConfigModel {
#Id
#GeneratedValue
private Integer id;
}
Mysql table auto-generated is as follows. Please open images in these below links as I don't have 10 reputation to post images on stackoverflow.
The issue is I am able to save multiple projects with same project like below
Project_uid | hvacConfigs_id
------------------------------
1001 | 1
1001 | 2
Adding (1002 | 1) throws unique constraint violation.
I am able to get it working by changing table definition explicitly using mysql workbench by removing the unique constrain UK_me0ekntab0gknshag0xjjv35x. To remove unique constrain I have to first remove foreign key constrain FKeygabcnr2stdchxqpb5wuinf2 which is being referred by unique constrain. After these changes, I from spring boot app, I insert new rows it is successfully added with values (1002 | 1) & (1002 | 2).
I am able to explore that #CollectionTable annotation, annotated in hvacConfigs field in Java code, provides ways to insert uniqueConstraint and foreignKey definition as part of attribute definition. I have tried tweaking these attributes of annotation but with no success.
Please let me know if you have any solution how to override the unique constraint and foreign key constraints using java code so that this works without any manual adjustment to mysql table explictly.
To be honest, this is unclear what you want to achieve. But your mapping is invalid. You can use #ElementCollection annotation only with basic or embeddable types. If you want operate with HVACUserConfigModel as with an entity you should use #OneToMany association.
So, you can correct your mapping in this way:
#Embeddable
public class ProjectParameters {
#ElementCollection
#CollectionTable(
name = "hvacuserconfig",
joinColumns = #JoinColumn(name = "hvacConfigs_id")
)
private Set<HVACUserConfigModel> hvacConfigs;
}
#Embeddable
public class HVACUserConfigModel {
// #Id annotation is not used with #Embeddable types
// Like basic types, embeddable types do not have any identity,
// being managed by their owning entity.
private Integer id;
// ...
}
or in this way:
#Embeddable
public class ProjectParameters {
#OneToMany
#JoinColumn(name = "hvacConfigs_id")
private Set<HVACUserConfigModel> hvacConfigs;
}
#Entity
#Table(name = "hvacuserconfig")
public class HVACUserConfigModel {
#Id
#GeneratedValue
private Integer id;
}
We solved this using ManytoMany annotation instead of ElementCollection annotation. It works the same way creating new intermediate table to contain Project Id and HVACUserConfigModel id but does not create the unique index.
Related
I am trying to join to Hibernate Entities in a OneToOne Mapping. I am able to fetch the data for a given primary key from the Main Entity, the joining entity, however, returns null. I am new to hibernate and any help will be appreciated.
I have two Tables,
PT_CORE
Primary Key: ptId - Integer;
Foreign Key: stId(ST_AUX) - Integer;
Columns: ptId, ptName
ST_AUX
Primary Key: stId;
Columns: stId, stName
The two tables get populated by other applications and mine is a read-only operation.
Below is my first Entity class(PtCore.java)
#Entity
#Table(name="PT_CORE")
public class PtCore implements Serializable{
#Id
#Column(name="ptId", nullable = false)
private int id;
#Column(nullable=false)
private int stId; //The Foreign key column
#OneToOne
#JoinTable( name = "core_aux", joinColumns = {#JoinColumn(Name="ptId")},
inverseJoinColumns = {#JoinColumn(Name="stId")}
)
private StAux staux;
//Getters, setters and toString() for above
}
StAux is another Entity, defined as below,
#Entity
#Table(name="ST_AUX")
public class StAux implements Serializable {
#Id
#Column(nullable=false)
private Integer stId;
#OneToOne
private PtCore ptcore;
#Column
private String stName;
//Getters, Setters and toString follow.
}
I do below in the Service method:
PtCore obj = (PtCore) session.get(PtCore.class,1);
System.out.println(obj);
In the Results, I get the value of ptName, but the stAux class variables are null, Indicating that the join does not work as expected.
First of all you have the mapping information existing in your PT_CORE. And I assume it is something like FOREIGN KEY (stid) REFERENCES (stid). If you want to use existing schema and existing data I guess there is no mapping table core_aux really existing. At least you did not mention it. However it is visible as #JoinTable annotation but still there is this above mentioned foreign key which seems to be the real mapping (so again not the join table).
I suggest the following
remove this
#Column(nullable=false)
private int stId; //The Foreign key column
from your PtCore. I think it is not needed. Also in PtCore, remove the #JoinTable (because what I told above) and add mapping informaiion to #OneToOne annotation, like:
#OneToOne
#JoinColumn(name = "stid")
private StAux staux;
from your PT_CORE.
Then in StAux alter also a bit:
#Id
#Column(name = "stid") // this might not be needed but if there is like "st_id"...
private Integer stId; // so just for sure
#OneToOne(mappedBy = "staux")
private PtCore ptcore;
Because you have existing tables and constraints there might raise errors if hibernate tries to auto-generate those again by JPA instructions.
Check this for example for more information.
UPDATE: just realized also that in your title is #OneToMany but in your code is #OneToOne.
So you might want to elaborate your question and/or title a bit.
In your relation, the owning side is PtCore, the inverse side is StAux.
In bidirectional OneToOne relations, the inverse side has to have the mappedBy attribute. Actually, the mappedBy attribute contains the name of the association-field on the owning side.
So, you must change your inverse side code (StAux Entity). You have to add mappedBy attribute to #OneToOne in StAux class:
#OneToOne(mappedBy="staux")
private PtCore ptcore;
I have a simple table (ActivityLog) and I want it to have a PK that is also a FK to another table (User).
It seems to be a common thing to have, and I tried to follow this wikibook
Primary Keys through OneToOne and ManyToOne Relationships. The example there involved a composite key. I need just a primitive key, so I ended up with:
#Entity
public class User {
#Id
private Long id;
// other stuff
}
#Entity
public class ActivityLog {
#Id
#OneToOne(optional = false)
#JoinColumn(name="user_id", referencedColumnName="id")
private User user;
// other stuff
}
Unfortunately i am getting:
Caused by: java.lang.IllegalArgumentException: This class [class com.example.ActivityLog] does not define an IdClass
at org.hibernate.metamodel.internal.AbstractIdentifiableType.getIdClassAttributes(AbstractIdentifiableType.java:183)
at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation$IdMetadata.<init>(JpaMetamodelEntityInformation.java:253)
I tried to annotate ActivityLog with:
#IdClass(Long.class)
(even though from what I understand it is applicable only for composite keys), yet I am getting the exact same error.
Is my case different than what's on the mentioned wikibook?
Is Spring at fault here? (As suggested in this question? (no accepted answers)).
This should help:
#Entity
public class ActivityLog {
#Id
#Column(name = "user_id")
private Long id;
#OneToOne(optional = false)
#JoinColumn(name="user_id", referencedColumnName="id")
private User user;
// other stuff
}
Btw. I would expect, that you need more logs per user, so you would probably need some additional (generated) id anyway ...
I have four entities to map together, "Association", "Account", "Transaction" and "TransactionEvent". The id of Association is a simple integer id. Account and Transaction each have embedded id's consisting of a mapping to an Association and a number.
TransactionEvent should have an embedded id consisting of one Account and one Association. Now, each of those are mapped to an Association, and I want it to be the same Association for one TransactionEvent.
JPA Annotations is used for the Hibernate mapping, but I cannot make this work. I have tried forcing the same column name for the Association key, but Hibernate complains about repeated columns.
Is this possible to solve, or am I not thinking straight?
Here are the annotated classes, but I trimmed away getters/setters and non-id columns, annotations from the javax.persistence namespace:
#Entity
public class Association implements Serializable {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private long id;
}
#Embeddable
public class AccountPK implements Serializable {
#ManyToOne(optional=false)
private Association association;
#Column(nullable=false)
private int number;
}
#Embeddable
public class TransactionPK implements Serializable {
#ManyToOne
private Association association;
#GeneratedValue(strategy=GenerationType.AUTO)
private long number;
}
#Embeddable
public class AccountEventPK implements Serializable {
#ManyToOne(optional=false)
#JoinColumns({
#JoinColumn(name="association_id", referencedColumnName="association_id"),
#JoinColumn(name="account_number", referencedColumnName="number")
})
private Account account;
#ManyToOne(optional=false)
#JoinColumns({
#JoinColumn(name="association_id", referencedColumnName="association_id"),
#JoinColumn(name="transaction_number", referencedColumnName="number")
})
private Transaction transaction;
}
Actual Account, Transaction and AccountEvent entities are on the form
#Entity
public class Account implements Serializable {
#EmbeddedId
private AccountPK id;
}
I don't have much experience with placing associations directly in the embedded id component since this is not supported by JPA but is Hibernate specific.
As an alternative my suggestion would be to use the approach described in the Composite Primary Keys section of the JPA wikibook:
(...) JPA 1.0 requires that all #Id
mappings be Basic mappings, so if
your Id comes from a foreign key
column through a OneToOne or
ManyToOne mapping, you must also
define a Basic #Id mapping for the
foreign key column. The reason for
this is in part that the Id must be a
simple object for identity and caching
purposes, and for use in the IdClass
or the EntityManager find() API.
Because you now have two mappings for
the same foreign key column you must
define which one will be written to
the database (it must be the Basic
one), so the OneToOne or ManyToOne
foreign key must be defined to be
read-only. This is done through
setting the JoinColumn attributes
insertable and updatable to false,
or by using the
#PrimaryKeyJoinColumn instead of the
#JoinColumn.
A side effect of having two mappings
for the same column is that you now
have to keep the two in synch. This is
typically done through having the set
method for the OneToOne attribute
also set the Basic attribute value to
the target object's id. This can
become very complicated if the target
object's primary key is a
GeneratedValue, in this case you
must ensure that the target object's
id has been assigned before relating
the two objects.
(...)
Example ManyToOne id annotation
...
#Entity
#IdClass(PhonePK.class)
public class Phone {
#Id
#Column(name="OWNER_ID")
private long ownerId;
#Id
private String type;
#ManyToOne
#PrimaryKeyJoinColumn(name="OWNER_ID", referencedColumnName="EMP_ID")
private Employee owner;
...
public void setOwner(Employee owner) {
this.owner = owner;
this.ownerId = owner.getId();
}
...
}
This looks like to be what you're looking for (and maybe less complicated). I'd try to implement this solution (incrementally).
I am having troubles getting this working and I wonder if what I am doing simply does not make sense?
public class Application {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="id")
private long id;
....
}
#MappedSuperclass
public abstract class Sample {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#OneToOne (cascade=CascadeType.ALL)
protected Application application;
....
}
// TestSample contains a list that is mapped not by the primary key of the Sample
public class TestSample extends Sample {
#OneToMany(mappedBy="application", cascade=CascadeType.ALL, fetch=FetchType.LAZY)
private List<Part> parts = new ArrayList<Part>();
....
}
public class Part {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private long id = 0;
#ManyToOne (cascade=CascadeType.ALL, fetch=FetchType.LAZY)
Application application;
}
The problem I am having is that I am able to add parts, the database looks correct, then when I attempt to fetch the the parts list I get an empty list.
I can get it to work if I compromise on the database structure by changing these classes:
// TestSample contains a list that is mapped not by the primary key of the Sample
public class TestSample extends Sample {
#OneToMany(mappedBy="testSample", cascade=CascadeType.ALL, fetch=FetchType.LAZY)
private List<Part> parts = new ArrayList<Part>();
....
}
public class Part {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private long id = 0;
#ManyToOne (cascade=CascadeType.ALL, fetch=FetchType.LAZY)
TestSample testSample;
}
The tables are being auto generated by hibernate, so they are coming out like this:
application
id : number
....
test_sample
id : number
application_id : number
...
part
id : number
application_id : number
If I change it to the less desirable way that works, the last table is different:
part
id : number
test_sample_id : number
Because the id's in all cases are being auto generated, there are no shared primary keys. Essentially what I am trying to do is use mappedby where mappedby is referring to a field that is not the primary key of the table/class called "TestSample". This is what I am not sure if makes sense in JPA.
The OneToMany is bi-directional with the "Part" class. I think this is getting very difficult to explain (:
Your one-to-many association between TestSample and Part is not bidirectional, the mappedBy is not correct (the application table is not owning the relation, it is not even aware of test_sample), your mapping doesn't make sense. There is something to change.
I think that you should show what the expected tables are, not the generated one (since the mappings are incoherent, the generated result can't be satisfying). You are talking about compromise so I believe that you have an idea of what the expected result should be. Please show it.
I'm using Hibernate 3.5.2-FINAL with annotations to specify my persistence mappings. I'm struggling with modelling a relationship between an Application and a set of Platforms. Each application is available for a set of platforms.
From all the reading and searching I've done, I think I need to have the platform enum class be persisted as an Entity, and to have a join table to represent the many-to-many relationship. I want the relationship to be unidirectional at the object level, that is, I want to be able to get the list of platforms for a given application, but I don't need to find out the list of applications for a given platform.
Here are my simplified model classes:
#Entity
#Table(name = "TBL_PLATFORM")
public enum Platform {
Windows,
Mac,
Linux,
Other;
#Id
#GeneratedValue
#Column(name = "ID")
private Long id = null;
#Column(name = "NAME")
private String name;
private DevicePlatform() {
this.name = toString();
}
// Setters and getters for id and name...
}
#Entity
#Table(name = "TBL_APP")
public class Application extends AbstractEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "NAME")
protected String _name;
#ManyToMany(cascade = javax.persistence.CascadeType.ALL)
#Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})
#JoinTable(name = "TBL_APP_PLATFORM",
joinColumns = #JoinColumn(name = "APP_ID"),
inverseJoinColumns = #JoinColumn(name = "PLATFORM_ID"))
#ElementCollection(targetClass=Platform.class)
protected Set<Platform> _platforms;
// Setters and getters...
}
When I run the Hibernate hbm2ddl tool, I see the following (I'm using MySQL):
create table TBL_APP_PLATFORM (
APP_ID bigint not null,
PLATFORM_ID bigint not null,
primary key (APP_ID, PLATFORM_ID)
);
The appropriate foreign keys are also created from this table to the application table and platform table. So far so good.
One problem I'm running into is when I try to persist an application object:
Application newApp = new Application();
newApp.setName("The Test Application");
Set<DevicePlatform> platforms = EnumSet.of(Platform.Windows, Platform.Linux);
newApp.setPlatforms(platforms);
applicationDao.addApplication(newApp);
What I would like to happen is for the appropriate rows in the Platform table to created, i.e. create a row for Windows and Linux, if they don't already exist. Then, a row for the new application should be created, and then the mapping between the new application and the two platforms in the join table.
One issue I'm running into is getting the following runtime exception:
2010-06-30 13:18:09,382 6613126-0 ERROR FlushingEventListener Could not synchronize database state with session org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.example.model.Platform
Somehow, the platform set is not being persisted when I try to persist the application. The cascade annotations are supposed to take care of that, but I don't know what's wrong.
So my questions are:
Is there a better way to model what I want to do, e.g. is using an Enum appropriate?
If my model is alright, how do I properly persist all of the objects?
I've been struggling with this for hours, and I've tried to recreate all of the code above, but it might not be complete and/or accurate. I'm hoping someone will point out something obvious!
You should decide whether your Platform is an entity or not.
If it's an entity, it can't be an enum, because list of possible platforms is stored in the database, not in the application. It should be a regular class with #Entity annotation and you will have a normal many-to-many relation.
If it isn't an entity, then you don't need TBL_PLATFORM table, and you don't have a many-to-many relation. In this case you can represent a set of Platforms either as an integer field with bit flags, or as a simple one-to-many relation. JPA 2.0 makes the latter case simple with #ElementCollection:
#ElementCollection(targetClass = Platform.class)
#CollectionTable(name = "TBL_APP_PLATFORM",
joinColumns = #JoinColumn(name = "APP_ID"))
#Column(name = "PLATFORM_ID")
protected Set<Platform> _platforms;
-
create table TBL_APP_PLATFORM (
APP_ID bigint not null,
PLATFORM_ID bigint not null, -- the ordinal number of enum value
primary key (APP_ID, PLATFORM_ID)
);
and enum Platform without annotations.
Simple use below mapping on your entity. Suppose that we have:
public enum TestEnum { A, B }
Then in your Entity class:
#ElementCollection(targetClass = TestEnum.class)
#CollectionTable(
name = "yourJoinTable",
joinColumns = #JoinColumn(name = "YourEntityId")
)
#Column(name = "EnumId")
private final Set<TestEnum> enumSet= new HashSet<>();
The following example shows what the situation is when Module is an entity and Langue is an enum.
#Entity
public class Module {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String libelle;
#ElementCollection(targetClass = Langue.class, fetch = FetchType.EAGER)
#CollectionTable(name = "link_module_langue",
joinColumns = #JoinColumn(name = "module_id", referencedColumnName = "id"))
#Enumerated(EnumType.STRING)
#Column(name = "langue")
private Set<Langue> langues;
}
public enum Langue {
FRANCAIS, ANGLAIS, ESPAGNOLE
}
You should create link_module_langue table, please see the following sql code :
CREATE TABLE `link_module_langue` (
`module_id` BIGINT(20) NOT NULL,
`langue` VARCHAR(45) NOT NULL,
PRIMARY KEY (`module_id`, `langue`),
CONSTRAINT `module_fk`
FOREIGN KEY (`module_id`)
REFERENCES `module` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE);
NB: Langue is not an entity and would not have its own table.