Why does Hibernate create a Join Table by default for #OneToMany? - java

#OneToMany annotation, by default, creates a join table, unless the mappedBy element is specified.
What is the reason for this behaviour? For example, with the following entities:
#Entity
public class User {
// ...
#OneToMany
private List<UserDocument> documents;
// ...
}
#Entity
public class UserDocument {
// ...
#ManyToOne
private User user;
// ...
}
For the User entity, why doesn't Hibernate simply:
Find the field with type User in UserDocument by doing reflection on UserDocument entity.
Infer the value of mappedBy for the #OneToMany annotation by itself?
What is the reason for not doing this and generating a join table as the default behaviour? Why is Hibernate (or JPA) is designed this way?

A simple reason behind this is that Hibernate cannot known for sure that a filed of type User inside of UserDocument is corresponding to the specific User-UserDocument relation. Without a mappedBy property, Hibernate can only create a join table or insert a generated column in UserDocument table. However, the latter alters data model and introduces more problem than it may resolve ( distinguish generated or declared column; table schema mismatch model class; etc.). Thus Hibernate use a join table to store the mapping.
For example, if you want to track the last one who modifies a document, you may need another many-to-one relation in UserDocument. This cannot be infered and resolved just using reflection.
#Entity
public class UserDocument {
// ...
#ManyToOne
private User user;
#ManyToOne
private User lastModifiedBy;
// ...
}

Related

Insert order of JPA entity with compound EmbeddedId (an EmbeddedId that contains another EmbeddedId)

