在@ManyToOne 端合并托管实体

Merging a managed entity on the @ManyToOne side

下面给出了从 DepartmentEmployee 的 one-to-many 关系。

部门(parent):

@OneToMany(mappedBy = "department", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Employee> employeeList = new ArrayList<Employee>(0);

员工(child):

@JoinColumn(name = "department_id", referencedColumnName = "department_id")
@ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH})
private Department department;

合并托管实体 (child),如下所示(在 EJB 中使用 CMT),

Employee employee = entityManager.find(Employee.class, 1L);
employee.setDepartment(department); // department is supplied by a client.
employee.setEmployeeName("xyz");
entityManager.merge(employee);

不更新数据库中相应的员工行table。只有当 CascadeType.MERGE 从 child @ManyToOne 关系中删除 Employee.

时才会发生

为什么 table 中的行没有更新?关于此示例,CascadeType.MERGE 的唯一目的是什么?

我目前正在使用带有 JPA 2.1 的 EclipseLink 2.6.0。

级联应该总是 propagate from a Parent to a Child 而不是相反。

在您的情况下,您需要从子端移除级联:

@JoinColumn(name = "department_id", referencedColumnName = "department_id")
@ManyToOne(fetch = FetchType.LAZY)
private Department department;

并确保设置关联的双方:

Employee employee = entityManager.find(Employee.class, 1L);
employee.setDepartment(department);
employee.setEmployeeName("xyz");
department.getEmployeeList().add(employee);
entityManager.merge(department);

此代码存在风险,因为您关联的是一个分离的部门实例,该实例可能具有一个分离的员工集合。如果具有新 xyz 名称的当前员工在该列表中,则其更改将被分离实例的名称覆盖。

例如,在您致电employee.setDepartment(部门)之后; 员工(1L) -> 部门' -> 员工(1L)'

调用 employee(1L) 实例上的合并不会执行任何操作,因为名称更改已经可见,但它会级联到部门。合并部门然后级联到具有旧名称的 employee(1L)' 实例。如果您检查 employee.getEmployeeName() 的值,您会看到合并导致它重置,这可能是您看不到数据库更新的原因。

虽然不调用合并不是一个选项,因为您仍然有员工引用一个分离的部门,这应该是一个例外情况。这就是提供者发出插入的原因。

推测您在关系上设置了级联合并,因为客户端提供的部门对象也可能包含员工变更。如果是这样,要同步这些更改并更改 employeeName,您将使用:

Department managedDepartment = entityManager.merge(department);
Employee employee = entityManager.find(Employee.class, 1L);
employee.setDepartment(managedDepartment);
managedDepartment.getEmployeeList().add(employee);
employee.setEmployeeName("xyz");

如果部门不存在,这两者都可以将员工添加到部门,如果存在,仍然可以更改名称。

我也阅读了其他答案和 bugzilla 问题,但我仍然相信这种行为是一个错误,或者至少是一个缺失的功能。

请跟随我的思路:

Employee employee = entityManager.find(Employee.class, 1L);

员工是被管理实体;

employee.setDepartment(department); // department is supplied by a client.

部门是一个独立的实体;

entityManager.merge(employee);

由于员工已经被管理,合并员工只会触发部门的合并级联。
所以:

merge(department)

现在部门也管理了;这将触发员工级联:

merge(employee1)
merge(employee2)
...
merge(employeeN)

但有两种不同的情况:

  1. department.getEmployeeList 已获取
    它包含分离的实体:合并将附加 employee# 并且 不应 在部门上触发级联,因为它已经被调用(否则会产生无限循环)。
    请注意,雇员 未包含在雇员列表中
    在这种情况下,一切都必须正常工作。
  2. department.getEmployeeList 尚未获取并且延迟加载现在
    它包含托管实体:merge 应该什么也不做,因为 employees# 已经被管理并且 department 上的 cascade 已经被调用。
    但是..

在这种情况下,employeeList中是否包含employee?

三种可能性(取决于刷新模式、自动提交等其他变量...):

  1. 否:一切正常
  2. 是的,而且是同一个实例:一切正常
  3. 是的,但这是一个不同的实例:可能导致更改覆盖

我认为 "bug" 可能在这里,当延迟加载时:在同一个 EntityManager 中不应该有同一个实体的两个托管实例。

我想不出一个反例。