Pull to refresh

Spring и обработка событий в Hibernate

Reading time 3 min
Views 18K
Как-то обделена на хабре такая тема, как обработка событий при работе с сущностями с использованием Hibernate — я смог найти только один пост уже почти мохнатого года. Но то аудит, а нам нужна возможность автоматизировать работу с некоторыми атрибутами сущностей и при этом упростить процедуру работы с ними.

Для начала создадим демонстрационный стенд с двумя сущностями 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
Tags:
Hubs:
+10
Comments 17
Comments Comments 17

Articles