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

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.

Related

Child entity is not getting inserted/updated on parent entity update with onetomany

I am using #OneToMany annotation to save parent and child entities but I am facing issues while saving child entity in a particular case.
Child entity is getting saved in two cases:
During first insert of a parent with child.
During update of a parent with child when there was no child inserted/saved in database
in first insert
But When parent is inserted with child 1 and then during update of a parent I try to insert child 2 then I am not able to save the child 2
it is failing with below exception:
o.h.e.jdbc.spi.SqlExceptionHelper - ORA-01407: cannot update ("app_Name"."Child_entity"."REFERENCE_ID") to NULL\n
23:22:06.068 ERROR o.h.i.ExceptionMapperStandardImpl - HHH000346: Error during managed flush [org.hibernate.exception.ConstraintViolationException: could not execute statement]
Please see my code as below:
#Data
#Entity
#Table("Parent_table")
public class Parent_entity implements Serializable {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval =true)
#JoinColumn(name="REFERENCE_ID")
private Set<Child_Entity> childrens ;
}
#Data
#Entity
#Table("child_table")
public class Child_entity implements Serializable {
#Id
#GeneratedValue(generator = "seq_gen", strategy = GenerationType.SEQUENCE)
#SequenceGenerator(name = "seq_gen", sequenceName = "child_SEQ",allocationSize=1)
#Column(name ="col_name")
private Integer asSeq;
#Column(name ="REFERENCE_ID")
private String referenceid;
}
In mapper class, I am explicitly setting primary key of the parent table.
Oracle database side I have below foreign key constraint added
ALTER TABLE child_table
ADD CONSTRAINT FK_parent_table
FOREIGN KEY (REFERENCE_ID)
REFERENCES Parent_table(REFERENCE_ID);
I have browsed through similar question on stackoverflow, which suggests that if you are using existing column for foreign key then existing values for that column should not be null.
But in my case column "REFERENCE_ID" is already non nullable.
Please let me know or suggest if I need to add something else to make it work.
Thank you.
Edit:
In update scenario, Hibernate is generating below query:
update child_table set reference_id=null where reference_id=? and child_seq=?
where reference_id is Parent's primary key and child_seq is Child's primary key
Any idea why hibernate is trying to update Parent's primary key
I am explicitly setting Parent Primary key's value in Child's entity
There are actually three problems here:
Apparently "update scenario" inserts two new children instead of keeping one and adding one.
Unidirectional OneToMany relationship with a non-nullable join column
Lombok-generated equals and hashCode
TL;TR: GOTO 2
1. Update scenario
Hibernate is trying to update reference_id to NULL because it wants to "detach" a child entity from the parent. That means, that during update, you are adding two new children instead of keeping one and adding one. I haven't seen the relevant piece of code of yours, but I assume it might look more or less like this:
ParentEntity parent = new ParentEntity();
parent.setId("test");
ChildEntity child1 = new ChildEntity();
child1.setReferenceid(parent.getId());
parent.setChildrens(new HashSet<>(Arrays.asList(child1)));
repository.save(parent);
ChildEntity child2 = new ChildEntity();
child2.setReferenceid(parent.getId());
parent.getChildrens().add(child2);
repository.save(parent);
It ends up with a ConstraintViolationException. In the second save call, child1 is still a "detached" instance,
its id is NULL and Hibernate treats both children as they were new. So first it adds them to the child_table and later tries to remove the "old" one,
by setting its referenceId to NULL (orphan removal hapens later, and is kind of unrelated).
It could be easily fixed:
// ...
parent = repository.save(parent); // <- save(parent) returns updated object
ChildEntity child2 = new ChildEntity();
child2.setReferenceid(parent.getId());
parent.getChildrens().add(child2);
repository.save(parent);
No exceptions anymore but it doesn't solve the problem. Sooner or later you are going to remove a child from the children set and
it will always result in an exception.
2. Unidirectional OneToMany relationship with a non-nullable join column
The canonical way of modeling it would be as follows:
ParentEntity
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#JoinColumn(name = "REFERENCE_ID", nullable = false)
private Set<ChildEntity> childrens;
ChildEntity
#Column(name = "REFERENCE_ID", insertable = false, updatable = false)
private String referenceid;
It should work but Hibernate will generate unnecessary 'update' queries:
select parententi0_.parent_id as parent_i1_1_1_, childrens1_.reference_id as referenc3_0_3_, childrens1_.id as id1_0_3_, childrens1_.id as id1_0_0_, childrens1_.name as name2_0_ ...
select nextval ('child_seq')
select nextval ('child_seq')
insert into child_table (name, reference_id, id) values (?, ?, ?)
insert into child_table (name, reference_id, id) values (?, ?, ?)
update child_table set reference_id=? where id=?
update child_table set reference_id=? where id=?
delete from child_table where id=?
Not a big deal with one or two items, but with 100?
This happens because the ParentEntity is the
owner of the relationship (due to the #JoinTable annotation). It knows nothing about child_table foreign key and doesn't know how to deal with it.
2b. "Half of" bidirectional OneToMany relationship
Alternatively, we can try to make ChildEntity the owner of the relationship by removing #JoinColumn and adding mappedBy. In theory, there should be a corresponding #ManyToOne on the other side of the relationship, but it seems to work without it. This solution is optimal, might be not portable though (to different JPA providers or different Hibernate versions).
ParentEntity
#OneToMany(mappedBy = "referenceid", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private Set<ChildEntity> childrens;
ChildEntity (no changes, same as in the question)
#Column(name = "REFERENCE_ID")
private String referenceid;
On update Hibernate generates following queires:
select parententi0_.parent_id as parent_i1_1_1_, childrens1_.reference_id as referenc3_0_3_, childrens1_.id as id1_0_3_, childrens1_.id as id1_0_0_, childrens1_.name as name2_0_ ...
select nextval ('child_seq')
select nextval ('child_seq')
insert into child_table (name, reference_id, id) values (?, ?, ?)
insert into child_table (name, reference_id, id) values (?, ?, ?)
delete from child_table where id=?
3. Lombok-generated equals and hashCode
This is not directly related to your question but I think you will face this problem sooner or later. You are using #Data annotations (I assume they are Lombok's, if not, ignore this section). They will generate equals and hashCode methods from all the fields by default, including ids. It is fine in the ParentEntity, where the id is set manually. But in the ChildEntity, where the id (aka asSeq) is generated by the database, it breaks the hashCode()/equals() contract. It may lead to really sneaky bugs. From Hibernate documentation:
The issue here is a conflict between the use of the generated identifier, the contract of Set, and the equals/hashCode implementations. Set says that the equals/hashCode value for an object should not change while the object is part of the Set. But that is exactly what happened here because the equals/hasCode are based on the (generated) id, which was not set until the JPA transaction is committed.
You may want to read more about it here:
Implementing equals() and hashCode()
The JPA hashCode() / equals() dilemma

JPA one-to-one bidirectional mapping on same object using association table

I have a table Person and now I want to express a relation like "best friend". Assuming a person can only have one best friend I don't want to alter the Person table to add a best friend column, rather I want to have an additional mapping table, e.g.:
Table Person (id name):
1 foo
2 bar
3 somebody
4 somebodyelse
Table BestFriendMapping (personId bestfriendId):
1 2
3 4
I was doing something like this:
class Person {
#OneToOne()
#Fetch(FetchMode.SELECT)
#JoinTable(name = "BestFriendMapping",
joinColumns = #JoinColumn(name = "personId", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "bestfriendId",
referencedColumnName = "id"))
private Person bestFriend;
}
The problem is, that now when I add a new Person, the mapping table is populated with two entries, for example the newly added Person is having id=10 and his bestFriend 20, then the entries are:
10 20
20 10
I would like to have just one entry, but still be able to get the best friend of a person no matter which I have in my hand currently. I found out that I probably have done two unidirectional instead of one bi-directional mapping, so I have to use mappedBy, but I am not sure what is the syntax when it is about the one and the same entity object, thus one and the same field inside the object. The examples on the internet are always showing the mapping of two entities via a mapping table.
Or maybe something like this?!? In addition to the JoinColumns and InverseJoinColumns to add mappedBy to the OnetoOne just like this #OneToOne(mappedBy="bestFriend"), kind of weird :)

