首页 文章

合并@ManyToOne端的托管实体

提问于
浏览
5

鉴于以下从 DepartmentEmployee 的一对多关系 .

部门(母公司):

@OneToMany(mappedBy = "department", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Employee> employeeList = new ArrayList<Employee>(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);

不更新数据库表中相应的员工行 . 仅当 CascadeType.MERGEEmployee 中的子 @ManyToOne 关系中删除时才会发生 .

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

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

3 回答

  • 2

    级联应始终propagate from a Parent to a Child而不是相反 .

    在您的情况下,您需要从子端删除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);
    
  • 1

    我也阅读了其他答案和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是 already fetched
      它包含分离的实体:merge将在部门上附加员工#和 should not 触发级联,因为它已被调用(否则生成无限循环) .
      请注意employeeList中的员工 is not contained .
      在这种情况下,一切都必须工作 .

    • department.getEmployeeList是 not yet fetched 并且是延迟加载的 now
      它包含托管实体:合并应该什么也不做,因为员工#已经被管理,并且已经调用了部门级联 .
      但..

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

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

    • 不:一切都应该有效

    • 是的,它是同一个实例:一切都应该有效

    • 是的,但它是一个不同的实例:可能导致更改覆盖

    我认为"bug"可能在这里,当延迟加载时: it should not be possible to have two managed instances of the same entity in the same EntityManager .

    我想不出一个反例 .

  • 1

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

    例如,在你调用employee.setDepartment(department)之后;员工(1L) - >部门' - >员工(1L)'

    在employee(1L)实例上调用合并将不会执行任何操作,因为名称更改已经可见,但它会传送到部门 . 合并部门然后级联到具有旧名称的雇员(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");
    

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

相关问题