I'm working on a project in WebSphere 8.5.5 (OpenJPA 2.2.3) that needs to cascade creation and merging through a large JPA annotated entity model. We are having a very specific problem when merging grand-children either by calling EntityManager.merge() on the grand-parent or by the triggered flush at the commit of a transaction. Here are the details:
Relevant portion of entity mappings:
EntityA has a oneToMany to EntityB
EntityB has a oneToMany to EntityC
EntityC has a oneToMany to EntityD
All have bidirectional mappings. Entity A and B have single column primary keys. Entity C has a composite primary key that includes a foreign key to the primary key of Entity B. Entity D has a composite key that includes the composite key of Entity C. Please see the mappings below.
#Entity
#Table(name="TableA")
public class EntityA extends BaseEntity {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TABLE_A_ID_GEN")
#SequenceGenerator(name="TABLE_A_ID_GEN", sequenceName="TABLE_A_ID", allocationSize=1)
#Column(name="TABLE_A_ID")
private Integer id;
#OneToMany(fetch=FetchType.LAZY, mappedBy="entityA", cascade=CascadeType.ALL)
private List<EntityB> entityBList;
...
}
#Entity
#Table(name="TableB")
public class EntityB extends BaseEntity {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TABLE_B_ID_GEN")
#SequenceGenerator(name="TABLE_B_ID_GEN", sequenceName="TABLE_B_ID", allocationSize=1)
#Column(name="TABLE_B_ID")
private Integer id;
#ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
#JoinColumn(name="TABLE_A_ID")
private EntityA entityA;
#OneToMany(fetch=FetchType.LAZY, mappedBy="entityB", cascade=CascadeType.ALL)
private List<EntityC> entityCList;
...
}
#Entity
#Table(name="TableC")
public class EntityC extends BaseEntity {
#EmbeddedId
private EntityC_PK id = new EntityC_PK();
#MapsId("entityB_Id")
#ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
#JoinColumn(name="TABLE_B_ID")
private EntityB entityB;
#OneToMany(fetch=FetchType.LAZY, mappedBy="entityC", cascade=CascadeType.ALL)
private List<EntityD> entityDList;
...
}
#Embeddable
public class EntityC_PK implements BaseComponent {
#Column(name="TABLE_B_ID", nullable = false, updatable = false)
private Integer entityB_Id;
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TABLE_C_ID_GEN")
#SequenceGenerator(name="TABLE_C_ID_GEN", sequenceName="TABLE_C_ID", allocationSize=1)
#Column(name="TABLE_C_ID")
private Integer entityC_Id;
...
}
#Entity
#Table(name="TABLE_D")
public class EntityD extends BaseEntity {
#EmbeddedId
private EntityD_PK id = new EntityD_PK();
#MapsId("entityC_Id")
#JoinColumns({
#JoinColumn(name = "TABLE_B_ID"),
#JoinColumn(name = "TABLE_C_ID")})
#ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
private EntityC entityC;
...
}
#Embeddable
public class EntityD_PK implements BaseComponent {
#Embedded
private EntityC_PK entityC_Id;
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TABLE_D_ID_GEN")
#SequenceGenerator(name="TABLE_D_ID_GEN", sequenceName="TABLE_D_ID", allocationSize=1)
#Column(name="TABLE_D_ID")
private Integer entity_id;
...
}
What Works:
You can call an EntityManager.persist() on Entity A (with all the children attached) and the model will cascade the persist correctly.
What Doesn't Work:
If you instantiate Entity A and call EntityManager.persist(entityA) and THEN add the children, grand-children, etc. when you EntityManager.merge(entityA) (or allow the implicit merge upon committing the transaction) it will fail to execute the INSERT statements in the correct order. To make things more confusing the order of the INSERTS is not consistent across repeat executions of unit tests. It fails by attempting to insert Entity D before Entity C.
The Question:
How to we correct the JPA annotations to enforce the correct insert order (and update/delete) upon merge?
EDIT 1:
The insert/delete order is critical because the database enforces the foreign key relationships with constraints.
Let me first state (and maybe I'm stating the obvious, sorry) that you should review the JPA spec for your scenarios.......embedables sometimes have differently rules about them. Next, you state 'EntityManager.create()', but I think you meant .persist? You later talk about merge so maybe you mean .merge? Either way, I'd suggest you stick with .persist if you want to persist new entities rather than a merge. While it is not illegal, merge is typically for merging detached entities, etc.
With that out of the way, let me get at the heart of your question and give you a property which might help with your order. You didn't state in your text if your ddl contains a foreign key constraint. Since you are concerned with order, I'd assume you have such a constraint. If you do, OpenJPA knows nothing about this constraint, and as such, will not know to order things appropriately. By default, you can't depend on the order of SQL, and the randomness of the ordering is exactly what I expect. However, if you need things to be order in such a way as to support an FK constraint, then you need to allow OpenJPA to 'learn' about your constraint. To do that, you need to set this property in your persistence.xml file (or you can set it as a JVM custom property):
<property name="openjpa.jdbc.SchemaFactory" value="native(ForeignKeys=true)"/>
This property allows OpenJPA to inspect your schema and in so doing it can learn about your FK constraint. With that knowledge, OpenJPA can properly order SQL.
Finally, if you don't have an FK constraint, but you want to order the SQL in a certain way, then you might need to use this:
<property name="openjpa.jdbc.UpdateManager" value="operation-order"/>
Do not, and I repeat do not use both of these properties together. It can have odd side effects. Please focus on the SchemaFactory property first, and then if it doesn't help try UpdateManager. The operation-order tells OpenJPA to order SQL based on how your persist your entities, or in other words, the order of operations. This might actually not be overly helpful to your situation since you persist A and expect everything else to be cascaded (OpenJPA would likely persist A first, but when it comes to B and C, it is a crapshoot which will go first). However, if you persisted A, then C, then B, the SQL should go in order of inserting A, C, then B with "operation-order" set.

An object with an Eager fetch Type property, executes queries for the lazy loaded properties of that property.. why?

I'm using SpringBoot, when a repository is called for the AModel, the repository is executing queries for BModel, CModel and DModel even when I am not calling for either CModel or DModel. any idea why this is happening and how I can prevent it?
#Entity
public class AModel extends Model {
#OneToOne(fetch = FetchType.EAGER)
#JsonIgnore
private BModel;
}
#Entity
public class BModel extends Model {
#OneToOne(fetch = FetchType.LAZY)
private CModel;
#OneToOne(fetch = FetchType.LAZY)
private DModel;
}
#Query("select a from com.project.models.AModel a where a.id = :id")
#Override
Candidate findOne(#Param("id")Long id);
The reason here is that when the entity AModel includes entity BModel, which in turn includes CModel and DModel. It has to fetch CModel and DModel when the fetch for AModel is called, otherwise your queries won't be able to complete if the objects of CModel and DModel are not fetched for, and the entire purpose of making the fetchType as Eager for AModel will be gone.
This happens cause of the oneToOne relations from BModel to CModel and DModel.
When you define a relation with FetchType.LAZY, then hibernate needs to substitute the object with a proxy, so that when you access this the first time, it can load it.
Now with oneToOne relation which is nullable, hibernate has no chance to know, if the relation is null or not without execute a select, cause the table in relation usually use the same primary key.
So if your relations are nonnullable, then define optional = false and no eager fetching is done. If this is not the case you could also use a oneToMany relation instead.
See also this stackoverflow question

Hibernate Envers creating modified fields for foreign owned OneToMany relationships

I am using Hibernate 4.3.1.Final
If I have two Entities, let's say A and B. A contains a set of B objects that is annotated as a OneToMany association.
If I set "org.hibernate.envers.global_with_modified_flag" to true and "org.hibernate.envers.modified_flag_suffix" to "Modified", then Envers correctly adds columns for the all of the columns in that table with the specified suffix, but it also expects to find a modified column for each of the associations even though they are owned by the foreign side.
In the below case, Envers expects columns in A for "foo" "fooModified", and "bObjectsModified" when I would think that it should expect columns for "foo" and "fooModified" in A and "aIdModified" in B.
#Entity
#Table("A")
#Audited
class A {
private String foo;
private Set<B> bObjects;
#Column(name = "foo")
public getFoo( return foo; )
#OneToMany(fetch = FetchType.LAZY,
mappedBy = "a")
public Set<B> getBObjects() { return bObjects; }
}
#Entity
#Table("B")
#Audited
class B {
private A a;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "aId")
public getA(){ return a; }
}
Has anyone else seen this? How do I change that behavior other than annotating every one of my #ManyToOne relationships with #Audited(withModifiedFlag=false). I have many thousands of relationships, so even testing that part will be a huge pain.
The alternative is forcing the database to know details about our Java code that it has no business knowing and makes it much more difficult to add bi-directional associations.
For those who may come later, at least as of 4.3.1.Final, the only way to do this is to remove the global configuration flag and add that option to the #Audited annotation on every class so that it is #Audited(withModifiedFlag=true) and then add #Audited(withModifiedFlag=false) to every property (not column!) in that class for which you do not want a modified field to be created.
In the other Hibernate modules, global configuration options can be overridden at the class or attribute level. For Envers, global configuration options can never be overridden.
Also note that the modified field names are based on the attribute name in the Java class and not the value in the #Column annotation that the rest of Hibernate ORM uses.

