在@ManyToOne 端合并托管实体
Merging a managed entity on the @ManyToOne side
下面给出了从 Department
到 Employee
的 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)
但有两种不同的情况:
- department.getEmployeeList 已获取
它包含分离的实体:合并将附加 employee# 并且 不应 在部门上触发级联,因为它已经被调用(否则会产生无限循环)。
请注意,雇员 未包含在雇员列表中。
在这种情况下,一切都必须正常工作。
- department.getEmployeeList 尚未获取并且延迟加载现在
它包含托管实体:merge 应该什么也不做,因为 employees# 已经被管理并且 department 上的 cascade 已经被调用。
但是..
在这种情况下,employeeList中是否包含employee?
三种可能性(取决于刷新模式、自动提交等其他变量...):
- 否:一切正常
- 是的,而且是同一个实例:一切正常
- 是的,但这是一个不同的实例:可能导致更改覆盖
我认为 "bug" 可能在这里,当延迟加载时:在同一个 EntityManager 中不应该有同一个实体的两个托管实例。
我想不出一个反例。
下面给出了从 Department
到 Employee
的 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)
但有两种不同的情况:
- department.getEmployeeList 已获取
它包含分离的实体:合并将附加 employee# 并且 不应 在部门上触发级联,因为它已经被调用(否则会产生无限循环)。
请注意,雇员 未包含在雇员列表中。
在这种情况下,一切都必须正常工作。 - department.getEmployeeList 尚未获取并且延迟加载现在
它包含托管实体:merge 应该什么也不做,因为 employees# 已经被管理并且 department 上的 cascade 已经被调用。
但是..
在这种情况下,employeeList中是否包含employee?
三种可能性(取决于刷新模式、自动提交等其他变量...):
- 否:一切正常
- 是的,而且是同一个实例:一切正常
- 是的,但这是一个不同的实例:可能导致更改覆盖
我认为 "bug" 可能在这里,当延迟加载时:在同一个 EntityManager 中不应该有同一个实体的两个托管实例。
我想不出一个反例。