合并@ManyToOne端的托管实体

时间:2015-06-22 09:51:04

标签: java hibernate jpa orm eclipselink

以下是从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.MERGE中的@ManyToOne关系被移除时,才会发生此事。

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

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

3 个答案:

答案 0 :(得分: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 :(得分:1)

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

例如,在您调用employee.setDepartment(department)之后; 员工(1L) - &gt;部门' - &gt;雇员(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");

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

答案 2 :(得分: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)

但有两种不同的情况:

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

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

    1. 不:一切都应该有效
    2. 是的,它是同一个实例:一切都应该有用
    3. 是的,但它是一个不同的实例:可能会导致更改覆盖
    4. 我认为&#34; bug&#34;延迟加载时可能在这里:在同一个EntityManager中不应该有两个相同实体的托管实例

      我无法想到一个反例。