合并@ManyToOne端的托管实体

下面给出了从DepartmentEmployee的一对多关系。

部门(家长):

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

员工(孩子):

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

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

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

不更新数据库表中相应的员工行。 仅当从Employee的子@ManyToOne关系中删除CascadeType.MERGE时才会发生这种情况。

为什么表中的行不会更新? CascadeType.MERGE关于这个例子的唯一目的是什么?

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

级联应始终从父级传播到子级,而不是相反。

在您的情况下,您需要从子端删除Cascade:

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

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

请按照我的想法:

 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已被提取
    它包含分离的实体:merge将附加employee#并且不应该触发部门级联,因为它已被调用(否则生成无限循环)。
    请注意,employeeList 中不包含 employee。
    在这种情况下,一切都必须工作。
  2. 尚未获取 department.getEmployeeList,现在延迟加载
    它包含托管实体:合并应该什么也不做,因为员工#已经被管理,并且已经调用了部门级联。
    但..

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

三种可能性(取决于其他变量,如flush-mode,auto-commit,……):

  1. 不:一切都应该有效
  2. 是的,它是同一个实例:一切都应该有效
  3. 是的,但它是一个不同的实例:可能会导致更改覆盖

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

我想不出一个反例。

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

例如,在您调用employee.setDepartment(department)之后; 员工(1L) – >部门’ – >员工(1L)’

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

不调用merge虽然不是一个选项,因为你仍然有员工引用一个独立的部门,这应该是一个例外情况。 这就是提供者发布插入的原因。

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

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

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