Как-то обделена на хабре такая тема, как обработка событий при работе с сущностями с использованием 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