JPA i audyt encji z wykorzystaniem @EntityListeners
W ostatnim poście opisywałem jak poradzić sobie z zapisywaniem danych audytowych encji przy wykorzystaniu JPA. W komentarzu pod postem padło pytanie ze strony Piotrka:
Ciekaw jestem jednak jakiegoś sposobu w czystym JPA, który pozwoli na wydelegowanie zbierania audytowych informacji do osobnego „listenera”.
Otóż postanowiłem zgłębić temat 🙂 i faktycznie jest zgrabniejszy sposób w jaki można wykonać to zadanie. Należy wykorzystać adnotację @EntityListeners. Na początek stwórzmy klasę listenera:
public class AuditListener { @PrePersist void onPrePersist(Object o) { if (o instanceof Auditable) { ((Auditable<User>)o).setCreationDate(new Date()); ((Auditable<User>)o).setCreationUser(getCurrentUser()); onPreUpdate(o); } } @PreUpdate void onPreUpdate(Object o) { if (o instanceof Auditable) { ((Auditable<User>)o).setModificationDate(new Date()); ((Auditable<User>)o).setModificationUser(getCurrentUser()); } } public User getCurrentUser() { return Application.getCurrentUser(); } }
Nie musi ona implementować żadnego interfejsu, posługujemy się podobnie jak w poprzednim przykładzie adnotacjami @PrePersist oraz @PreUpdate. Tak oznaczone metody wykonają się odpowiednio przez zapisem i aktualizacją encji. Ważne jest aby posiadały odpowiednią sygnaturę, to znaczy muszą zwracać void oraz przyjmować jeden argument (może to być typ bardziej specyficzny niż Object). W implementacji tych metod wiele się nie zmieniło, korzystam tutaj ze znanego wcześniej interfejsu Auditable.
Teraz możemy zmodyfikować klasę naszej encji:
@Entity @Table(name = "customers") @EntityListeners(AuditListener.class) public class Customer implements Auditable
Jak widać powyżej nie musimy już dziedziczyć po klasie AuditEntity (coś można pokusić się o pozostawienie tam pól audytowych wraz z mapowaniem oraz getterami i setterami), ważne aby implementować interfejs Auditable. Pokazany wcześniej listener AuditListener dodajemy do encji poprzez adnotację @EntityListeners. W ten sposób można zarejestrować kilka listenerów nasłuchujących na operacje bazodanowe wykonywane na naszej encji.
Istnieje możliwość pozbycia się takiego listenera dla encji dziedziczącej z encji, dla której został dołączony już konkretny listener. Służy do tego adnotacja @ExcludeSuperclassListeners.
@Entity @ExcludeSuperclassListeners public class SpecificEntity extends BaseEntity { }
O! Właśnie o to chodziło. W Hibernate Interceptory można ustawiać też per SessionFactory, w JPA pewnie też jakoś się da. Wtedy wyeliminujemy nawet adnotację z listenerem. Ale i tak jest lepiej 🙂