1

Inverse side (Department) :

@OneToMany(mappedBy = "department", fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH})
private List<Employee> employeeList = new ArrayList<Employee>(0);

Owning side (Employee) :

@JoinColumn(name = "department_id", referencedColumnName = "department_id")
@ManyToOne(fetch = FetchType.LAZY)
private Department department;

The method merging an Employee entity supplied by a client having a null Department in it :

public Employee update(Employee employee) {
    Department department = employee.getDepartment();

    if (department == null) {
        Employee managedEmployee = entityManager.find(Employee.class, employee.getEmployeeId());
        // Obtain the original Employee entity which may still have its Department intact.

        if (managedEmployee == null) {
            throw new EntityNotFoundException();
        }

        Department managedDepartment = managedEmployee.getDepartment();

        if (managedDepartment != null) {
            managedEmployee.getDepartment().getEmployeeList().remove(managedEmployee);
            // Removing an Employee entity from the list on the inverse side,
            // since it will no longer be pointing to Employee after Employee is merged.
        }

        return entityManager.merge(employee);
    }
}

The supplied Employee is a detached entity. Suppose that Department is optional for Employees and hence, there is a null foreign key (Thus, ON DELETE SET NULL is specified in the back-end system).

Is it necessary to explicitly remove an Employee entity as shown in the update() method above from the employee list on the inverse side of the relationship (Department), when the supplied Employee does not contain a Department (because it has already been set to null by the client while editing the Employee entity) before merging the supplied Employee entity?

I think, the provider will keep, the Employee reference in the list of employees on the inverse side of the relationship, on dangling otherwise.

