如何在Spring JPA中保存引用现有实体的新实体?
想象一下以下型号:
雇员:
@ManyToMany(cascade = CascadeType.ALL) @JoinTable(name = "employee_project", joinColumns = @JoinColumn(name = "Emp_Id"), inverseJoinColumns = @JoinColumn(name = "Proj_id")) private Set projects = new HashSet();
项目:
@ManyToMany(mappedBy = "projects") private Set employees = new HashSet();
现在,如果我创建一个引用现有项目的新员工并尝试坚持该员工,我会收到一个错误:
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! }
我猜你正在与存储库进行交互而不适当地扩展事务边界。 默认情况下,事务(以及会话)边界位于存储库方法级别。 这会导致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(…)
。