Hibernate Enver : #AuditJoinTable rows missing - java

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.

Related

Mimicing Deferred Constraint with MySQL in Hibernate

As I understand from the MySQL documentation and from this question, it is NOT POSSIBLE to use deferred constraint in MySQL.
So, my question is; with below entity model (a uni-directional relation) in Hibernate, I want to swap two a collection of Package between two Product entities within a Transaction.
public class Product{
#OneToMany(fetch = FetchType.LAZY)
#JoinTable(
name="product_selected_packages",
joinColumns = {#JoinColumn(name = "product")},
inverseJoinColumns = {#JoinColumn(name="packages",unique = true)},
uniqueConstraints = {#UniqueConstraint(name = "UK_package",columnNames = {"packages"})}
)
private List<Package> packages;
}
public class Package {
#Column(unique = true)
private String code;
}
But when I write this type of code below (this is all done within a #Transactional method),
List<Package> product1Packages = new ArrayList<>(product1.getPackages()); // save product1 packages into memory
product1.getPackages().clear(); // clear all product1 packages
session.flush(); // flush session in order to sync db with hibernate
product2.getPackages().addAll(product1Packages);
Of course there is a more complex logic behind it but in a general way, Hibernate produces below SQL(which makes sense)
START TRANSACTION;
DELETE FROM product_selected_packages WHERE product = :SOME_ID;
INSERT INTO product_selected_packages(product,packages) VALUES (1,1) , (1,2) , (1,3) ...;
ROLLBACK; -- OR COMMIT
But due to MySQL's limitations on constraints which is stated in the documentation; above statement throws an error.
Duplicate entry '1621' for key 'UK_unique_index_constraint'
Because documentation states;
MySQL checks foreign key constraints immediately; the check is not
deferred to transaction commit.
Long story short, how can I create a work-around method for this type of situation without losing the Transaction ?

Lazy One-to-one Optional Bidirectional Mapping using Hibernate annotations

I want to create a Lazy One-to-one Bidirectional 'Optional' Mapping using Hibernate annotations. I know that the normal usage of #MappedBy and #JoinColumn result in N+1 queries being fired every time.
Is there a way I can avoid this? Not just at runtime, but at the POJO level. I am using Hibernate 4.3, so can't think about bytecode enhancement.
Further, if there is no way out, is it possible to apply criteria on unidirectional mappings. For example, I have A <-> B, and C -> A as mappings. And I am searching on B. Is it possible to apply a restriction on C when C is clearly unidirectional with A?
The #OneToOne annotaion doesn't work in hibernate as needed. Please consider the #LazyToOne or try using #OneToMany like #OneToOne. Also you can attempt #PrimaryKeyJoinColumn.
p.s. The #LazyToOne annotation doesn't exist in JPA realization, you should use #OneToOne(fetch = FetchType.LAZY, optional = false) there
I could not find a complete but minimal examples of LAZY bidirectional #OneToOne, so here it is. It is neither hibernate-version-dependent nor does it misuse #OneToMany.
Parent
Defines the id and is responsible for managing the consistency/synchronization, but technically does not own the relationship, because it can not reference any unique index in B (or at least we do not want to add redundant data).
#Entity
public class A {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne(
mappedBy = "a",
cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY
)
private B b;
public void setB(B b) {
if (b == null) {
if (this.b != null) {
this.b.setA(null);
}
} else {
b.setA(this);
}
this.b = b;
}
// ... other setters/getters
}
Child
Technically owns the relationship by re-using the id of parent A.
#Entity
public class B {
#Id
// Not generated but shared with A
private Long id;
#OneToOne(fetch = FetchType.LAZY)
#MapsId
#JoinColumn(name = "id") // otherwise use "a_id" instead of "id" in the DB column
private A a;
// ... other setters/getters
}
And this is how the tables should look like (assuming postgres):
CREATE TABLE a (
id bigint NOT NULL PRIMARY KEY,
);
CREATE TABLE b (
id bigint NOT NULL PRIMARY KEY,
FOREIGN KEY (id) REFERENCES a(id);
);

Using primary key of one table as foreign key to another table [duplicate]

I receive following error when I save the object using Hibernate
object references an unsaved transient instance - save the transient instance before flushing
You should include cascade="all" (if using xml) or cascade=CascadeType.ALL (if using annotations) on your collection mapping.
This happens because you have a collection in your entity, and that collection has one or more items which are not present in the database. By specifying the above options you tell hibernate to save them to the database when saving their parent.
I believe this might be just repeat answer, but just to clarify, I got this on a #OneToOne mapping as well as a #OneToMany. In both cases, it was the fact that the Child object I was adding to the Parent wasn't saved in the database yet. So when I added the Child to the Parent, then saved the Parent, Hibernate would toss the "object references an unsaved transient instance - save the transient instance before flushing" message when saving the Parent.
Adding in the cascade = {CascadeType.ALL} on the Parent's reference to the Child solved the problem in both cases. This saved the Child and the Parent.
Sorry for any repeat answers, just wanted to further clarify for folks.
#OneToOne(cascade = {CascadeType.ALL})
#JoinColumn(name = "performancelog_id")
public PerformanceLog getPerformanceLog() {
return performanceLog;
}
Introduction
When using JPA and Hibernate, an entity can be in one of the following 4 states:
New - A newly created object that hasn’t ever been associated with a Hibernate Session (a.k.a Persistence Context) and is not mapped to any database table row is considered to be in the New or Transient state.
To become persisted we need to either explicitly call the persist method or make use of the transitive persistence mechanism.
Persistent - A persistent entity has been associated with a database table row and it’s being managed by the currently running Persistence Context.
Any change made to such an entity is going to be detected and propagated to the database (during the Session flush-time).
Detached - Once the currently running Persistence Context is closed all the previously managed entities become detached. Successive changes will no longer be tracked and no automatic database synchronization is going to happen.
Removed - Although JPA demands that managed entities only are allowed to be removed, Hibernate can also delete detached entities (but only through a remove method call).
Entity state transitions
To move an entity from one state to the other, you can use the persist, remove or merge methods.
Fixing the problem
The issue you are describing in your question:
object references an unsaved transient instance - save the transient instance before flushing
is caused by associating an entity in the state of New to an entity that's in the state of Managed.
This can happen when you are associating a child entity to a one-to-many collection in the parent entity, and the collection does not cascade the entity state transitions.
So, you can fix this by adding cascade to the entity association that triggered this failure, as follows:
The #OneToOne association
#OneToOne(
mappedBy = "post",
orphanRemoval = true,
cascade = CascadeType.ALL)
private PostDetails details;
Notice the CascadeType.ALL value we added for the cascade attribute.
The #OneToMany association
#OneToMany(
mappedBy = "post",
orphanRemoval = true,
cascade = CascadeType.ALL)
private List<Comment> comments = new ArrayList<>();
Again, the CascadeType.ALL is suitable for the bidirectional #OneToMany associations.
Now, in order for the cascade to work properly in a bidirectional, you also need to make sure that the parent and child associations are in sync.
The #ManyToMany association
#ManyToMany(
mappedBy = "authors",
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
}
)
private List<Book> books = new ArrayList<>();
In a #ManyToMany association, you cannot use CascadeType.ALL or orphanRemoval as this will propagate the delete entity state transition from one parent to another parent entity.
Therefore, for #ManyToMany associations, you usually cascade the CascadeType.PERSIST or CascadeType.MERGE operations. Alternatively, you can expand that to DETACH or REFRESH.
This happens when saving an object when Hibernate thinks it needs to save an object that is associated with the one you are saving.
I had this problem and did not want to save changes to the referenced object so I wanted the cascade type to be NONE.
The trick is to ensure that the ID and VERSION in the referenced object is set so that Hibernate does not think that the referenced object is a new object that needs saving. This worked for me.
Look through all of the relationships in the class you are saving to work out the associated objects (and the associated objects of the associated objects) and ensure that the ID and VERSION is set in all objects of the object tree.
Or, if you want to use minimal "powers" (e.g. if you don't want a cascade delete) to achieve what you want, use
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
...
#Cascade({CascadeType.SAVE_UPDATE})
private Set<Child> children;
In my case it was caused by not having CascadeType on the #ManyToOne side of the bidirectional relationship. To be more precise, I had CascadeType.ALL on #OneToMany side and did not have it on #ManyToOne. Adding CascadeType.ALL to #ManyToOne resolved the issue.
One-to-many side:
#OneToMany(cascade = CascadeType.ALL, mappedBy="globalConfig", orphanRemoval = true)
private Set<GlobalConfigScope>gcScopeSet;
Many-to-one side (caused the problem)
#ManyToOne
#JoinColumn(name="global_config_id")
private GlobalConfig globalConfig;
Many-to-one (fixed by adding CascadeType.PERSIST)
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name="global_config_id")
private GlobalConfig globalConfig;
This occurred for me when persisting an entity in which the existing record in the database had a NULL value for the field annotated with #Version (for optimistic locking). Updating the NULL value to 0 in the database corrected this.
This isn't the only reason for the error. I encountered it just now for a typo error in my coding, which I believe, set a value of an entity which was already saved.
X x2 = new X();
x.setXid(memberid); // Error happened here - x was a previous global entity I created earlier
Y.setX(x2);
I spotted the error by finding exactly which variable caused the error (in this case String xid). I used a catch around the whole block of code that saved the entity and printed the traces.
{
code block that performed the operation
} catch (Exception e) {
e.printStackTrace(); // put a break-point here and inspect the 'e'
return ERROR;
}
Don't use Cascade.All until you really have to. Role and Permission have bidirectional manyToMany relation. Then the following code would work fine
Permission p = new Permission();
p.setName("help");
Permission p2 = new Permission();
p2.setName("self_info");
p = (Permission)crudRepository.save(p); // returned p has id filled in.
p2 = (Permission)crudRepository.save(p2); // so does p2.
Role role = new Role();
role.setAvailable(true);
role.setDescription("a test role");
role.setRole("admin");
List<Permission> pList = new ArrayList<Permission>();
pList.add(p);
pList.add(p2);
role.setPermissions(pList);
crudRepository.save(role);
while if the object is just a "new" one, then it would throw the same error.
beside all other good answers, this could happen if you use merge to persist an object and accidentally forget to use merged reference of the object in the parent class. consider the following example
merge(A);
B.setA(A);
persist(B);
In this case, you merge A but forget to use merged object of A. to solve the problem you must rewrite the code like this.
A=merge(A);//difference is here
B.setA(A);
persist(B);
If your collection is nullable just try: object.SetYouColection(null);
This issue happened to me when I created a new entity and an associated entity in a method marked as #Transactional, then performed a query before saving. Ex
#Transactional
public someService() {
Entity someEntity = new Entity();
AssocaiatedEntity associatedEntity = new AssocaitedEntity();
someEntity.setAssociatedEntity(associatedEntity);
associatedEntity.setEntity(someEntity);
// Performing any query was causing hibernate to attempt to persist the new entity. It would then throw an exception
someDao.getSomething();
entityDao.create(someEntity);
}
To fix, I performed the query before creating the new entity.
To add my 2 cents, I got this same issue when I m accidentally sending null as the ID. Below code depicts my scenario (and OP didn't mention any specific scenario).
Employee emp = new Employee();
emp.setDept(new Dept(deptId)); // --> when deptId PKID is null, same error will be thrown
// calls to other setters...
em.persist(emp);
Here I m setting the existing department id to a new employee instance without actually getting the department entity first, as I don't want to another select query to fire.
In some scenarios, deptId PKID is coming as null from calling method and I m getting the same error.
So, watch for null values for PK ID
It can also happen when you are having OneToMany relation and you try to add the child entity to the list in parent entity, then retrieve this list through parent entity (before saving this parent entity), without saving child entity itself, e.g.:
Child childEntity = new Child();
parentEntity.addChild(childEntity);
parentEntity.getChildren(); // I needed the retrieval for logging, but one may need it for other reasons.
parentRepository.save(parentEntity);
The error was thrown when I saved the parent entity. If I removed the retrieval in the previous row, then the error was not thrown, but of course that's not the solution.
The solution was saving the childEntity and adding that saved child entity to the parent entity, like this:
Child childEntity = new Child();
Child savedChildEntity = childRepository.save(childEntity);
parentEntity.addChild(savedChildEntity);
parentEntity.getChildren();
parentRepository.save(parentEntity);
If you're using Spring Data JPA then addition #Transactional annotation to your service implementation would solve the issue.
I also faced the same situation. By setting following annotation above the property made it solve the exception prompted.
The Exception I faced.
Exception in thread "main" java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.model.Car_OneToMany
To overcome, the annotation I used.
#OneToMany(cascade = {CascadeType.ALL})
#Column(name = "ListOfCarsDrivenByDriver")
private List<Car_OneToMany> listOfCarsBeingDriven = new ArrayList<Car_OneToMany>();
What made Hibernate throw the exception:
This exception is thrown at your console because the child object I attach to the parent object is not present in the database at that moment.
By providing #OneToMany(cascade = {CascadeType.ALL}) , it tells Hibernate to save them to the database while saving the parent object.
i get this error when i use
getSession().save(object)
but it works with no problem when I use
getSession().saveOrUpdate(object)
For the sake of completeness: A
org.hibernate.TransientPropertyValueException
with message
object references an unsaved transient instance - save the transient instance before flushing
will also occur when you try to persist / merge an entity with a reference to another entity which happens to be detached.
One other possible reason: in my case, I was attempting to save the child before saving the parent, on a brand new entity.
The code was something like this in a User.java model:
this.lastName = lastName;
this.isAdmin = isAdmin;
this.accountStatus = "Active";
this.setNewPassword(password);
this.timeJoin = new Date();
create();
The setNewPassword() method creates a PasswordHistory record and adds it to the history collection in User. Since the create() statement hadn't been executed yet for the parent, it was trying to save to a collection of an entity that hadn't yet been created. All I had to do to fix it was to move the setNewPassword() call after the call to create().
this.lastName = lastName;
this.isAdmin = isAdmin;
this.accountStatus = "Active";
this.timeJoin = new Date();
create();
this.setNewPassword(password);
There is another possibility that can cause this error in hibernate. You may set an unsaved reference of your object A to an attached entity B and want to persist object C. Even in this case, you will get the aforementioned error.
There are so many possibilities of this error some other possibilities are also on add page or edit page. In my case I was trying to save a object AdvanceSalary. The problem is that in edit the AdvanceSalary employee.employee_id is null Because on edit I was not set the employee.employee_id. I have make a hidden field and set it. my code working absolutely fine.
#Entity(name = "ic_advance_salary")
#Table(name = "ic_advance_salary")
public class AdvanceSalary extends BaseDO{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "employee_id", nullable = false)
private Employee employee;
#Column(name = "employee_id", insertable=false, updatable=false)
#NotNull(message="Please enter employee Id")
private Long employee_id;
#Column(name = "advance_date")
#DateTimeFormat(pattern = "dd-MMM-yyyy")
#NotNull(message="Please enter advance date")
private Date advance_date;
#Column(name = "amount")
#NotNull(message="Please enter Paid Amount")
private Double amount;
#Column(name = "cheque_date")
#DateTimeFormat(pattern = "dd-MMM-yyyy")
private Date cheque_date;
#Column(name = "cheque_no")
private String cheque_no;
#Column(name = "remarks")
private String remarks;
public AdvanceSalary() {
}
public AdvanceSalary(Integer advance_salary_id) {
this.id = advance_salary_id;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
public Long getEmployee_id() {
return employee_id;
}
public void setEmployee_id(Long employee_id) {
this.employee_id = employee_id;
}
}
I think is because you have try to persist an object that have a reference to another object that is not persist yet, and so it try in the "DB side" to put a reference to a row that not exists
Case 1:
I was getting this exception when I was trying to create a parent and saving that parent reference to its child and then some other DELETE/UPDATE query(JPQL). So I just flush() the newly created entity after creating parent and after creating child using same parent reference. It Worked for me.
Case 2:
Parent class
public class Reference implements Serializable {
#Id
#Column(precision=20, scale=0)
private BigInteger id;
#Temporal(TemporalType.TIMESTAMP)
private Date modifiedOn;
#OneToOne(mappedBy="reference")
private ReferenceAdditionalDetails refAddDetails;
.
.
.
}
Child Class:
public class ReferenceAdditionalDetails implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#OneToOne
#JoinColumn(name="reference",referencedColumnName="id")
private Reference reference;
private String preferedSector1;
private String preferedSector2;
.
.
}
In the above case where parent(Reference) and child(ReferenceAdditionalDetails) having OneToOne relationship and when you try to create Reference entity and then its child(ReferenceAdditionalDetails), it will give you the same exception. So to avoid the exception you have to set null for child class and then create the parent.(Sample Code)
.
.
reference.setRefAddDetails(null);
reference = referenceDao.create(reference);
entityManager.flush();
.
.
In my case , issue was completely different. I have two classes let's say c1 & c2. Between C1 & C2 dependency is OneToMany. Now if i am saving C1 in DB it was throwing above error.
Resolution of this problem was to get first C2's id from consumer request and find C2 via repository call.Afterwards save c2 into C1 object .Now if i am saving C1, it's working fine.
I was facing the same error for all PUT HTTP transactions, after introducing optimistic locking (#Version)
At the time of updating an entity it is mandatory to send id and version of that entity. If any of the entity fields are related to other entities then for that field also we should provide id and version values, without that the JPA try to persist that related entity first as a new entity
Example: we have two entities --> Vehicle(id,Car,version) ; Car(id, version, brand); to update/persist Vehicle entity make sure the Car field in vehicle entity has id and version fields provided
Simple way of solving this issue is save the both entity.
first save the child entity and then save the parent entity.
Because parent entity is depend on child entity for the foreign key value.
Below simple exam of one to one relationship
insert into Department (name, numOfemp, Depno) values (?, ?, ?)
Hibernate: insert into Employee (SSN, dep_Depno, firstName, lastName, middleName, empno) values (?, ?, ?, ?, ?, ?)
Session session=sf.openSession();
session.beginTransaction();
session.save(dep);
session.save(emp);
One possible cause of the error is the inexistence of the setting of the value of the parent entity ; for example for a department-employees relationship you have to write this in order to fix the error :
Department dept = (Department)session.load(Department.class, dept_code); // dept_code is from the jsp form which you get in the controller with #RequestParam String department
employee.setDepartment(dept);
I faced this exception when I did not persist parent object but I was saving the child. To resolve the issue, with in the same session I persisted both the child and parent objects and used CascadeType.ALL on the parent.
My problem was related to #BeforeEach of JUnit. And even if I saved the related entities (in my case #ManyToOne), I got the same error.
The problem is somehow related to the sequence that I have in my parent.
If I assign the value to that attribute, the problem is solved.
Ex.
If I have the entity Question that can have some categories (one or more) and entity Question has a sequence:
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "feedbackSeq")
#Id
private Long id;
I have to assign the value question.setId(1L);
Just make Constructor of your mapping in your base class.
Like if you want One-To-One relation in Entity A, Entity B.
if your are taking A as base class, then A must have a Constructor have B as a argument.

