15

I have a JPA annotated class which contains a collection like so:

@Entity
public class Employee {
    @Id
    private int id; 
    @Basic
    private String name;    
    @OneToMany
    @JoinTable(name = "ORG", joinColumns = @JoinColumn(name="MINION"),
        inverseJoinColumns = @JoinColumn(name="EMP"))
    private List<Employee> minions = new ArrayList<Employee>();

    @PreUpdate
    public void preUpdate(){ ... }
}

What I'm seeing is that if I have a managed Employee entity and I add to it's collection of minions the preUpdate method is not getting invoked. A new row is added to the mapping table in the DB so I know the update is going through. If I change a property directly on the Employee, like name, then preUpdate fires as expected when the transaction is committed.

Is there a way to get PreUpdate to fire when a mapped collection is modified? Or is there some other technique or Hibernate specific annotation for detecting when this happens?

Adam B
  • 1,724
  • 3
  • 14
  • 29

3 Answers3

7

@PreUpdate event is triggered just before database UPDATE operation is executed for the entity in question.

If you're not updating direct properties of Employee, there's no UPDATE to execute for its table and thus @PreUpdate listener is never called. You should have better luck using @PrePersist event which is triggered by "flush" rather than "update".

ChssPly76
  • 99,456
  • 24
  • 206
  • 195
  • 1
    You're correct. PrePersist doesn't help either since it is fired on INSERT and in this case the collection is modified after the entity has already been persisted. I guess my question is really asking if there are techniques that function similar to PreUpdate to detect when a mapped collection is modified. – Adam B Nov 23 '09 at 23:10
  • 1
    I'm not sure what you mean by "fired on INSERT". Aren't you calling `entityManager.persist(employee)` to save your collection? If you're not (e.g. you're relying on flush _only_) you're going to have to configure your own listener for one (or more) of "flush" / "auto-flush" / "flush-entity" events (http://docs.jboss.org/hibernate/stable/entitymanager/reference/en/html/configuration.html#d0e500). Or, for a Hibernate-specific solution, write your own interceptor (http://docs.jboss.org/hibernate/stable/core/reference/en/html/events.html#objectstate-interceptors) - onFlushDirty() is always called – ChssPly76 Nov 24 '09 at 00:12
2

Maybe a this custom workaround works:

Create a subclass of ArrayList which identifies changes through ActionListener pattern

public class Employee {
    ....

    private List<Employee> minions = createChangeNotifierList();

    private List<Employee> createChangeNotifierList() {
        ChangeNotifierList<Employee> l = new ChangeNotifierList<Employee>();
        l.setActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                preUpdate();
            }
        });
        return l;
    }

    public void setMinions(List<Employee> l) {
        if (!(l instanceof ChangeNotifierList)) {
            l = createChangeNotifierList();
            preUpdate();
        }
        this.minions = l;
    }

    public void preUpdate(){ ... }
}


public class ChangeNotifierList<T> extends ArrayList<T> {

    private ActionListener actionListener;

    public ChangeNotifierList() {
    }

    public ChangeNotifierList(List<T> list) {
        super.addAll(list);
    }

    public void setActionListener(ActionListener actionListener) {
        this.actionListener = actionListener;
    }

    public boolean add(T e) {
        boolean b = super.add(e);
        if (b) {
            notifyChange();
        }
        return b;
}

    private void notifyChange() {
        actionListener.actionPerformed(null);
    }

    .....
}
rabolfazl
  • 435
  • 1
  • 8
  • 24
0

Here is my implementation for Hibernate provider:

http://pastebin.com/8cPB96bZ

Generally you just mark methods that should be called in the case of a dirty collection with @PreCollectionChange annotation.

Mikhail Kadan
  • 556
  • 1
  • 5
  • 15
  • It's always a good idea to include the import statements, so in any case it's possible to determine which import is exactly needed. In this case, I have where you took the ReflectionUtils from. Nowdays, I think many third party libraries have a class caled `ReflectionUtils` included – alexander Apr 26 '21 at 05:29