I have some issues with JPA cache, the scenario is as follows:
I had two classes A and B with relationship ManyToMany, but then i split the ManyToMany into two OneToMany relations and new class
public class A{
#OneToMany(cascade = CascadeType.ALL, mappedBy="a")
private List<AB> ab;
}
public class B{
#OneToMany(cascade = CascadeType.ALL, mappedBy="b")
private List<AB> ab;
}
public class AB{
#ManyToOne
#JoinColumn(name = "A_id", referencedColumnName="id")
private A a;
#ManyToOne
#JoinColumn(name = "B_id", referencedColumnName="id")
private B b;
...other fields
}
My problem is that when i delete A: i want to A and AB to be deleted, and B not touched in db (the same if i delete B).
That works, and DB state is fine but there is problem with JPA and cache.
When i remove A (and AB with cascade), JPA cache still holds references to them in B instance - what is undesirable. So when i query for B i will find nested AB instances that were removed.
I tried to put cascades on AB ManyToMany fields but it doesn't help.
If i clear cache with : entityManager.getEntityManagerFactory().getCache().evictAll(); everything works but it's not a good solution.
I use EntityManager with EJB3, and cascades from javax.persistence.
I would be grateful for answers.
Your problem is similar to eclipselink 2 OneToMany with one association table how to delete from one side . So, before deleting A (and therefore deleting AB), remove all references from B to AB.
Related
I have two entities A and B. A is parent of B, ie A is having a list of B as child.
The JPA classes corresponding look like below :
The first entity :
#Entity
#Table(name = "A")
public class A {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "refA", orphanRemoval = true)
private List<B> listOfB;
}
The second entity :
public class B {
#JoinColumn(name = "A_REF")
#ManyToOne
private CoreRecord refA;
private int updatedColumn;
#PrePersist
private void prePersit() {
this.updatedColumn = 1;
}
#PreUpdate
private void preUpdate() {
this.updatedColumn = 2;
}
}
My issue now is that when i first persit A whith a list of B, everything goes fine. Each object of B is cascadly persisted and the updatedColumn if filled correcty.
Then juste after persisting A, if i add a new instance of B in the listOfB and call merge on A, the new instance of B will be persisted in the database but the value of updatedColumn not filled correctly.
When i debug the code, i can notice that the prePersit method is being called but the modification done to my updatedColumn field is never persisted in the database. I have also notice that the preUpdate method is never called.
I would like the modification done in one of the method prePersit or preUpdate to the updatedColumn of the new instance of B to be persisted in the database when i call merge on A.
I've been tourning around searching what i'm doing wrong. Since then i have'nt found the problem nor the solution. I need your help please. Thanks in advance.
My JPA provider is eclipselink 2.5.2
Imagine having following simplified code using Hibernate and JPA:
#Entity
class C {
#Id #GeneratedValue public long id;
}
#MappedSuperclass
abstract class A {
#Id #GeneratedValue public long id;
#OneToMany(cascade = CascadeType.ALL)
public List<C> list1;
#OneToMany(cascade = CascadeType.ALL)
public List<C> list2;
}
#Entity
class B extends A { }
Using a PostgreSQL database, this leads to a table b_c with columns
b_id | list1_id | list2_id
Now, trying to persist any B leads to following exception:
org.postgresql.util.PSQLException: FEHLER: NULL-Wert in Spalte „list2_id“ verletzt Not-Null-Constraint
Detail: Fehlgeschlagene Zeile enthält (779, 827, null).
Which roughly translates to
NULL-Value in column "list2_id" violates Non-Null-Constraint. Contents: (770, 827, null)
Why does this happen and how can I avoid it?
Merging the lists into one is not an option. Using Sets instead does not change anything.
This is happening because of the unidirectional #OneToMany association. You need to first determine if your association is unidirectional or bidirectional.
If your association is unidirectional then you need to use #JoinColumn to fix the extra join table problem. If your association is bidirectional, then you will rely on the #ManyToOne side to propagate all entity state changes, with the use of mappedBy on the #OneToMany side. You can find good explanation about different ways of #OneToMany association here.
Given an Entity that is audited by Envers, which contains one collection.
Entity A
#Audited
public class A{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
....
#OneToMany
#JoinColumn(name = "a_id")
#AuditJoinTable(name = "A_B_AUDIT"
,inverseJoinColumns = #JoinColumn(name = "a"))
private List<B> bs;
....
}
Entity B
#Audited
public class B{
#Id
private int id;
....
#Column(name = "a_id")
private int aId;
#ManyToOne
#JoinColumn(name = "a_id", insertable = false, updatable = false)
private A a;
}
As per their documentation
Envers stores the audit information for additions and deletions to these AuditJoinTables (A_B_AUDIT). But Unfortunately, this is not working and the rows inside the tables are missing.
When I run my project following tables gets created :
A_AUDIT
B_AUDIT
A_B_AUDIT
I've separate controllers to persist A object and B Object. When I try to save B with aId and A, audit_table (A_B_AUDIT) does not gets updated but B_AUDIT with updated revision gets updated.
Can someone please let me know what i am missing here.
Thank you !!
Hibernate Enver Version : 5.1.4.Final
As said in the comments, your issue is that you're performing the persistence of these two entities in separate transactions but you aren't making the proper association and thus you're tainting the state of Hibernate's first level cache. Inadvertently, you're also causing Envers not to be able to properly audit your entities because you're not giving it all the proper state.
In order for this use case to work with Envers, you need to modify how you are handling the persistence of your Entity B given that it is the only thing that seems to know about the relationship with Entity A.
During the persistence of Entity B, you should likely do this:
if ( b.getAId() != null ) {
A a = entityManager.find( A.class, b.getAId() );
a.getBs().add( b );
a = entityManager.merge( a );
b.setA( a );
entityManager.persist( b );
}
This means during the persistence of Entity B, you should get an audit row added in the audit join table like you expected and the state in the Hibernate first level cache will contain all the proper references between the two entities.
If we put Envers aside for a moment and focus on normal JPA provider usage, you should be able to do this after you persist B and it would work:
final A theA = b.getA();
assertNotNull( theA );
assertTrue( !theA.getBs().isEmpty() );
assertTrue( theA.getBs().contains( b ) );
Obviously if you aren't setting the associations, these assertions (which should all pass) won't. The only time they would pass would be when you requery entity B, but that should not be necessary.
Hopefully you understand.
I have a Class A which has an object of Class B which has an object of Class C. I want to get object of class B from object of class A without getting object of class C in b. (I have , and want to keep it this way, everything with lazy loading)
I am doing:
Hibernate.initialize(a.getObjectOfClassB());
But get exception. Is there any way to do what i want? Cutting the hibernate initialize chain?
Thanks in advanced!
So your entity structures appear to be mapped as follows:
public class EntityA {
#OneToMany(mappedBy = "a")
private List<EntityB> bList;
}
public class EntityB {
#ManyToOne
private EntityA a;
#OneToMany(mappedBy = "b")
private List<EntityC> cList;
}
public class EntityC {
#ManyToOne
private EntityB b;
}
So you have a specific EntityA that you want to fetch it's associated EntityB instances. You can obtain that list either at query time or as a post initialization step.
The important thing to note here is that the mappings between A - B - C are using #OneToMany which are lazily fetched by default.
To do this at query time:
SELECT a
FROM EntityA a
JOIN FETCH EntityB b
WHERE a.id = :entityAId
The returned EntityA already has your List<EntityB> already loaded for you and you need to do nothing else.
To do this as a post initialization step after you've fetched a single EntityA instance.
Hibernate.initialize(entityA.getBList());
or
entityA.getBList().size();
I have a the two following classes:
#Entity
class A {
#Id
private aId;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "AB", joinColumns = #JoinColumn(name = "aId", referencedColumnName = "aId"), inverseJoinColumns = #JoinColumn(name = "bId", referencedColumnName = "bId"))
private Set<B> bSet;
}
#Entity
class B {
#Id
private bId;
}
I load the complete object structure from one database and then enters a new transaction on the second database to persist the structure again. However the "AB" table is left empty. This is very strange as "B" is persisted though I only explicitly persist "A". I have check that A-objects contains non empty sets of B, so that is not a problem.
This leaves me with the conclusion that Hibernate believes that "AB"-table should exist as both "A" and "B" already have their primary keys. Is there a way around this so I can get Hibernate to persist the join-table in the second database?
I guess this is happening because you are using proxy object.That is if you create instances of A and B with new operator and then call persist ,Join table record will be created .But you are using object from obtained from entitymanager(these are proxy objects) so you have to merge object that way entitymanager will create new proxies of this objects.