I'm currently using EclipseLink 2.5 for a project and encountered a little problem with a ManyToMany Relation:
I have a mapped superclass Identifiable that defines a composite key (uuid, revision). Both Entity classes Appointment and Content are subclasses of Identifiable.
Now I try to define an unidirectional ManyToMany relation from Appointment to Content, but it seems that EclipseLink doesn't create the JoinTable right. There are only two columns in it (uuid, revision), but there should be four (one uuid and revision for each side of the relation).
Lust but not least, the code:
#MappedSuperclass
#IdClass(IdentifiablePK.class)
public abstract class Identifiable implements Serializable {
#Id
#UuidGenerator(name = "uuid")
#GeneratedValue(generator = "uuid")
protected String uuid;
#Id
protected Integer revision;
/* ... */
}
public class Appointment extends Identifiable {
#JoinColumns({
#JoinColumn(columnDefinition = "trainingcontent_uuid", referencedColumnName = "uuid", nullable = false, updatable = false, insertable = false),
#JoinColumn(columnDefinition = "trainingcontent_revision", referencedColumnName = "revision", nullable = false, updatable = false, insertable = false)
})
#ManyToMany(fetch = FetchType.LAZY)
protected List<TrainingContent> trainingContents;
/* ... */
}
public class TrainingContent extends Identifiable {
/* ... */
}
I also tried to use #JoinTable instead of #JoinColumns, but then EclipseLink complains about the missing resepectively incomplete #JoinColumns Annotation, which is necessary for target entities with composite keys.
Am I doing something wrong?
Greetings,
chrert
For ManyToMany you must use a #JoinTable to #JoinColumns, the source and target join columns are specified inside the #JoinTable.
Your #JoinColumn must not have, "updatable = false, insertable = false", this makes no sense, as then it would not be able to insert anything, which appears to be what is happening.
See,
https://en.wikibooks.org/wiki/Java_Persistence/ManyToMany
Related
I'm programming a Multi-Tenant application. The tenant_id is present in every table and is part of each PK.
My tables are:
table S {
tenant_id PK
id PK
pid
...
}
table P {
tenant_id PK
id PK
...
}
Table S has a PK made up with (tenant_id, id). Table P has a PK made up with (tenant_id, id). Table S links to table P via (S.tenant_id = P.tenant_id and S.pid = P.id)
When I've implemented this in Java JPA, I can read table S including P, but adding a S has a strange behavior: JPA adds the missing P but doesn't fill S.pid so the P is correct but there is no link from S to P.
I don't want to have bidirectional links because P is also referred to by other fields of other tables.
My problem is somewhat similar to another question which has no answer yet.
The Java classes look like (simplified):
#Embeddable
public class MultiTenantId implements Serializable {
#Column(name = "tenant_id")
private String tenantId;
#Column(name = "id")
private String id;
...
}
#Entity
#Table(name = "P")
public class P {
#EmbeddedId
private MultiTenantId id;
...
}
#Entity
#Table(name = "S")
public class S {
#EmbeddedId
private MultiTenantId id;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "tenant_id", referencedColumnName = "tenant_id", insertable = false, updatable = false),
#JoinColumn(name = "pid", referencedColumnName = "id", insertable = false, updatable = false)}
)
private P p;
...
}
If I don't set the insertable = false, updatable = false then error
because the tenant_id is already used (in the MultiTenantId and in
this #ManyToOne)
if I set them then reading is OK but adding a S via JPA has a strange effect in the DB: it correctly adds the P, correctly adds the S except that it never sets the pid in the table S ==> the link is not done.
Thanks a lot in advance for your help, I'm completely lost with this problem !!
To the existing table through #ManyToOne
#ManyToOne
#JoinColumns({
#JoinColumn(name = "ID", referencedColumnName = "APPLICATION_ID", insertable = false, updatable = false),
#JoinColumn(name = "VERSION", referencedColumnName = "APPLICATION_VERSION", insertable = false, updatable = false)
})
private ApplicationsBodies applicationsBodies;
I join another table.
But from the join table, I want to join only one column.
#Entity
#Table
public class ApplicationsBodies implements Serializable {
...
#Column(name = "APPLICATION_ID")
private Long applicationId;
#Column(name = "APPLICATION_VERSION")
private Long applicationVersion;
//I want to attach only this column
#Lob
#Column(name = "BODY")
private String body;
#Column(name = "ACTIVE_STATE")
private Integer activeState;
How can this be implemented using JPA / Hibernate?
UPDATE: Solution
My problem was solved by #Formula annotation. Because When I refer to an entity only for the purpose of loading this one field, for me it has become the most optimal solution.
I deleted the field: private ApplicationsBodies applicationsBodies. And created a field: private String body with annotation #Formula with value - SQL query joining only one column.
if you want to use join with JPA data, then I recommend to use JPL Query Language. So in your repository classes use as annotation :
#Query("select a.name from ApplicationsBodies a join a.applicationId d where d.id = :applicationVersion")
We have 2 entities with a #ManyToOne relationship.
When we create an instance of EntityB within a #Transactional method, entityAId (insertable = false updatable = false), is not updated automatically - even though that the entityA instance was already persisted.
Is there a way around this? Do we have to update it manually in the ctor?
#Entity
public class EntityA {
#Id
#GeneratedValue
private Long id;
public EntityA() {
super();
}
...
}
#Entity
public class EntityB {
#Id
#GeneratedValue
private Long id;
#ManyToOne(optional = true, fetch = FetchType.LAZY)
private EntityA entityA;
#Column(name = "entityA_id", insertable = false, updatable = false)
private Long entityAId;
public EntityB() {
super();
}
public EntityB(EntityA entityA) {
super();
this.entityA = EntityA;
}
...
}
EDIT: Also tried the following, but still entityAId = null within the transaction (even though entityA was persisted before).
#Entity
public class EntityB {
#Id
#GeneratedValue
private Long id;
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "entityA_id", insertable = false, updatable = false)
private EntityA entityA;
#Column(name = "entityA_id")
private Long entityAId;
...
}
Hibernate is not going to populate entity fields 'on the fly' (when you change some other fields or similar). It is also not going to do it on persist/flush (exceptions being some special fields like id and version).
Non-insertable/non-updatable fields are populated when entity instances are fetched from the DB. So, to make such fields initialized/refreshed by Hibernate in the same transaction in which you perform changes to the underlying columns they are mapped to, you should first flush the session and then either:
clear the session and re-read the entities;
or, refresh the entities for which you want to reflect such kind of changes.
To update the id field a persist action of the object is required. By default, objects in field entityA are not automatically persisted when persisting an object of EntityB.
I see two possible solutions:
A) Use cascade
#ManyToOne(optional = true, fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST})
private EntityA entityA;
(or use CascadeType.ALL)
B) Persist entityA manually
entityManager.persist(entityA);
To me your mapping does not look right. #ManyToOne or any other association defined between entities but you have defined it on entityAId. Ideally it should be entity (an here you should use insertable = false updatable = false)and you should have separate field entityAId with #column defined on it. Now you should update this field yourself.
If you want to handle hibernate for you remove insertable = false updatable = false
I have the following scenario:
Base Domain class:
#MappedSuperclass
public class BaseDomain {
#Id
protected UUID id;
}
Media Object class:
#Entity
public class MediaObject extends BaseDomain {
#ManyToMany
#JoinTable(
joinColumns = {
#JoinColumn(name = "BaseDomain_id", referencedColumnName = "id"
}
inverseJoinColumns = {
#JoinColumn(name = "Media_id", referencedColumnName = "id")
}
private List<BaseDomain> holders;
}
"Holder" A:
#Entity
public class A extends BaseDomain {
#ManyToMany
private List<MediaObject> media;
}
"Holder" B:
#Entity
public class B extends BaseDomain {
#ManyToMany
private List<MediaObject> media;
}
What I want to achieve is, to store a MediaObject and multiple entities may "hold" this object. My approach would be a using a JoinTable that stores the relation between the MediaObject and an arbitrary BaseDomain object (as above). The issue I'm facing is that the persistence provider (in my case Hibernate) would not be able to decide which actual table to join.
I'm thinking about using a unidirectional #OneToMany which is possible in JPA 2.1.
However, I want to ask, if there are some kind of best practices to approach such a situation.
Following snippet is used by me in production environement, it implements ManyToMany assiciation mapping for Hibernate.
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "printed_mails_logo",
joinColumns = {
#JoinColumn(name = "mails_id", nullable = false, updatable = false)
},
inverseJoinColumns = {
#JoinColumn(name = "logo_id", nullable = false, updatable = false) })
private Set<Logo> printedLogos;
printer_mails_logo is additinal associative table in database.
#JoinColumn(name='x') is the actual name of column in associative table.
This works well for me. I can fetch without no problem all logos that has been printed already.
i would like to create an application in this context : Zk 6, Spring v3.1.1, JPA 2.0, Hibernate 4.1.4, all with annotations but i have some pb with JPA concept.
Here are a type of case study :
3 tables, all linked via a join table ; we are dealing with cardinality 0, n.
So we have T_E_USER, T_E_TYPE and T_E_AIR.
Each table has a numeric ID, and a simple VARCHAR field.
A join table is created with T_J_USR_TPE_AIR with the 3 ID referenced by foreign keys forming a composed primary key.
I'm using Hibernate Tools for generate my entities (version JPA).
And that's where the problems start ....
I have, in each entity class, an attribute of type set with annotation # OneToMany.
I have a class representing the join that has an id attribute of complex type (another class) with an annotation EmbeddedId for a composite key.
And attributes representing the three entities with annotations # ManyToOne.
Here are my questions, because that's where I'm confused:
which should i set into the "mappedBy" attribute in the annotation # OneToMany of my entities?
Am I forced to do a class entity representing the join?
How does the CASCADE? Is it possible to use it in this context to enrich the join table "automatically"? Or should I manually instantiate the class representative of the join in order to persist the information myself?
A big thank you in advance for any kind soul who could give me a helping hand.
Thank you for your answers but one said "yes" when the other says "no" lol
Here's what I did during the day but I have not yet been tested.
In each entity table, i added a #OneToMany relation with mappedBy setted to the attribute defined in "join" entity :
#OneToMany(fetch = FetchType.LAZY,
mappedBy = "aircraft",
cascade = { CascadeType.REMOVE })
private Set<UserConfig> userConfigs = new HashSet<UserConfig>(0);
...
#OneToMany(fetch = FetchType.LAZY,
mappedBy = "userAccount",
cascade = { CascadeType.REMOVE })
private Set<UserConfig> userConfigs = new HashSet<UserConfig>(0);
...
#OneToMany(fetch = FetchType.LAZY,
mappedBy = "referenceType",
cascade = { CascadeType.REMOVE })
private Set<UserConfig> userConfigs = new HashSet<UserConfig>(0);
And i created a new Entity for the join table.
#Entity
#Table(name = "T_J_USR_RFT_AIR_URA")
public class UserConfig implements java.io.Serializable {
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "airId",
column = #Column(name = "URA_AIR_ID", nullable = false)),
#AttributeOverride(name = "usrId",
column = #Column(name = "URA_USR_ID", nullable = false)),
#AttributeOverride(name = "rftId",
column = #Column(name = "URA_RFT_ID", nullable = false))
})
private UserConfigId id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "URA_RFT_ID", nullable = false, insertable = false, updatable = false)
private ReferenceType referenceType;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "URA_USR_ID", nullable = false, insertable = false, updatable = false)
private UserAccount userAccount;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "URA_AIR_ID", nullable = false, insertable = false, updatable = false)
private Aircraft aircraft;
...
getter & setter
}
Where UserConfigId is :
#Embeddable
public class UserConfigId implements java.io.Serializable {
#Column(name = "URA_AIR_ID", nullable = false)
private Integer airId;
#Column(name = "URA_USR_ID", nullable = false)
private Integer usrId;
#Column(name = "URA_RFT_ID", nullable = false)
private Integer rftId;
...
getter & setter
}
What do you think about this practice ?
I just used "cascade" if an object of the join table is deleted in order to delete all element associated in the join.
Is it all right ?
Anyway thank you Tom, i will analyzed your link.
Thank you JMelnyk too.
You are welcome if you want to demonstrate what are the best practices for this case.
Three-way joins are tricky. I think what you've done, using an entity for the join table, is probably the right thing to do. To answer your questions:
Your #OneToMany attributes refer to the entity mapping the join table; they should be mappedBy the appropriate #ManyToOne attribute in that entity.
Yes, unfortunately, an entity for the join table is the best way to do this.
Cascades can be used to automatically add objects to the database, but not to create objects. You will need to create instances of the join entity in code.
which should i set into the "mappedBy" attribute in the annotation #
OneToMany of my entities?
mappedBy attribute represents a property name you are joining on. Read more...
e.g. AnyEntity holds List<Employee> which is joined on (mappedBy) department property in Employee entity, and that department property holds the association.
Am I forced to do a class entity representing the join?
No, you do not provide an entity class for join tables.
How does the CASCADE? Is it possible to use it in this context to
enrich the join table "automatically"? Or should I manually
instantiate the class representative of the join in order to persist
the information myself?
Yes it is possible to enrich associations of the entity and itself by marking associations with desired cascade type.
e.g. We have a Department which holds List<Employee> and I put CascadeType.PERSIST on employees. Now we populate department objects with its properties and employees. When we are finished, we persist only the department, and it will cascade operation to employees.