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

2022-01-18 00:00:00 spring java 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>();

现在,如果我创建一个引用现有项目的新员工并尝试保留该员工,则会收到错误消息:

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
}

我像这样更新现有的:

public void updateEmployee(EmployeeDTO empDTO) {

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

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

推荐答案

我猜你是在与存储库交互而没有适当地扩展事务边界.默认情况下,事务(以及会话)边界位于存储库方法级别.这会导致 Project 实例与 EntityManager 分离,因此它不能包含在持久化操作中.

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

这种方法使 Project 实例保持为托管实体,因此为正确处理新的 Employee 实例执行持久操作.

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.

两个存储库交互的不同之处在于,在第二种情况下,您将拥有一个分离的实例(已被持久化,已设置一个 id),而在第一个示例中,您拥有一个完全非托管的实例,没有有一个 id 集.id 属性是导致存储库区分调用 persist(...)merge(...) 的原因.所以第一种方法会触发 persist(...),第二种方法会触发 merge(...).

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(…).

相关文章