JPA recursive entity StackOverflowError

I have a User entity generated in Netbeans from an existing database table. The table has a column lastUpdatedByUser that is a User entity. Most of the tables in this database have a lastUpdatedByUser column and queries against those entities correctly return a user object as part of the result.
Ex. Retrieve FROM ProductionTable WHERE date = 'someDate' has a lastUpdatedByUser object that shows who last updated the table row and the rest of their user attributes.
If the productionTable data is edited in the web-app and submitted I need to update the lastUpdatedByUser column.
Users userUpdating = usersService.selectUserEntityByUserId(userId);
Users userEntity = usersFacade.findSingleWithNamedQuery("Users.findByUserId", parameters);
SELECT u FROM Users u WHERE u.userId = :userId
returns a User object that contains a lastUpdatedByUser that is a User object that contains a lastUpdatedByUser that is a User object that contains a lastUpdatedByUser object.... (I have no clue how many there are, and twenty rows of these adds up)
After I persist this
productionEntity.setLastUpdatedByUser(userUpdating);
I get Json StackOverflowError in the next request for the updated entity
gson.toJson(updatedProductionEntity)
The Users entity definition:
#OneToMany(mappedBy = "lastUpdatedByUser")
private Collection<Users> usersCollection;
#JoinColumn(name = "LastUpdatedByUser", referencedColumnName = "UserId")
#ManyToOne
private Users lastUpdatedByUser;
#OneToMany(mappedBy = "lastUpdatedByUser")
private Collection<Production> productionCollection;
How can edit that such that I continue to get a user object as part of other entities like Production, but only a single lastUpdatedByUser object for a User entity?
Thanks for any insight.
I'm guessing this is my issue:
#JoinColumn(name = "LastUpdatedByUser", referencedColumnName = "UserId")
as I found a FK in the Users table to its own UserId
Love refactoring
================================
Drop that FK from the Users table and regenerate the entity in Netbeans and I get
private Integer lastUpdatedByUser;
like it should be
instead of
private Users lastUpdatedByUser;
Now I get to edit all the entities that have valid FKs into the Users table and code and...
Thanks for listening.

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.

