0

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();
        } 
    }
codeLover
  • 2,571
  • 1
  • 11
  • 27
JL_SO
  • 1,742
  • 1
  • 25
  • 38
  • 1
    So your question boils down to: why is a change made to a department of an employee saved when loading the employee (and its department) from the database? Because that's how JPA is designed: loading entities gets you **managed** entities. Their state is automatically managed by JPA, so whatever change you make to a managed entity is saved to the database transparently. https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#pc-managed-state – JB Nizet Sep 12 '18 at 05:36
  • Yes thanks. For existing related entities, when the state of the related entity changes, that is saved along with the parent. But a related entity that does not already exist in an entity is not saved (by default ie with no cascading). It is one case amongst many. It is obvious I suppose when you know it, but it is a special case - it regards the propagation of state of a related entity and does not require any cascading to work. Similarly I have since found if you merge an existing entity that contains another new entity, that related entity is partially persisted w/o cascading. – JL_SO Sep 12 '18 at 05:48

1 Answers1

1

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.

codeLover
  • 2,571
  • 1
  • 11
  • 27