hibernatejpaeclipselinkhibernate-cascade

Cascade behaviour for persist() when creating a new entity vs modifying an existing entity


I am trying to understand why the behaviour of persist() is different on a ManyToOne in the cases of first, persisting a new entity and second, modifying that entity. My test setup has Employee having a unidirectional ManyToOne with Department; there is no relationship from Department back to Employee. For the test I do not have any cascade annotations on the Department field in Employee.

What I have found is that when creating an Employee, I must call em.persist(dept) otherwise the dept instance is not persisted and I get an exception. So, I am calling em.persist(dept) so that the dept entity is persisted. My next test is to commit, and start a new transaction, retrieve the employee entity with em.find(), modify the dept.name, then persist the employee. What I am finding is that the change to the dept is persisted, despite there being no cascade annotations whatsoever on the Department field in Employee.

Why is this? Why does the change to the dept get persisted (via em.persist(emp)) to the db without any cascade on Department but the creation of the dept not get persisted when the employee is first persisted? What am I missing? BTW just to be clear, at the end of the test, the change to dept's name (FURTHER MATHS) is persisted. Thanks.

EDIT I have just read that "You may call this method (persist()) on an already persistent instance, and nothing happens" at https://www.baeldung.com/hibernate-save-persist-update-merge-saveorupdate . I think this means that in changeDept() my call to persist() after find() is redundant, so I removed it and the outcome is the same. So this makes me think in addition to many misunderstandings, one of them might be my understanding of persist(), and how it does or does not relate to propagating the change in state of an entity (and its related entities) to the db. But still, there are no cascadeType annotations on Department.

EDIT2 I think I am getting somewhere. I have added a new method, which creates a new Department ("ENGLISH"), retrieves the employee as before with find(), sets the deparment on the employee to the new department, and commits. I get (thankfully, as expected) an exception:

java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing

This exception does not occur if I put the PERSIST cascadeType on the Department field. So what is apparent is that persist applies to persisting of new entities; it does not apply to propagating changes to existing entities. The question remains, is it default behaviour (ie without any cascadeType specified) to propagate changes to related entities)? I suppose it must be.

@Entity
public class Employee {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY) 
    private int id;
    private String name;
    private double salary;
    @ManyToOne
    private Department department;

    ...
}

Deparment:

@Entity
public class Department {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY) 
    private int id;
    private String name;
    ..
}

methods:

static void setupData() {
        try { 
            em.getTransaction().begin();
            Department dept = new Department();
            dept.setName("MATHS");
             ...
            Employee emp2 = new Employee("Darth Vader", 10003, dept);
            em.persist(emp2);
            em.persist(dept); //needed without cascadeType=PERSIST on Department
            em.getTransaction().commit();
        } catch (Exception e) {
            logger.error("oops: " + e);
            e.printStackTrace();
            em.getTransaction().rollback();
        } 
    }

    static void changeDept() {
        try {
            em.clear();
            em.getTransaction().begin();            
            Employee emp1 = em.find(Employee.class, 2);
            logger.info("emp1:" + emp1);        
            Department dept = emp1.getDepartment();
            dept.setName("FURTHER MATHS");
            em.persist(emp1);
            em.getTransaction().commit();           
        } catch (Exception e) {
            logger.error("oops: " + e);
            e.printStackTrace();
            em.getTransaction().rollback();
        } 
    }

Solution

  • Cascading basically specifies the operation to be performed on an underlying entity if the action is taken on the current entity.

    In your case, since you do not specify any cascade type on your department object in Employee entity, by default it gets set to NONE(the default cascade type, ie no action will be taken on Department object when you will perform operation on Employee object). But since you specified the relationship between Employee and Department , if the department associated with the Employee is not already present in database then you will face IllegalStateException. That is why you need to separately persist the Department object.

    In the second case, when you mention the cascade type to PERSIST, then it will automatically persist the department along with the Employee Object.