想象一下以下模型:
员工:
@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!
}
答案 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(…)
。