可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Given below a one-to-many relationship from Department
to Employee
.
Department (parent) :
@OneToMany(mappedBy = "department", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Employee> employeeList = new ArrayList<Employee>(0);
Employee (child) :
@JoinColumn(name = "department_id", referencedColumnName = "department_id")
@ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH})
private Department department;
Merging a managed entity (child) like the following (using CMT in EJB),
Employee employee = entityManager.find(Employee.class, 1L);
employee.setDepartment(department); // department is supplied by a client.
employee.setEmployeeName("xyz");
entityManager.merge(employee);
does not update the corresponding employee row in the database table. It happens only when CascadeType.MERGE
is removed from the child @ManyToOne
relationship in Employee
.
Why doesn't the row in the table get updated? What is the sole purpose of CascadeType.MERGE
regarding this example?
I am currently using EclipseLink 2.6.0 having JPA 2.1.
回答1:
Cascading should always propagate from a Parent to a Child and not the other way around.
In your case, you need to remove the Cascade from the child-side:
@JoinColumn(name = "department_id", referencedColumnName = "department_id")
@ManyToOne(fetch = FetchType.LAZY)
private Department department;
and make sure you set both sides of the association:
Employee employee = entityManager.find(Employee.class, 1L);
employee.setDepartment(department);
employee.setEmployeeName("xyz");
department.getEmployeeList().add(employee);
entityManager.merge(department);
回答2:
I read other answers and the bugzilla issue as well, but I'm still convinced that this behavior is a bug, or at least a missing feature.
Please follow my thinking:
Employee employee = entityManager.find(Employee.class, 1L);
employee is a managed entity;
employee.setDepartment(department); // department is supplied by a client.
department is a detached entity;
entityManager.merge(employee);
since employee is already managed, merging employee will only trigger merge cascade on department.
So:
merge(department)
Now depatment is managed too; and this will trigger cascade on employees:
merge(employee1)
merge(employee2)
...
merge(employeeN)
but there are two different scenarios:
- department.getEmployeeList is already fetched
it contains detached entities: merge will attach employee# and should not trigger cascade on department, since it has been already called (otherwise generating an infinite cycle).
Note that employee is not contained in employeeList.
In this case everything has to work.
- department.getEmployeeList is not yet fetched and is lazy loaded now
it contains managed entities: merge should do nothing since employees# are already managed and cascade on department has been already called.
But..
In this case, is employee contained in employeeList?
Three possibilities (depending on other variables like flush-mode, auto-commit, ...):
- no: everything should work
- yes, and it's the same instance: everything should work
- yes, but it's a different instance: probably leads to changes overwrite
I think the "bug" could be here, when lazy loading: it should not be possible to have two managed instances of the same entity in the same EntityManager.
I can't think a single counterexample.
回答3:
This code is risky, as you are associating a detached department instance, which then presumably has a detached collection of employees. If your current employee with the new xyz name is in that list, then its changes will get overridden by the detached instance's name.
for example, after you call employee.setDepartment(department);
employee(1L) -> department' -> employee(1L)'
Calling merge on the employee(1L) instance will do nothing as the name change is already visible, but it will casacade to the department. Merging department then cascades to the employee(1L)' instance which has the old name. If you checked the value of the employee.getEmployeeName(), you would see that merge caused it to reset, which is likely why you do not see a database update.
Not calling merge though is not an option, because you still have employee referencing a detached department, which is supposed to be an exception case. This is why the provider issues inserts.
Presumably you have cascade merge set on the relationships because the department object supplied by the client could contain employee changes as well. If so, to synchronize these changes and change the employeeName, you would use:
Department managedDepartment = entityManager.merge(department);
Employee employee = entityManager.find(Employee.class, 1L);
employee.setDepartment(managedDepartment);
managedDepartment.getEmployeeList().add(employee);
employee.setEmployeeName("xyz");
This both can add the employee to the department if it isn't there, and still make the name change if it is.