2

I have an entity like this:

@EntityListeners({MyEntityListener.class})
public class MyEntity {
    @OneToMany(mappedBy = "myentity", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<MyDependentEntity> dependents;

    //other attributes that are mapped to columns in MyEntity table
}

And the dependent entity:

public class MyDependentEntity {
    @ManyToOne(optional = false, cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
    @JoinColumn(name = "myentity_id")
    private MyEntity myEntity;
}

And MyEntityListener:

public class MyEntityListener {
    @PostPersist
    public void postPersist(MyEntity entity) {
        logStuff(entity);
    }
}

And this code that starts it:

MyDependentEntity dependent = new MyDependentEntity();
dependent.set...(...);
dependent.set...(...);

MyEntity entity = new MyEntity();
entity.set...(...);
entity.set...(...);
entity.set...(...);
entity.setDependents(Collections.singleton(dependent))
dependent.setEntity(entity);
entityManager.merge(entity);

It works and the entity listener is triggered. The problem is: the dependents attributes is null (all others are correctly populated).

The database has the correct data (entity and the corresponding dependents). Debugging I can see that the entity persisted has the dependent in the attribute. But as it enters in the entity listener the dependent attribute is null.

Is there any way that I can have the dependents attribute populated in the entity listener? I need it so I can process the Entity which was just saved.

JSBach
  • 4,679
  • 8
  • 51
  • 98

1 Answers1

2

I have tested this in Hibernate and, as you are persisting a new instance, you can fix this by either:

  1. calling entityManager.persist(entity) rather than entityManager.merge(entity) or,

  2. by adding the the @PostPersist call back to the entity itself rather than defining a separate listener class (https://en.wikibooks.org/wiki/Java_Persistence/Advanced_Topics#Example_of_Entity_event_annotations).

Why?

Firstly, note that the call entityManager.merge(entity) returns a new managed instance and it is this newly created instance that is passed to your listener - not the instance you passed to merge(). You can add some debugging to confirm this.

For differences between merge and persist see here:

JPA EntityManager: Why use persist() over merge()?

The JPA spec (3.2.7.1) notes that when calling merge with a new instance:

  • If X is a new entity instance, a new managed entity instance X' is created and the state of X is copied into the new managed entity instance X'.
  • For all entities Y referenced by relationships from X having the cascade element value cascade=MERGE or cascade=ALL, Y is merged recursively as Y'. For all such Y referenced by X, X' is set to reference Y'. (Note that if X is managed then X is the same object as X'.)

The JPA spec also notes:

The PostPersist and PostRemove methods will be invoked after the database insert and delete operations respectively. These database operations may occur directly after the persist, merge, or remove operations have been invoked or they may occur directly after a flush operation has occurred (which may be at the end of the transaction).

So the following sequence of events may explain what you see:

  1. call to merge results in a new managed instance being created with only basic properties merged at this point.
  2. database flush occurs for the parent entity.
  3. post persist listener executes and is passed this new managed instance.
  4. cascade operations are applied to new managed instance (only now are the associations set on this new instance).
  5. associated entities are flushed to the database.

Enabling Hibernate SQL logging supports the above: the call to the listener executes immediately after the insert statement for the parent entity and before the associated collection is flushed.

The following also seems to confirm that the listener is called at some point between merge of basic properties and merge of associated collections.

MyEntity e2 = entityManager.merge(entity);
--> listener called and size of associated collection is 0
System.out.println(e2.getDependents().size()); // outputs 1
Community
  • 1
  • 1
Alan Hay
  • 22,665
  • 4
  • 56
  • 110
  • wow, great answer! Thanks! I changed the code and it works, but there is a problem: I will add the entity listener to an already existing system which uses merge() to save the entity. Is there any way I can get the attribute or do I need to inject the entity manager and get the dependents manually? – JSBach Dec 17 '15 at 08:24
  • Never mind, I will change the existing code to use persist :) – JSBach Dec 17 '15 at 08:50
  • Or you could add the callback on the entity itself rather than using a separate listener? https://en.wikibooks.org/wiki/Java_Persistence/Advanced_Topics#Example_of_Entity_event_annotations – Alan Hay Dec 17 '15 at 08:59
  • That is a nice idea! I will discuss with my colleagues which one we would take.Thanks! – JSBach Dec 17 '15 at 10:54