JPA Cascade remove orphans - jpa

I have a question. I use JPA with eclipselink and I have relationship:
Table A ---< Table AB >---- Table B
class A {
#OneToMany(mappedBy = "a")
private List<AB> abList
}
class AB {
#ManyToOne(fetch = FetchType.LAZY)
private A a;
#ManyToOne(fetch = FetchType.LAZY)
private B b;
}
class B {
#OneToMany(mappedBy = "b")
private List<AB> abList;
}
It is ManyToMany relationship between A and B with join table AB.
Now I want to remove a record from table A and to cascade remove records from table AB(join table) and from B table as well.
But from B only those which arent't connected to any other record from table A (many-to-many relationship between A and B).
How should I set CascadeType or orphanremoval to do it properly?

If you want to remove AB and B when A is deleted then just set cascade remove, (and orphanRemoval=true if you want removed instances to be removed).
If the relationship from A to B is truly ManyToMany, i.e. B can have multiple references, then you cannot cascade the remove to B, (but can still cascade to AB, ensure you maintain bi-directional relationships in your model).
There is no way to delete B if there happens to be no relationship to it from anywhere. This would be something like garbage collection, which does not exist in relational databases. You need to handle this from your application. If B is something you want to delete with A, then consider not having it shared, but have each A have its own private B instance.

I suppose you are working with Hibernate
you should make an orphanRoval=true,
after that try this:
supposing we needto remove a (which class is A),
we make :
for (AB ab : a.getAbList()) {
B b = ab.get();
if (b.getAbList().size()==1) {
em.remove(b);
}
em.remove(ab);
}
em.remove(a);
Hope this help

Related

How to load JPA-JoinTable entities, from an entity selected with mybatis?

I have 3 tables for this case. Table A, Table B, Table ab that has relationship between A and B.
Class A has a list of B annotated with JoinTable and OneToMany, and Table ab is being used for this.
public class A {
// ...
#OneToMany( ... )
#JoinTable( ... )
List<B> bList;
}
Class A is loaded with MyBatis, cause some nested query is needed for this api.
And being loaded by MyBatis, bList is remaining empty.
Is there any way to load bList with jpa, when class A is loaded by non-jpa method ?

How to successfully do delete/remove operation in ManyToMany relationship?

I'm using ManyToMany with JPA annotation, I need your valuable suggestions.
(Assume Person and Address. Same Address is referred to more person (living at same address)). I have to delete a person from that address.
Person p1 = new Person();
Person p2 = new Person();
Address add1 = new Address();
p1.add(add1);
p2.add(add1);
As well doing
add1.add(p1) ;
add1.add(p2) ;
THen on merge or persist iit mapped appropriately.
p1 - add1
p2 - add1
I have to delete p2 alone , when i did
p2.removeAddress(add1)
removeAddress(add1) {
addColelction.remove(add1) }
What happens is it deleted the entry for address and again by Hibernate jpa provider again tries to persist at Address entity and says "deleted entity passed to persist " and henc transaction roll back happens.
My correction on the question. The mapping exist as
In Script side :
#ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST,CascadeType.MERGE,CascadeType.REFRESH})
#JoinTable(name = "XXXX", joinColumns = { #JoinColumn(name = "X1_ID", nullable = false, updatable = false) }, inverseJoinColumns = { #JoinColumn(name = "X1_ID", nullable = false, updatable = false) })
#org.hibernate.annotations.Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
private Collection<Parser> parsers;
In Parser side
#ManyToMany(fetch = FetchType.EAGER, mappedBy = "parsers")
private Collection<Script> scripts;
The data saved as
Script1 - Parser1
Script2 - Parser1
Our data model is Object A has oneTomany to B , B has oneTomany to Script objects.
Say collection of A has (B1,B2,.....)
B1 has (Script1)
B2 has (Script2)
When we want to delete that B2 object (we do just EM.merge(A)), we want the particular B2 from the collection has to be deleted and the related the Script2 has to be deleted. Script2 delete should remove the intermediate entry alone but should not delete the Parser.
But Parser1 gets deleted and Transaction gets rolled back saying ''deleted entity passed to persist
Please share your ideas.
You mention you only want the join table cleared up, but you have both DELETE_ORPHAN and cascade all set on the Script->Parser mapping. The first setting seems to be a hibernate option equivalent to JPA's orphan removal, which will cause any entities de-referenced from the collection to be deleted in the database. This will cause Address1 in the example to be deleted.
The cascade all option will force the remove operation to be cascaded to any entities still referenced by Person/Script when remove is called on Person/Script. In the first example this will cause Address2 to be removed/deleted from the database.
If you want address 1 or address2 to survive, then remove the appropriate setting. As mentioned in comments, you will also want to clean up your references, as the survivors will be left referencing a deleted Person/Script entity which may cause problems in your application.

Correct way to statelessly update a one-to-many relationship in JPA?

