Hibernate Envers creating modified fields for foreign owned OneToMany relationships - java

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.

Related

OneToMany ManyToOne removing child object with Hibernate

I'm using Spring Boot and Hibernate.
Lets assume we have two entities:
#Entity
public class A{
#OneToMany(mappedBy = "objectA",fetch = FetchType.EAGER,cascade = CascadeType.ALL,orphanRemoval=true)
private Set<B> objectSet = new HashSet<>();
}
#Entity
public class B{
#ManyToOne
private A objectA;
}
And we have two transacional methods;
deleteB_X(int idB){
entityManager.remove(entityManager.find(idB,B.class));
}
deleteB_Y(int idB){
B obj=entityManager.find(idB,B.class);
obj.getObjectA().getObjectSet().remove(obj);
}
What I understand (correct me if I'm wrong):
We have orphanRemoval=true so deleteB_Y(int) will work.
By setting mappedBy argument we say that class A is "the owning
site" of relation.
CascadeType is used when we persist/update/merge/remove class A (then it invokes persist/update/merge/remove on child property objectSet). I think we can say that it protects me from situation where I end up with object of B and no object of A class (unless we manually add some B objs).
From what I understand CascadeType should not interfare with orphanRemoval, because CascadeType takes care of things where we do 'some stuff' with A's objects (and then recursively do it to B's objects). And here is something that I don't understand at all.
Why deleteB_x(int) doesn't work and why if we remove CascadeType it starts working? I feel like that deleteB_X(int) is much cleaner solution to removing object B from the DB than deleteB_Y(int), but sadly it won't work since it colides with CascadeType.
EDIT1.
Method deleteB_X(int) just doesn't remove object from DB, if we remove cascade = CascadeType.ALL evertyhing works just fine. Why?
The issue was that my class A was fetched EAGER in class B instance and because of that (I assume) that there was a conflict when I was deleting B's instance alone without taking care of the same B instance in private Set<B> objectSet. Changing EAGER to LAZY or excluding CascadeType.PERSIST from #OneToMany(cascade=...) solved my issue.

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.

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).

JPA Cascading Delete: Setting child FK to NULL on a NOT NULL column

I have two tables: t_promo_program and t_promo_program_param.
They are represented by the following JPA entities:
#Entity
#Table(name = "t_promo_program")
public class PromoProgram {
#Id
#Column(name = "promo_program_id")
private Long id;
#OneToMany(cascade = {CascadeType.REMOVE})
#JoinColumn(name = "promo_program_id")
private List<PromoProgramParam> params;
}
#Entity
#Table(name = "t_promo_program_param")
public class PromoProgramParam {
#Id
#Column(name = "promo_program_param_id")
private Long id;
//#NotNull // This is a Hibernate annotation so that my test db gets created with the NOT NULL attribute, I'm not married to this annotation.
#ManyToOne
#JoinColumn(name = "PROMO_PROGRAM_ID", referencedColumnName = "promo_program_id")
private PromoProgram promoProgram;
}
When I delete a PromoProgram, Hibernate hits my database with:
update
T_PROMO_PROGRAM_PARAM
set
promo_program_id=null
where
promo_program_id=?
delete
from
t_promo_program
where
promo_program_id=?
and last_change=?
I'm at a loss for where to start looking for the source of the problem.
Oh crud, it was a missing "mappedBy" field in PromoProgram.
Double-check whether you're maintaining bidirectional association consistency. That is; make sure that all PromoProgramParam entities that link to a PromoProgram as its parent are also contained in said parent's params list. It's a good idea to make sure this happens regardless of which side "initiates" the association if you will; if setPromoProgram is called on a PromoProgramParam, have the setter automatically add itself to the PromoProgram's params list. Vice versa, when calling addPromoProgramParam on a PromoProgram, have it set itself as the param's parent.
I've encountered this problem before as well, and it was due to not maintaining bidirectional consistency. I debugged around into Hibernate and found that it was unable to cascade the delete operation to the children because they weren't in the list. However, they most certainly were present in the database, and caused FK exceptions as Hibernate tried to delete only the parent without first deleting its children (which you've likely also encountered with the #NonNull in place).
FYI, I believe the proper "EJB 3.0"-way of making the PromoProgramParam.promoProgram field (say that a 100 times) non-nullable is to set the optional=false attribute on the #ManyToOne annotation.

Categories