Как-то обделена на хабре такая тема, как обработка событий при работе с сущностями с использованием Hibernate — я смог найти только один пост уже почти мохнатого года. Но то аудит, а нам нужна возможность автоматизировать работу с некоторыми атрибутами сущностей и при этом упростить процедуру работы с ними.
Для начала создадим демонстрационный стенд с двумя сущностями User и AnObject, а так же DAO-слоем для них.
Добавим в сущность AnObject атрибут с двумя свойствами — дата последнего редактирования и автор правки:
И не забудем поправить тестовый класс с учётом нововведений.
С этого момента у нас уже есть всё необходимое для внесения данных о дате/авторе изменений в ручном режиме, но… люди мы «ленивые», поэтому давайте автоматизируем и эту работу — пусть за нас везде это делает Hibernate. Для этого добавляем Listener и просим Hibernate его использовать при возникновении события save или update (commit):
Теперь остался заключительный штрих — поправить тесты так, чтобы они учитывали новые изменения (хотя правильнее было бы сперва тесты поправить, а потом уже добавлять Listener)
На этом можно поставить финальную точку — у любой сущности, которая имплементирует интерфейс LastModifiable автоматически при каждом сохранении в БД будут изменяться поля lastUpdated и lastEditor.
UPD: В примере была небольшая недосказанность — прослушивание было только для события saveOrUpdate, в то время, как могут быть вызваны и просто save и просто update. обновил тесты
UPD: Обновил исходники — добавил пример с использованием Spring Data JPA (использовал только сущности, без слушателей). Слушателя
Для начала создадим демонстрационный стенд с двумя сущностями User и AnObject, а так же DAO-слоем для них.
Код
Здесь и далее привожу только значимые куски кода — в полной версии можно посмотреть на github
@Entity @Table(name = "user") public class User { @Id @GeneratedValue private long id; @Basic @Column(name = "username", updatable = false, unique = true, nullable = false) private String username; // getter and setter }
@Entity @Table(name = "anObject") public class AnObject { @Id @GeneratedValue private long id; @Column private String value; // getter and setter }
Добавим в сущность AnObject атрибут с двумя свойствами — дата последнего редактирования и автор правки:
Код
@Embeddable public class LastModified { @Column @Temporal(TemporalType.TIMESTAMP) private Calendar lastUpdated; @OneToOne @JoinColumn(name = "lastEditor_id") private User lastEditor; // getter and setter }
public interface LastModifiable { LastModified getLastModified(); void setLastModified(LastModified modified); }
@Entity @Table(name = "anObject") public class AnObject implements LastModifiable { @Id @GeneratedValue private long id; @Column private String value; @Embedded private LastModified lastModified; // getter and setter }
И не забудем поправить тестовый класс с учётом нововведений.
С этого момента у нас уже есть всё необходимое для внесения данных о дате/авторе изменений в ручном режиме, но… люди мы «ленивые», поэтому давайте автоматизируем и эту работу — пусть за нас везде это делает Hibernate. Для этого добавляем Listener и просим Hibernate его использовать при возникновении события save или update (commit):
Код
@Component public class LastModifiedListener extends DefaultSaveOrUpdateEventListener { private transient static final Logger LOG = LoggerFactory.getLogger(LastModifiedListener.class.getName()); @Autowired private UserDao userDao; @Override public void onSaveOrUpdate(SaveOrUpdateEvent event) { LOG.trace("object: {}", event.getObject()); if (event.getObject() instanceof LastModifiable) { LastModified lastModified = new LastModified((User) userDao.get(2)); ((LastModifiable) event.getObject()).setLastModified(lastModified); LOG.trace("object: {}", event.getObject()); } super.onSaveOrUpdate(event); } }
@Component public class HibernateEventWiring { @Autowired private SessionFactory sessionFactory; @Autowired private LastModifiedListener lastModifiedListener; @PostConstruct public void registerListeners() { EventListenerRegistry registry = ((SessionFactoryImpl) sessionFactory).getServiceRegistry().getService( EventListenerRegistry.class); registry.getEventListenerGroup(EventType.SAVE_UPDATE).prependListener(lastModifiedListener); } }
Теперь остался заключительный штрих — поправить тесты так, чтобы они учитывали новые изменения (хотя правильнее было бы сперва тесты поправить, а потом уже добавлять Listener)
На этом можно поставить финальную точку — у любой сущности, которая имплементирует интерфейс LastModifiable автоматически при каждом сохранении в БД будут изменяться поля lastUpdated и lastEditor.
UPD: В примере была небольшая недосказанность — прослушивание было только для события saveOrUpdate, в то время, как могут быть вызваны и просто save и просто update. обновил тесты
UPD: Обновил исходники — добавил пример с использованием Spring Data JPA (использовал только сущности, без слушателей). Слушателя
org.springframework.data.jpa.domain.support.AuditingEntityListener не стал добавлять, чтобы сохранился пример с обработкой событий в Hibernate