如何在Spring JPA中保存引用现有实体的新实体?

时间:2013-04-11 08:14:34

标签: java spring jpa spring-data spring-data-jpa

想象一下以下模型:

员工:

@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>();

项目:

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

现在,如果我创建一个引用现有项目并试图坚持该员工的新员工,我会收到错误:

detached entity passed to persist: Project

我按如下方式创建员工:

public void createNewEmployee(EmployeeDTO empDTO) {

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

  repository.saveAndFlush(emp);  // FAILS
}

我会像这样更新现有的:

public void updateEmployee(EmployeeDTO empDTO) {

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

   repository.saveAndFlush(emp);  // WORKS!
}

1 个答案:

答案 0 :(得分:20)

我猜你在没有适当扩展事务边界的情况下与存储库进行交互。默认情况下,事务(以及会话)边界位于存储库方法级别。这会导致Project实例与EntityManager分离,因此无法将其包含在持久操作中。

此处的解决方案是将事务边界扩展到客户端:

@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));
  }
}

此方法会导致Project实例保持为托管实体,从而为正在处理的新Employee实例执行持久操作。

与两个存储库交互的区别在于,在第二种情况下,您将拥有一个分离的实例(已经被持久化,具有一个id集),而在第一个示例中,您有一个完全不受管理的实例,而不是有一个id集。 id属性是导致存储库区分调用persist(…)merge(…)的原因。因此,第一种方法会导致persist(…)被触发,第二种方法会导致merge(…)