Tiny
  • 27,221
  • 105
  • 339
  • 599
  • There was a [previous question](http://stackoverflow.com/q/31769284/1391249) with the same contents but I accidentally forgot to remove `cascade = CascadeType.ALL` and `orphanRemoval = true` on the inverse side of the relationship changing the whole definition of the question. – Tiny Aug 02 '15 at 20:30
  • How are you dealing with the underlying transaction of the `update` method? Are you using extended persistence context propagation? Do you retrieve and/or modify the entities' instances that matter before and/or after invoking the `update` method? How? – Guillermo Aug 04 '15 at 04:24
  • It is a transactional scoped `EntityManager` in EJBs (which defaults to `@PersistenceContext(type = PersistenceContextType.TRANSACTION)`). `@PersistenceContext(type = PersistenceContextType.EXTENDED)` does not apply to stateless session beans. Holding a state using verbose stateful session beans for this kind of CRUD operations is absolutely superfluous. EJBs are consumed by JSF where as and when required, entities are modified as per business requirements and resubmitted to an appropriate EJB for them to be propagated to the underlying database. I am not sure how it is related. – Tiny Aug 04 '15 at 04:38
  • So you are using *Container Managed Transaction* (means you never explicit begin or commit a transaction) and the `update` method is directly invoked (through the injected EJB) only by JSF backing beans logic, right? – Guillermo Aug 04 '15 at 04:59
  • Yes absolutely as said. But where is it related as to how associations are maintained - bidirectional/unidirectional? – Tiny Aug 04 '15 at 05:05
  • I ask for more details to resolve the situation depicted in the question, you do not generalize. Well, based on this, you can check the specs and test the code but *my answer apply as it is*. You should ask another question for a "general" case if you have doubts; I think the possibles answers could derived in something like my last comment in the answer's thread. – Guillermo Aug 04 '15 at 05:57
  • The question title itself is sufficient for passionate experts I guess. I am not sure what details I may append to the question body. – Tiny Aug 04 '15 at 06:15

2 Answers2

1

Because the owner side of the association is on Employee entity, it isn't necessary to remove the employee from the employees Department's collection.

You could remove that part of the code.

Also be aware that merge method will throws an IllegalArgumentException if employee is a removed entity instance. So maybe you can't simply remove the find(employee) part from your code, or perhaps you could catch the exception re-throwing the EntityNotFoundException but all this depends on your code logic.

Adding oficial source

Quote from JPA 2 specification (JSR-338) section Synchronizing to database:

Bidirectional relationships between managed entities will be persisted based on references held by the owning side of the relationship. It is the developer’s responsibility to keep the in-memory references held on the owning side and those held on the inverse side consistent with each other when they change. In the case of unidirectional one-to-one and one-to-many relationships, it is the developer’s responsibility to insure that the semantics of the relationships are adhered to. It is particularly important to ensure that changes to the inverse side of a relationship result in appropriate updates on the owning side, so as to ensure the changes are not lost when they are synchronized to the database.

Guillermo
  • 1,523
  • 9
  • 19
  • Why is it not required while JPA requires to explicitly remove an entity from its inverse collection before removing the entity (i.e. `entityManager.remove(entity)`)? – Tiny Aug 03 '15 at 05:15
  • For those piece of code isn't necessary because the association is represented by a field (`department_id`) on the Employee's table. After committed the changes (which set some `department_id` to null) to retrieve the Department's employees collection the provider will find Employee tuples that have the specific Department's id – Guillermo Aug 03 '15 at 05:49
  • If the inverse collection is lazily loaded, then the `Employee` entity whose `Department` is set to null while merging the `Employee` entity will not visible, when that collection of employees is fetched from the inverse side of the relationship because it has to be fetched afresh from database. The same holds true while removing/deleting an entity - the deleted entity will be invisible in its lazily loaded inverse collection, when that collection is attempted to be fetched. To be continued. – Tiny Aug 03 '15 at 07:13
  • The JPA provider nevertheless, requires explicitly removing en entity from its inverse collection while deleting the entity using `entityManager.remove(entity)`. To complete the contract to remove an entity from the inverse collection while merging is a mystery to me :). – Tiny Aug 03 '15 at 07:13
  • Following what I said. On the same persistent context that update method get access, if you already fetch the department and initialize its collection before invoke the update method, you will see the Employee, even you set to null its department, after method invocation (then you must explicit remove it from the collection to keep object model consistent). Only you know which is the situation and what behaivor applies. If you want an accurated answer give more details :) – Guillermo Aug 03 '15 at 08:16
  • There is a [relevant question](http://stackoverflow.com/q/30996141/1391249). Looking at it can you prove that it is really not required? Or else what kind of "*more details*" do you expect me to add to the current question while the current example can affect all types of associations (`@OneToOne`, `@OneToMany`, `@ManyToOne`, `@ManyToMany`)? I can only accept a conclusive/canonical answer/s possibly from official (true) sources because it is going to affect the whole big application I have been trying for a couple of years. – Tiny Aug 03 '15 at 20:10
  • The link in your comment mainly refers to the use of orphan removal option and the behavior for a specific JPA provider, it is not the case we discuss here. I will add a quote from official true source, you don't get distracted from our issue! :) – Guillermo Aug 03 '15 at 21:29
  • Special thanks for support. To be honest, I came across that quote several times and I found that it expects developers to keep references consistent on both the side. The merge operation does not appear to be a special case. Does it mention something somewhere which excludes the need of removing an entity from an inverse collection in the situation depicted in the question? – Tiny Aug 04 '15 at 02:40
  • I did it! jeje... Well, nop for this case but there is nothing more generic and official that the specs. The specification indicates the needs to be satisfied by the provider and, from our point of view as API clients, tells us what to expect. We/you must be aware about all points that apply on the situation and find out the answer by ourselves. Did you try my solution? Anyway I will ask you more details to refine it – Guillermo Aug 04 '15 at 04:08
1

The application is required to maintain both sides of bidirectional relationships for caching. If you don't maintain the collection, it will be out of sync with the database until it is reloaded.

It 'might' not be necessary, but this would be a function of your provider and caching. If the Department is cached (in the current EntityManager or in a second level cache), and the list was fetched, then it will not know about the change. In which case, you then would have to force a reload of the entity using a refresh option or cache invalidation option. It is usually much easier to maintain the list directly. If you are worried about performance, you can verify if the list was already loaded before calling remove, but this isn't always needed (EclipseLink's change tracking can allow modification without loading the collection).

"ON DELETE SET NULL" is outside of JPA, and I don't know of any way to tell the provider that this is set on a relationship. There isn't much point in setting the Employee.department relationship to null and merging, as JPA will then try to update the foreign key to null which is unnecessary. I would not recommend it be used, but if it must, refresh/invalidate the Employee in the cache instead of updating the relationship to null.

Chris
  • 20,138
  • 2
  • 29
  • 43
  • `Employee.department` will be set to `null` by the client itself while updating an `Employee` (since `Department` is optional for an `Employee` (odd enough though), the client may choose/decide to remove a `Department` from a particular `Employee`). That `Employee` entity with a `null` `Department` is resubmitted by the client which has to be merged by JPA (with a `null` `Department` in it). Here, "*There isn't much point in setting the `Employee.department` relationship to null and merging, as JPA will then try to update the foreign key to null which is unnecessary.*" – Tiny Aug 04 '15 at 14:51
  • The merge comment was only applicable to the "ON DELETE SET NULL", which only comes into play when deleting a Department. The client setting the employee.department to null but keeping the department alive is a special case that requires merging both changes to JPA. Its a pain as you cannot rely on cascade merge settings, since the references don't exist anymore – Chris Aug 05 '15 at 14:23
  • So, all in all, it is required and the entity has to be removed manually from the inverse collection in the given situation? – Tiny Aug 05 '15 at 17:57