I have a REST interface for a datamodel that has several one-to-many and many-to-many relationships between entities. While many-to-many relationships seem easy to manage statelessly, I'm having trouble with one-to-many. Consider the following one-to-many relationship:
Employee:
#ManyToOne
#JoinColumn(name = "Company_id")
private Company company;
Company:
#OneToMany(mappedBy = "company", cascade = CascadeType.ALL, orphanRemoval=true)
public Set<Employee> employees = new HashSet<Employee>();
When a company is updated, its employee collection may have been updated as well (employees removed or added) but since the REST interface only allows updating the company as a whole, I cannot explicitly delete or add employees.
Simply replacing the collection does not work, but I found that this seems to work:
public void setEmployees(Set<Employee> employee) {
this.employees.clear(); // magic happens here?
this.employees.addAll(employees);
for (Iterator<Employee> iterator = employees.iterator(); iterator.hasNext();) {
Employee employee = (Employee) iterator.next();
employee.setCompany(this);
}
}
Is this the way it should be done, or is there a better way?
EDIT: In fact the above does not work! It appears to work at first, but then it will break with:
Exception in thread "main" java.lang.IllegalStateException: An entity copy was already assigned to a different entity.
I assume this happens because the db already contains a set of employees and if any of the "old" employees are also part of the replacement set, they collide with the ones in the database.
So what is the right way to replace the set?
First make sure equals is implemented properly. As per hibernate spec: http://docs.jboss.org/hibernate/orm/4.1/manual/en-US/html/ch04.html#persistent-classes-equalshashcode
I had a similar problem doing a merge. Essentially I had to fetch the existing employees associated with the company. I had to merge any changes to existing employees, and then add any new employees.
Query query = em.createQuery("select e from Employee e where e.company = '" + company.getId() + "'");
Collection<Employee> existingEmployees = new LinkedList<Employee>();
try{
Iterables.addAll(existingEmployees, (Collection<Employee>) query.getResultList());
}
catch(NoResultException nre){
//No results
}
for(Employee existingEmployee : existingEmployees){
for(Employee employee : company.getEmployees()){
if(existingEmployee.name().equals(employee.name())){
employee.setId(existingEmployee.getId());
}
employee.setCompany(company);
}
}
i think you have no better choice then to replace the existing collection and simply set the new one provided by the REST response.

Hibernate Envers : track revisions in the owning side of a OneToMany relation

I have two audited entities, A and B. Entity A holds a collection of entity B (annotated as One-to-many relationship). When inserting a new instance of A into the database, all rows of A and B are at the same revision (let's say revision 1). Then, there is an update on A which only affect the instances of entity B (cascade type is merge). So after the update, the entity A is still at revision 1, whereas the entities of B are at revision 2 (new MOD entry in the audit table).
The problem is when I retrieve all the revisions of A, I would expect to get 2 revisions in return : one for the creation, one for the modification of the owning collection of B.
I can get this behaviour in case of ManyToMany but I can't get it work the same way with a OneToMany relation.
(I'm using Hibernate 3.6.10-Final)
I solved my problem by adding a hidden lastUpdated date field on my equivalent of your A entity.
#Entity
public class A {
private Date lastModified;
#OneToMany(mappedBy = "a", cascade = CascadeType.ALL )
private List<B> blist;
public void touch(){
lastModified=new Date();
}
}
In the related entities (like you B field), I added the following :
public class B {
#ManyToOne
private A a;
#PreUpdate
public void ensureParentUpdated(){
if(a!=null){
a.touch();
}
}
}
This ensures that a revision is added to A whenever a revision is added to B.

JPA OneToMany relations and performace

I have two entities: parent Customer and child Order.
Each Customer has 1,000,000 Orders for example, so it is not needed in any given time to load a Customer with all Orders but I want to have this ability to make join query on these two entities in JPA.
So because of this, I must create #OneToMany relationship for making join queries.
My question is: how to get query without making joinColumn because even in Lazy mode it is possible to load 1,000,000 objects!
I just want to get query on these object with where restrictions like native join.
If you don't want the #OneToMany relationship implicitly set in your Customer class than you don't have to. You can execute JPQL queries (in very precise manner) without the marked relationship.
Assume you have:
#Entity
public class Customer {
// all Customer-related fields WITHOUT #OneToMany relationship with Order
}
#Entity
public class Order {
#ManyToOne
private Customer owner;
}
Then if you want to get all Orders for particular Customer you can execute a simple JPQL query like that:
// Customer customer = ...
// EntityManager em = ...
String jpql = "SELECT o FROM Order o WHERE o.owner = :customer";
TypedQuery<Order> query = em.createQuery(jpql, Order.class);
query.setParameter("customer", customer);
List<Order> orders = query.getResultList();
In this way you can execute the code only when you're really sure you want to fetch Customer's orders.
I hope I've understood your problem correctly.
EclipseLink has support for QueryKeys, that allow you to define fields or relationships for querying that are not mapped. Currently there in no annotation support for query keys, but you can define them using the API and a DescriptorCustomizer.
Also you do not need the OneToMany to query on it, just use the inverse ManyToOne to query,
i.e.
Select distinct c from Customer c, Order o where o.customer = c and o.item = :item
Or,
Select distinct o.customer from Order o join o.customer c where o.customer = c and o.item = :item