JPA - Eager - Lazy best practice

JBoss EAP 6
Hibernate 4
I have a J2EE application with a web browser client. ( Apache click )
Both the internal business logic and the client use the same entity objects.
I would like to have all relations in the entities set to lazy loading. This way I have good performance.
But when using the entities in the client ( that is the server side code of apache click ) I would need a lot of the relations to be eager loaded. The client code is accessing the back-end through a session bean.
So I have a couple of ways I can solve this:
Create 2 of each JPA entities, one with eager loading and one with lazy loading. And then use the one with eager loading in the client, and the one with lazy loading in the server. Most of the server logic will be in a transaction, so lazy loading is fine here.
Make all relations lazy loading. When accessing the entities from the client, make sure there is a transaction. ( #TransactionAttribute(TransactionAttributeType.REQUIRED) )
and code the access to the necessary fields so they are accessible after session bean call.
But that means that I have to start a transaction when that is not required, i.e. if I am only getting some objects. And I have to maintain more code. And I have to know exactly what relations the client needs.
Create an inheritance hierarchy, where I have a super entity, and then 2 child, one with objects relations lazy loaded, and one with only values, no objects. i.e. :
Super
#MappedSuperclass
public class SuperOrder {
#Id
#Column(name = "id")
#GeneratedValue(.....)
private Long id;
#Column(name = "invoice", length = 100)
private String invoice;
Child 1
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#Table(name = "testorder")
#SequenceGenerator(....)
public class Order extends SuperOrder {
#ManyToOne(targetEntity = PrintCustomerEnt.class, fetch = FetchType.EAGER, optional = true)
#JoinColumn(name = "print_customer_id", nullable = true)
#ForeignKey(name = "fk_print_customer")
#Valid
private PrintCustomerEnt printCustomer;
public PrintCustomerEnt getPrintCustomer() {
return printCustomer;
}
public void setPrintCustomer(final PrintCustomerEnt printCustomer) {
this.printCustomer = printCustomer;
}
}
Child 2
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#Table(name = "testorder")
#SequenceGenerator(...)
public class LazyOrder extends SuperOrder {
#Transient
private String printCustomerName;
#Column(name = "print_customer_id", nullable = true)
private Long printCustomerId;
What is the best practice... or is there something other good way to do this.
Basically the problem is I want to use the same entities in different scenarios. Sometimes I need eager loading, and sometimes I need lazy loading.
I suggest that you create just one JPA entity with lazy relationships, and when you need to load eagerly some of them create a Service that uses JPQL(HQL) to do some FETCH trick. The idea is one JPA entity and many services.
I've been programing in JPA 2 for some a while now, and I can say there are couple of now written rules that I almost always apply:
Use LAZY Inicialization on all your OneToMany, ManyToMany Relations
Use EAGER Inicalization on all your OneToOne, ManyToOne Relations
This rules apply on 99% of my projects. I think these are best practices due to my personal experience and some research I've been doing.
Note: I must say I do not use JOIN FETCH on Lazy Inicialization, instead I write a Prefetch Method. Example:
#Entity
class Entity{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
private Integer id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "mappedName",
orphanRemoval = true)
private List<Child1> collection1;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "mappedName",
orphanRemoval = true)
private List<Child2> collection2; }
And then we have the Controller:
class EntityController{
public Entity findCompraFolioFull(Integer id) {
EntityManager em = getEntityManager();
try {
Entity entity = em.find(Entity.class, id);
//Initialize Collections inside Transaccion, this prevents
//LazyInizialization No Proxy Exception later in code when calling
//hollow collections
cp.getCollection().size();
cp.getCollection().size();
return cp;
} finally {
em.close();
}
}
}
I don't recomend FETCH JOIN
public Entity findEntityByJoinFetch(Integer id) {
EntityManager em = getEntityManager();
try {
TypedQuery<Entity> tq = em.createQuery(
"SELECT e FROM Entity e\n"
+ "LEFT JOIN FETCH e.collection1\n"
+ "LEFT JOIN FETCH e.collection2\n"
+ "WHERE e.id = :id", Entity.class);
tq.setParameter("id", id);
return tq.getSingleResult();
} finally {
em.close();
}
}
Reasons I don't recomend Fetch Join Appoach:
If your collections are java.util.List type then this getSingleResult() will fail in hibernate due to lack of capacity to fetch MultipleBags without indexing notations on your OneToMany Relation.
You can always change the type of your collections to java.util.set in order to multiple bags to be fetched but this brings new kind of situations to deal with, Sets aren't ordered and HashCode() method won't work correctly so you'll have to #Override it inside Children Classes, and if you are using JAVAFX TableView to bind model to Items you won't be able to bind collections Set Type to Item Property of TableView, not directly at least.

JPA + CascadeType + cache

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.

Categories