Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to save a new entity that refers existing entity in Spring JPA?

Imagine the following models:

Employee:

@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "employee_project", joinColumns = @JoinColumn(name = "Emp_Id"), inverseJoinColumns = @JoinColumn(name = "Proj_id"))
private Set<Project> projects = new HashSet<Project>();

Project:

@ManyToMany(mappedBy = "projects")
private Set<Employee> employees = new HashSet<Employee>();

Now if I create a new employee that refers to an existing project and try to persist that employee, I get an error:

detached entity passed to persist: Project

I create the employee as follows:

public void createNewEmployee(EmployeeDTO empDTO) {

  Employee emp = new Employee();
  // add stuff from DTO, including projects

  repository.saveAndFlush(emp);  // FAILS
}

and I update existing ones like this:

public void updateEmployee(EmployeeDTO empDTO) {

   Employee emp = repository.findOne(empDTO.getId());
   // set stuff from DTO, including projects

   repository.saveAndFlush(emp);  // WORKS!
}
like image 704
wannabeartist Avatar asked Apr 11 '13 08:04

wannabeartist


1 Answers

I guess you're interacting with the repository without expanding the transaction boundaries appropriately. By default, the transaction (and thus session) boundary is at the repository method level. This causes the Project instance to be detached from the EntityManager, so that it cannot be included in a persist operation.

The solution here is to extend the transaction boundary to the client:

@Component
class YourRepositoryClient {

  private final ProjectRepository projects;
  private final EmployeeRepository employees;

  // … constructor for autowiring

  @Transactional
  public void doSomething() {
    Project project = projects.findOne(1L);
    Employee employee = employees.save(new Employee(project));
  }
}

This approach causes the Project instance stay a managed entity and thus the persist operation to be executed for the fresh Employee instance being handled correctly.

The difference with the two repository interactions is that in the second case you'll have a detached instance (has already been persisted, has an id set), where as in the first example you have a completely unmanaged instances that does not have an id set. The id property is what causes the repository to differentiate between calling persist(…) and merge(…). So the first approach will cause a persist(…) to be triggered, the second will cause a merge(…).

like image 183
Oliver Drotbohm Avatar answered Oct 05 '22 11:10

Oliver Drotbohm