Lazy loading doesn't work - jpa

Hy all,
I have three tables Child, Pet and Toy. Pet has a reference key the child id, and a toy has a reference key to a dog id.
I want to load all data about a Child and his pets, but i don't want to load the toy data
#OneToMany(mappedBy = "petEntity", fetch = FetchType.LAZY)
public Set<PetEntity> getPetEntitySet() {
return petEntitySet;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ChildId", insertable = false, updatable = false)
public ChildEntity getChildEntity() {
return childEntity;
}
same for set of toys.
to load data i have
List<ChildEntity> list = entityManager.createQuery(
"SELECT c FROM ChildEntity c "+
"LEFT JOIN FETCH c.petEntitySet ",
ChildEntity.class).getResultList();
return list;
but this thing loads me all data, not just the information about child and his pets.
How can i suppress the entity manager to load only the data in the table, and not to make joins when i don't want that
Thanks for your advices
i forgot to mention that in this chase it doesn't only load all data, but it returns more than just one copy of a child elemnt
The reason why it is loading more data is because you are using the keyword fetch. This will EAGERLY load whatever the child's pets.
Just remove the fetch and lazy loading will occur.
Update
I see that this is actually what you want, so just ensure equals() and hashCode() is correctly overriden in all relevant classes, as I assume you are using Set.
In this way, jpa knows how to look for duplicates
Second update
Yes you can easily use DISTINCT in your queries.
Just add DISTINCT in your select
List<ChildEntity> list = entityManager.createQuery(
"SELECT DISTINCT c FROM ChildEntity c "+
"LEFT JOIN FETCH c.petEntitySet ",
ChildEntity.class).getResultList();
return list;