I've been reading posts about orphanRemoval= true in JPA .
According to documentation :
orphanRemoval is a flag -
Whether to apply the remove operation to entities that have been
removed from the relationship and to cascade the remove operation to
those entities.
Also I refered to this article for more info , where they have tried to set child entity (address - in their example ) as null.
I currently understand that making orphanRemoval= true will perform similar operation as cascade=CascadeType.REMOVE and if I remove my parent entity , it will delete the child entity as well .
What i want to test is the additional functionality that it brings which is removal of entities that are not referenced by their parent entity.
I am trying to create a similar scenario where I am setting the new collection of phones as new ArrayList<>() where the parent entity is Person .
Following are my entity classes .
Person.java
#Entity
#Table(name = "person")
#Data
public class Person {
#Id
int pd ;
String fname;
String lname;
#OneToMany(fetch=FetchType.LAZY,cascade=CascadeType.ALL,mappedBy="person",orphanRemoval=true)
List<Phone> phones = new ArrayList<>() ;
public boolean addPhone(Phone phone) {
boolean added = this.phones.add(phone);
phone.setPerson(this);
return added;
}
}
Phone.java
#Entity
#Table(name = "phone")
#Data
public class Phone {
private int countryCode;
#Id
private String number ;
#ManyToOne
#JoinColumn(name="fk_person")
Person person ;
}
main class
public void testFlow() {
Person p = fetchById(765);
p.setPhones(new ArrayList<>());
personRepo.save(p); **// exception on this line**
getPersons();
}
public Person fetchById(int id) {
Optional<Person> pe = personRepo.findById(id);
Person person = pe.get();
System.out.println("person is :"+ person.getFname());
System.out.println("cc is :"+ person.getPhones().get(0).getNumber());
return person;
}
public List<Person> getPersons() {
List<Person> persons = personRepo.findAll();
persons.forEach(p -> {
System.out.println("person :"+p.getPd());
System.out.println("person phones :"+p.getPhones().get(0).getNumber());
System.out.println("=================================");
});
return persons;
}
The entry method is testFlow() .
When I execute this code , I get error :
org.hibernate.HibernateException: A collection with
cascade="all-delete-orphan" was no longer referenced by the owning
entity instance: com.example.entity.Person.phones
Any clue how i can test the working example of orphanRemoval ?
The problem is caused by the following line:
p.setPhones(new ArrayList<>());
In Hibernate, you cannot overwrite a collection retrieved from the persistence context if the association has orphanRemoval = true specified. If your goal is to end up with an empty collection, use p.getPhones().clear() instead.
This is the line the exception should be thrown:
personRepo.save(p);
It happens, because you are trying to save Person that doesn't reference any Phones. I means, that you're dereferencing only Person but not the Phone entities. Since it's a bidirectional relationship, you would have to dereference both:
public void testFlow() {
Person p = fetchById(765);
p.getPhones().foreach(ph -> ph.setPerson(null));
p.setPhones(new ArrayList<>());
personRepo.save(p); **// exception on this line**
getPersons();
}
Related
I have two entities bound with one-to-many relationship. But one entity can exist without the other. So the relationship is uni-directional. As this;
#Entity
public class TransportationOrderProduct {
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
private List<WarehousePackage> selectedWarehousePackages;
}
#Entity
public class WarehousePackage{
}
And hibernate created these tables;
TransportationOrderProduct
id
TransportationOrderProductSelectedWarehousePackages
transportationOrderProductId
selectedWarehousePackageId
WarehousePackage
id
When fetching the collection (selectedWarehousePackages) everything works fine.
But when I clear the TransportationOrderProduct.selectedWarehousePackages list, and add new ones, Hibernate throws DuplicateEntry exception. Saying that transportationOrderProductId in the TransportationOrderProductSelectedWarehousePackages table cannot be inserted twice.
I think this is because Hibernate doesn't delete the relation in the TransportationOrderProductSelectedWarehousePackages table when I call;
TransportationOrderProduct.selectedWarehousePackages.clear()
And add some entities after ;
TransportationOrderProduct.selectedWarehousePackages.add(newPackage)
TransportationOrderProduct.selectedWarehousePackages.add(newPackage)
.
.
Can somebody help?
Its sounds the relation is one-Many as I understand
Don’t use unidirectional one-to-many associations
avoid unidirectional one-to-many associations in your domain model. Otherwise, Hibernate might create unexpected tables and execute more SQL statements than you expected and this certainly described why hibernate create 3 entities while your implementation should be 2 entities with relation one-2-many.
The definition of uni-directional sounds not an issue in our case if we use directional it's OKAY it will serve our purpose you just need attribute to map our association
using annotation
#Entity
public class TransportationOrderProduct {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
private Set<WarehousePackage> packages= new HashSet<>();
public Set<WarehousePackage> getPackages() {
return packages;
}
public void setPackages(Set<WarehousePackage> packages) {
this.packages = packages;
}
public void addPackages (WarehousePackage value) {
this.packages.add(value);
}
public void clearPackages (WarehousePackage value) {
this.packages.clear();
}
...
}
#Entity
public class WarehousePackage{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
}
You can save transportation order with association you need
TransportationOrderProduct transportation = new TransportationOrderProduct ();
Set<WarehousePackage> packages = new HashSet <> () ;
WarehousePackage package1 = new WarehousePackage () ;
WarehousePackage package2 = new WarehousePackage () ;
packages.add(package1);
packages.add(package2);
transportation.setPackages(packages) ;
session.save(transportation);
I have an EJB many-to-many (bi-directional) relation between classes (entity-classes) Person and Hobby. There are corresponding tables in the database, called PERSON and HOBBY, as well as a table PERSON_HOBBY for the many-to-many relationship.
As I will detail below, the problem is that whenever I try to persist a person with hobbies, I run into a Foreign Key constraint violation. This is because the entityManager tries to save new rows into PERSON_HOBBY that contain references to a person-entity with ID=0, which doesn’t exist in the PERSON table. I’ll come back to that later, but first I’ll show the relevant parts of the entity classes.
First, here is entity class Person:
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(nullable = false)
private String name;
#Column(nullable = false)
private String email;
#ManyToMany(cascade = {CascadeType.MERGE}, fetch = FetchType.EAGER)
/* Note: I used to have CascadeType.PERSIST in the above line as well, but
it caused "Detached object passed to persist" exceptions whenever I tried to
persist a person with hobbies. So I suppose I was right in deleting
CascadeType.PERSIST...? */
#JoinTable(name = "PERSON_HOBBY",
joinColumns = #JoinColumn(name="personId", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="hobbyId", referencedColumnName="id"))
private List<Hobby> hobbies = new ArrayList<Hobby>();
public List<Hobby> getHobbies () {
return hobbies;
}
public void setHobbies (List<Hobby> hobbies) {
this.hobbies = hobbies;
for(Hobby h:hobbies) { // this is to maintain bi-directionality
h.addPerson(this);
}
}
// other getters and setters omitted here.
Then entity class Hobby:
#Entity
public class Hobby {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(nullable = false)
private String description;
#ManyToMany(mappedBy = "hobbies", fetch = FetchType.EAGER)
private List<Person> persons;
public Hobby() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
// getter and setter for Description omitted here.
public List<Person> getPersons () {
return persons;
}
public void setPersons (List<Person> persons) {
this.persons = persons;
}
public void addPerson (Person p) {
this.persons.add(p);
}
}
I also have a stateless session bean, that’s shown here as far as relevant to the issue:
#Stateless
#Default
public class PersonRepositoryImpl implements PersonRepository {
#PersistenceContext
private EntityManager entityManager;
#Override
public Person create(Person p) {
entityManager.persist(p);
entityManager.flush();
return p;
}
#Override
public Person createPersonWithHobbies(Person p, List<Hobby>hobbyLijst) {
p = create(p); // I've also tried simply: create(p);
System.out.println("The newly assigned ID for the persisted
person is: " + p.getId());
// That last line **always** prints the person-ID as being 0 !!!!
p.setHobbies(hobbyLijst);
entityManager.merge(p); // This should save/persist the person's hobby's!
entityManager.flush();
return p;
}
}
Now from my servlet, I've been trying in two different ways. First, I tried calling method create(p) on the above session bean. That is, after creating a new Person instance p, setting all its non-relational fields, AND calling setHobbies on it (with a non-zero list of Hobby objects taken from the database), I called:
personRepo.create(p);
But this resulted in the Foreign Key (FK) exception:
INSERT on table 'PERSON_HOBBY' caused a violation of foreign key
constraint 'FK_EQAEPVYK583YDWLXC63YB3CXK' for key (0). The statement
has been rolled back.”
The FK-constraint mentioned here is the one in PERSON_HOBBY referring to PERSON.
The second way I tried was to make the following call from the servlet:
personRepo.createPersonWithHobbies(p, hobbyLijst);
where, just like before, p is the new person object; and hobbyLijst is that person's list of hobbies. And this resulted in the exact same FK-exception as the earlier call to personRepo.create(p).
Importantly, the println statement within method createPersonWithHobbies, calling getId() on the newly persisted person-object, ALWAYS gives that object's ID as being 0. Which I suppose does explain the FK-exception, since there's no person entity/row in the PERSON table with an ID of 0, nor is there supposed to be one. But of course the getId() call should not output 0. Instead, it should output the newly generated ID of the newly persisted person entity. (And yes, it IS persisted correctly in the PERSON tabel, with a correctly generated ID>0. So the correct ID is there in the PERSON-table - it just seems to be invisible to the entityManager and/or the container.)
Thanks.
I randomly get the following exception: org.eclipse.persistence.exceptions.ValidationException Exception Description: The attribute [id] of class [domain.Person] is mapped to a primary key column in the database. Updates are not allowed.
The exception is thrown mostly when executing "deleteAllPersons()". The error occurs when there exists a link between the 2 entities that has to be deleted, and Seems to occur on the second person that gets deleted in my case.
It is also noteworthy that the error often doesn't appear in debug mode or when the test suite is run on it's own. This leads me to believe that the problem might be timing related or that the cause is an inconsistency that in some cases gets resolved automatically before it causes any issues.
public void deleteAllPersons(){
for(Person person: getAllPersons()){
long id = person.getId();
deletePerson(id);
}
with the deletePerson(id) method being
public void deletePerson(long id) {
Person person = getPerson(id);
List<OrderBill> ordersToBeUnlinked = new ArrayList<>();
ordersToBeUnlinked.addAll(person.getOrders());
for (OrderBill order : ordersToBeUnlinked) {
person.removeOrder(order);
System.out.println(order + "deleted");
}
try{
manager.getTransaction().begin();
manager.merge(person);
} catch (Exception e1){
throw new DbException("Error merging person" + person + "\n" + e1.getMessage(), e1);
}
try {
manager.remove(person);
manager.flush();
manager.getTransaction().commit();
} catch (Exception e){
manager.getTransaction().rollback();
throw new DbException("error deleting " + person + "\n" + e.getMessage(), e);
}
}
Merging goes fine, but on deleting the exception is thrown.
In the exception message I can see that Person has no more orders in it's list
In case I made a mistake in the Person or OrderBill class,
Here is the most important part of both of those classes:
Person
#Entity(name="Person")
public class Person {
#Id
#Column(name="PERSON_ID")
#GeneratedValue
private long id;
#ManyToMany(mappedBy="authors", fetch=FetchType.LAZY, cascade={CascadeType.MERGE})
private Set<OrderBill> orders;
#Column(name="name")
private String name = "undefined";
public Person(String name){
setName(name);
payments = new HashSet<Payment>();
orders = new HashSet<OrderBill>();
}
public void removeOrder(OrderBill order){
orders.remove(order);
if(order.getAuthors().contains(this)){
order.removeAuthor(this);
}
}
OrderBill:
#Entity(name="OrderBill")
#NamedQueries({
#NamedQuery(name="Order.getAll", query="select o from OrderBill o"),
#NamedQuery(name="Order.findOrders", query="select o from OrderBill o join o.orderWeek as w where (w.weekNr = :w and w.yearNr = :y)")
})
public class OrderBill implements Transaction{
#Id
#Basic(optional = false)
#NotNull
#Column(name="ORDER_ID")
#GeneratedValue
private long id;
#ManyToMany
#JoinTable(
name="ORDER_PERSON",
joinColumns=#JoinColumn(name="ORDER_ID"),
inverseJoinColumns=#JoinColumn(name="PERSON_ID")
)
private Set<Person> authors;
public OrderBill(double totalCost, int weekNr, int yearNr){
setOrderWeek(new OrderWeek(weekNr, yearNr));
setTotalCost(totalCost);
authors = new HashSet<>();
}
public void removeAuthor(Person author){
authors.remove(author);
if(author.getOrders().contains(this))
author.removeOrder(this);
}
In case it helps someone, I'll write what I have found below:
Chris's comment turned out to be correct, but it took me a long time before I found where I made a mistake when trying to delete all references.
I don't know if this is the only problem, but I have found one main logic flaw:
When I want to delete the person I first remove all its references from the orders list and than merge the person to update the database.
But the orders list is marked "mappedBy" so its changes won't be reflected in the database
I had updated each order through "orders" list in person,so I expected the cascade merge to handle it
But by updating the person, the list with orders gets emptied. Because of this no references to the updated orders are left when the person gets merged (so the cascade merge doesn't affect them)
I fixed the problem by adding merge(order) inside the for-loop in deletePerson
for (OrderBill order : ordersToBeUnlinked) {
person.removeOrder(order);
manager.merge(order);
}
note: of course I had to put this for loop inside a transaction because of the merge.
I'm experiencing a problem I cannot get my head around. I really hope someone out there can help.
Entity model
This might get a little meta as my data model represents programming objects. I've got three entities: Program, Clazz and Method. Classes can have multiple methods and occasionally inherited some other methods (from other classes).
#Entity
public class Program {
#OneToMany(mappedBy="program", fetch=FetchType.LAZY, cascade=CascadeType.ALL)
#OrderBy("name ASC")
private Set<Clazz> clazzes = new TreeSet<Clazz>();
}
#Entity
public class Clazz {
#ManyToOne
private Program program;
#OneToMany(mappedBy="clazz", fetch=FetchType.LAZY, cascade=CascadeType.ALL)
#OrderBy("name ASC")
private Set<Method> methods = new TreeSet<Method>();
#ManyToMany(fetch=FetchType.LAZY, mappedBy="inheritedBy")
#OrderBy("name ASC")
private Set<Method> inheritedMethods = new TreeSet<Method>();
#PreRemove
private void tearDown() {
inheritedMethods.clear();
}
}
#Entity #Table(name="sf_method")
public class Method {
#ManyToOne(optional=true)
private Clazz clazz;
#ManyToMany(fetch=FetchType.LAZY)
#JoinTable(name = "sf_method_inherited_by_class",
joinColumns = { #JoinColumn(name = "sf_method_inherited_by_class") },
inverseJoinColumns = { #JoinColumn(name = "sf_class_inherits_method") }
)
private Set<Clazz> inheritedBy = new HashSet<Clazz>();
#PreRemove
public void tearDown() {
inheritedBy.clear();
}
}
My problem: what I do
What I need to do is to delete a program, including all its classes and methods. I use Spring-JPA repositories, so my code is:
#Autowired ProgramRepository programs;
#Transactional(readOnly=false)
public void forceDelete(Program p) {
Set<Clazz> myClasses = p.getClazzes();
for (Clazz c : myClasses) {
logger.info("Clazz " + c.getName());
for (Method m : c.getMethods()) {
m.setClazz(null);
methods.save(m);
}
}
programs.delete(p);
}
What happens
The delete code works only in some circumstances. Sometimes I get the following error.
SqlExceptionHelper - integrity constraint violation: foreign key no
action; FKIBMDD2FV8TNJAF4VJTOEICS73 table: SF_METHOD AbstractBatchImpl
- HHH000010: On release of batch it still contained JDBC statements
Any idea? I spent so many hours on this and I failed every single resolution attempt. What am I doing wrong?
Here you are trying to delete the parent without deleting the child which have foreign key referencing to the parent.
Here, before you delete p you have to delete children for p.
You can get that by :
p.getClazzes();
Again before deleting Clazz, you have to delete children for it(in this case, Method) as above...
I have two tables where there is a OneToMany, MnatToOne relation.
When I have added instance of AlarmnotifyEmailEntity into alarmnotifyEmailEntityList object and update instance of AlarmnotifyEmailConfEntity, value is save properly into Database.
Bu I could not do the same thing when deleting one of the item of alarmnotifyEmailEntityList.
I am sure that value is removed from alarmnotifyEmailEntityList but it does not reflect this changes into Database
#Entity(name ="alarmnotify_email_conf")
#Table(name = "alarmnotify_email_conf")
public class AlarmnotifyEmailConfEntity implements Serializable {
#OneToMany(mappedBy = "alarmnotifyEmailConfRef",cascade=CascadeType.ALL)
private List<AlarmnotifyEmailEntity> alarmnotifyEmailEntityList;
}//end of Class
#Entity (name ="alarmnotify_email")
#Table(name = "alarmnotify_email")
public class AlarmnotifyEmailEntity implements Serializable {
#JoinColumn(name = "alarmnotify_email_conf_ref", referencedColumnName = "id")
#ManyToOne
private AlarmnotifyEmailConfEntity alarmnotifyEmailConfRef;
}end of Class
I am only invoking following statement to update.
JPAManager.getJPAManagerInstance().update(alarmnotifyemailconf);
public Object update(Object o) {
try {
tx.begin();
EntityManager em = getEntityManager();
System.out.println("updating object:" + o);
o = em.merge(o);
em.close();
tx.commit();
System.out.println("closed and commited merge operation");
return o;
}
catch (Exception e) {
e.printStackTrace();
}
return o;
}
From my experience cascade only applied for same operation. If we save parent, then children also will saved same case with update. But I think when you want to remove one of children, we have to remove explicitly using entity manager, and cannot just merging parent and expect will cascade remove to children.
I have found out the answer in jpa removing child from collection.
as a result adding orphanRemoval=true solved the problem.
#Entity(name ="alarmnotify_email_conf")
#Table(name = "alarmnotify_email_conf")
public class AlarmnotifyEmailConfEntity implements Serializable {
#OneToMany(mappedBy =
"alarmnotifyEmailConfRef",cascade=CascadeType.ALL ,orphanRemoval=true)
private List alarmnotifyEmailEntityList;
}//end of Class