JPA OneToMany eager fetch does not work

I have a weird problem with two entities with one-to-many relation in JPA. I am using Glassfish 3.1.2.2 with EclipseLink 2.3.2. This is the first entity:
#NamedQueries({
#NamedQuery(name="SampleQueryGroup.findAll", query="SELECT g FROM SampleQueryGroup g")
})
#Entity
public class SampleQueryGroup implements Serializable {
// Simple properties, including id (primary key)
#OneToMany(
mappedBy = "group",
fetch = FetchType.EAGER,
cascade = {CascadeType.REMOVE, CascadeType.MERGE}
)
private List<SampleQuery> sampleQueries;
// Gettes/setters, hashcode/equals
}
And this is the second one:
#Entity
public class SampleQuery implements Serializable {
// Simple properties, including id (primary key)
#ManyToOne(cascade = {CascadeType.PERSIST})
private SampleQueryGroup group;
// Gettes/setters, hashcode/equals
}
I have a stateless session bean which uses an injected EntityManager to run SampleQueryGroup.findAll named query. I also have a CDI managed bean which calls the SSB method and iterates through SampleQueryGroup.getSampleQueries() for each SampleQueryGroup returned by the method. I didn't paste the code as it is pretty straightforward and somehow standard for any Java EE application.
The problem is the eager fetch does not work and getSampleQueries() returns an empty list. However, when I change the fetch type back to FetchType.LAZY, everything works and I get the list correctly populated. I don't understand why this happens. Does it have anything to do with internal caching mechanisms?
My guess is that when you add a new SampleQuery you are not adding it to the SampleQueryGroup sampleQueries, so when you access it, it is not their. When it is LAZY you do not trigger it until you have inserted the SampleQuery, so then it is there.
You need to maintain both sides of your relationships. (you could also disable caching, or refesh the object, but your code would still be broken).
See,
http://en.wikibooks.org/wiki/Java_Persistence/Relationships#Object_corruption.2C_one_side_of_the_relationship_is_not_updated_after_updating_the_other_side

How can I access the underlying column after defining a #ManyToOne relationship on it in Spring?

I'm using Spring 3.2 with Roo 1.2.3 to build a database-backed Java application via Hibernate. I have several bidirectional OneToMany/ManyToOne relationships among the tables in my database. When I set up the ManyToOne side of the relationship using #JoinColumn (via "field reference" in Roo), a new field whose type is the related entity (the "one" in ManyToOne) is created. However, once this is done, there seems to be no way to access the underlying column value on which the ManyToOne relationship is based. This is a problem when the underlying join column contains data needed by the application (i.e. when the join column contains product stock numbers).
Is there any way to set up my entity class so that the column on which its ManyToOne relationship is based remains accessible without traversing the new join property? How can I define an accessor method for the value of this column?
I've been looking online for an answer to this question for several days, but to no avail. Thanks in advance for your help.
just map the column a second time with insertable=false and updateable=false
To make it more concrete. It's possible to do a HQL-SELCT and restrict a ManyToOne relationship, without any join in the resulting SQL:
Instead of using a join in
session.createQuery("FROM Person person WHERE person.adress.id = 42")
we use can use the adress_idcolumn
session.createQuery("FROM Person person WHERE person.adressId = 42")
This works, if you specify an additional adressId field, which is only used as mapping info for Hibernate:
#Entity
#Access(AccessType.FIELD)
public class Person{
#Id
String id;
#JoinColumn(name = "adress_id")
#ManyToOne(fetch = FetchType.LAZY)
#Nullable
public Adress adress;
#Column(name = "adress_id", insertable = false, updatable = false)
private String adressId;
}
#Entity
#Access(FIELD)
public class Adress{
#Id
String id;
}
The AccessType.FIELD is not needed (But we can leave getters/setters in example). The FetchType.LAZY and #Nullable are also optional, but make it clear when it makes sense to use it. We are able to load Person entities which have a specific Address (we know the address id). But we don't need a join because it's not needed for the WHERE-clause and not for the initial fetch (the address can be fetched lazy).

Categories