0

Unable to save child object reference.

Employee parent object contains the child employee_detail which also has a @ManyToOne defined to save Address object.

Table structure

EMPLOYEE
ID   BIGINT(20) NOT NULL AUTO_INCREMENT
NAME VARCHAR(100) NOT NULL

EMPLOYEE_DETAIL
ID              BIGINT(20) NOT NULL AUTO_INCREMENT
EMPLOYEE_ID     BIGINT(20) NOT NULL
ADDRESS_ID      BIGINT(20) NOT NULL

Entities

@Entity
@Table(name = "employee")
public class Employee {
 @Id
 @GeneratedValue(strategy = GenerationType.AUTO)
 @Column(name = "id")
 private Long id;

 @Column(name = "name")
 private String name;

 @OneToMany(mappedBy = "employee", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
 private List < EmployeeDetail > employeeDetails = new ArrayList < > ();

 public void addEmployeeDetail(EmployeeDetail ed) {
  employeeDetails.add(ed);
  ed.setEmployee(this);
 }

}

@Entity
@Table(name = "employee_detail")
public class EmployeeDetail {
 @Id
 @GeneratedValue(strategy = GenerationType.AUTO)
 @Column(name = "id")
 private Long id;

 @ManyToOne(optional = false, cascade = CascadeType.PERSIST)
 @JoinColumn(name = "employee_id")
 private Employee employee;

 @ManyToOne(optional = false, cascade = CascadeType.PERSIST)
 @JoinColumn(name = "address_id")
 private Address address;
}

In the REST Controller method:

public void saveEmployee(@RequestBody Employee employee)
{
    EmployeeDetail employeeDetail = new EmployeeDetail();
    employeeDetail.setEmployee(employee);

    for(EmployeeDetail ed : employee.getEmployeeDetails())
    {
        Address address = addressService.findOne(ed.getAddress().getId());
        employeeDetail.setAddress(address);
    }

    employee.addEmployeeDetail(employeeDetail);

    //SAVE
    employeeService.create(employee);
}

Exception:

nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.abc.Address

I am unable to save the Address details with the child table EmployeeDetail. What is wrong here?

  • you can't persist a detached instance, get a persistent instance of the `Address` before you update the database. – Roman C Jun 27 '16 at 15:05
  • Sorry I updated the Address retrieval code. But still the same problem. – FakirTrappedInCode Jun 27 '16 at 15:08
  • `cascade = CascadeType.PERSIST` in a `ManyToOne` means: When persisting `EmployeeDetail` also persist this field... if an Address is existing and trying to be persisted, an ´org.hibernate.PersistentObjectException´ is thrown... – XiCoN JFS Jun 27 '16 at 15:14
  • I removed the cascase annotation defined on Address field. Now got com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'employee_id' cannot be null – FakirTrappedInCode Jun 27 '16 at 15:27
  • You have to save the `Employee` first, then create the `EmployeeDetail` with the just persisted `Employee` – XiCoN JFS Jun 27 '16 at 15:36
  • If i think about it: why not just add the `Address` to the `Employee` itself? kinda wierd – XiCoN JFS Jun 27 '16 at 15:39

1 Answers1

1

Apply CascadeType.MERGE too for Address entity as below

 @ManyToOne(optional = false, cascade = {CascadeType.PERSIST,CascadeType.MERGE})
 @JoinColumn(name = "address_id")
 private Address address;

and use merge() instead of persist to save your changes

EDIT

Yes you will have to apply the CascadeType.MERGE for EmployeeDetail too .. otherwise you will get the same exception if you have a detached EmployeeDetail instance in you network of objects starting from Employee.

There are couple of scenarios which you will need to consider.As explained below.

When you call persist() on your root entity all the entity association mapped with CascadeType.PERSIST are also passed to the persist() (transitive peristence) . Now if any of your entity in the object graph is detached persist() will be called for that entity too and you will get an exception for that entity.

In order to save your complete graph of objects which includes both detached and transient instances , mark your association with CascadeType.MERGE and call merge() on your root entity .. Now merge() operation will be cascaded to all the entity association mapped with CascadeType.MERGE. The behaviour of merge is that it will merge any detached entity being passed to it to the existing entity in the persistence context or if the entity is transient it will make it persistent.

So while trying to save you entity graph you need to choose whether to use persist() or merge() based on the info whether the entity graph you are going to save has only transient instances or it has both transient and detached instances.You will also have to consider the cascade type set on each association.

For your code .. i see the Address instance you are setting on the EmployeeDetail instance is detached as you are getting it from another already fetched EmplyeeDetail instance.

You can get more detail on below link

JPA EntityManager: Why use persist() over merge()?

You can also extend your persistence context by creating conversations if you dont want to work with detached instances.More on this below

http://www.thoughts-on-java.org/unsychronized-persistencecontext-implement-conversations-jpa/

Community
  • 1
  • 1
Zulfi
  • 586
  • 4
  • 14
  • Please explain for further understanding of the problem. Also this should be applied for `Employee` in `EmployeeDetail` too. If u pass an existing `Employee` to `saveEmployee(@RequestBody Employee employee)` OP will get the same exception. – XiCoN JFS Jun 27 '16 at 15:30
  • @Zulfi adding @ManyToOne(optional = false, cascade = {CascadeType.PERSIST,CascadeType.MERGE}) to address still gave me the same detached entity passed to persist error – FakirTrappedInCode Jun 27 '16 at 16:49
  • @pondicherryFellow you have to use merge() instead of persist() – Zulfi Jun 27 '16 at 16:52
  • I'm using Spring Data JPA so i just use the save method. – FakirTrappedInCode Jun 27 '16 at 16:57
  • @Pondiccherry Fellow look at the link this might help http://stackoverflow.com/questions/16559407/spring-data-jpa-save-new-entity-referencing-existing-one – Zulfi Jun 27 '16 at 17:16
  • @Zulfi Now I made entityManager.merge() call which results in 'employee_id' cannot be null. Do you think the controller method logic is wrong or any missing annotation on entity. I added cascade = {CascadeType.PERSIST,CascadeType.MERGE} on Employee in EmployeeDetail – FakirTrappedInCode Jun 28 '16 at 11:04
  • @PondicherryFellow no your mapping and controller are correct seems to be an issue in Spring Data JPA.. can you post the code changes you have done to call merge() – Zulfi Jun 28 '16 at 11:35
  • @Zulfi Defined EntityManager entityManager on class and Transactional on method and called entityManager.merge(employee) to save – FakirTrappedInCode Jun 28 '16 at 14:08