JavaRush /Курсы /SQL & Hibernate /Управление обновлением данных

Управление обновлением данных

SQL & Hibernate
12 уровень , 4 лекция
Открыта

5.1 Время изменения данных

Когда ты хранишь различные записи в базе данных долгие годы, то очень часто возникают два вопроса:

  • Когда данная запись была добавлена в базу данных?
  • Когда данная запись менялась последний раз?

Это настолько частые задачи, что практически в каждую таблицу в базе данных добавляют две колонки:

  • created_time
  • updated_time

В первой хранится дата и время создания записи, а во второй – дата и время ее последнего изменения. И в каждом Entity-классе есть поля:


@Entity
@Table(name = "entities")	
public class Entity {
  ...
 
  @Column(name="created_time")
  private Date created;
 
  @Column(name="updated_time")
  private Date updated;
}

Hibernate может взять на себя всю работу по контролю за временем обновления объектов в базе с помощью двух аннотаций @CreationTimestamp и @UpdateTimestamp.

Пример:


@Entity
@Table(name = "entities")	
public class Entity {
  ...
 
	@CreationTimestamp
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "create_date")
    private Date createDate;
 
	@UpdateTimestamp
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "modify_date")
	private Date modifyDate;
}

В колонках, помеченных данными аннотациями, всегда будет храниться правильное время создания объекта и его последнего изменения.

5.2 Аннотация @PrePersist

Если тебе нужны какие-то более сложные сценарии для контроля времени объекта, то у Hibernate есть аннотации и на этот случай. Ими можно пометить методы класса, и Hibernate вызовет эти методы, когда будет сохранять объект в базу. Всего таких аннотаций 7:

@PrePersist Вызывается перед сохранением объекта в базу. (SQL INSERT)
@PostPersist Вызывается сразу после сохранения объекта в базу. (SQL INSERT)
@PreRemove Вызывается перед удалением объекта в базе.
@PostRemove Вызывается после удаления объекта в базе.
@PreUpdate Вызывается перед обновлением (SQL UPDATE) объекта в базе.
@PostUpdate Вызывается после обновления (SQL UPDATE) объекта в базе.
@PostLoad Вызывается после того, как объект был загружен из базы.

Давай напишем пример, где мы прописываем классу правильное время создания и обновления его объектов:


@Entity
@Table(name = "entities")	
public class Entity {
  ...
 
  @Column(name="created_time")
  private Date created;
 
  @Column(name="updated_time")
  private Date updated;
 
  @PrePersist
  protected void onCreate() {
    created = new Date();
  }
 
  @PreUpdate
  protected void onUpdate() {
  updated = new Date();
  }
}

Если Hibernate сохраняет объект в первый раз, то он вызовет метод, помеченный аннотацией @PrePersist. Если обновляет в базе существующий объект, то вызовет метод, помеченный аннотацией @PreUpdate.

5.3 Добавляем свои EntityListeners

Если тебе очень надо, то ты можешь отделить методы, которые вызывает Hibernate, от объекта, у которого он их вызывает. Спецификация JPA позволяет тебе объявить listener-классы, которые будут вызываться в определенные моменты обработки Entity-объектов.

Если у тебя много похожих Entity-объектов, то ты можешь вынести их часть в базовый класс и добавить Listener, который бы управлял их поведением. Пример:


@MappedSuperclass
public abstract class BaseEntity {
 
    private Timestamp createdOn;
 
    private Timestamp updatedOn;
 
}


@Entity
public class User extends BaseEntity {
 
     @Id
     private Long id;
 
     private String name;
}

Тогда для класса BaseEntity можно создать специальный listener-класс:


public class TimeEntityListener {
 
    public void onPersist(Object entity) {
    	if (entity instanceof BaseEntity) {
        	BaseEntity baseEntity = (BaseEntity) entity;
        	baseEntity.createdOn = now();
    	}
    }
 
    public void onUpdate(Object entity) {
    	if (entity instanceof BaseEntity) {
        	BaseEntity baseEntity = (BaseEntity) entity;
        	baseEntity.updatedOn = now();
    	}
    }
 
    private Timestamp now() {
    	return Timestamp.from(LocalDateTime.now().toInstant(ZoneOffset.UTC)   );
    }
}

И связать класс User и его listener можно с помощью парочки аннотаций:


@Entity @EntityListeners(class= TimeEntityListener.class)
public class User extends BaseEntity {
 
     @Id
     private Long id;
 
     private String name;
}

Комментарии (14)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Евгений Уровень 97
29 января 2025
Не понял про листенеры, сделал такой класс:

@MappedSuperclass
public class BaseEntity {
    
    @CreationTimestamp
    @Column(name = "create_date")
    private LocalDateTime createDate;

    @UpdateTimestamp
    @Column(name = "update_date")
    private LocalDateTime updateDate;
}
Наследую от него свои ентити и все работает... НЕ догоняю в чем отличие в функционале? Реализация проще...
Олег Уровень 106 Expert
13 октября 2024
Перестал понимать :С
Роман Уровень 88
12 января 2025
бро :C
Mitrus Latovous Уровень 41
30 ноября 2023
А разве над методами класса TimeEntityListener не нужно ставить аннотации @PrePersist и @PreUpdate?
Михаил Шапошников Уровень 1 Expert
27 декабря 2023
Нужно Как пример

public class TimeEntityListener {

    @PrePersist
    public void onPersist(Object entity) {
        if (entity instanceof BaseEntity) {
            BaseEntity baseEntity = (BaseEntity) entity;
            baseEntity.createdOn = now();
        }
    }

    @PreUpdate
    public void onUpdate(Object entity) {
        if (entity instanceof BaseEntity) {
            BaseEntity baseEntity = (BaseEntity) entity;
            baseEntity.updatedOn = now();
        }
    }

    private Timestamp now() {
        return Timestamp.from(LocalDateTime.now().toInstant(ZoneOffset.UTC));
    }
}
Dmytryi Shubchynskyi Уровень 111 Expert
18 марта 2023
Как подключить гибер в учебный проект?
Саша И. Уровень 101 Expert
20 марта 2023
Как обычно - через боль и страдания 😅
Dmytryi Shubchynskyi Уровень 111 Expert
21 марта 2023
Спасибо, получилось 😀
Роман Уровень 92
14 мая 2024
Ахаха))) У меня также вышло)))
Kirill Уровень 106 Expert
8 августа 2024
Боль и страдания или подключить?
BlackGrizzli Team Уровень 46
28 февраля 2023
Я так понимаю, изменение базы через createQuery вообще не вызывает @PreUpdate метод

try (Session session = main.sf.openSession()) {
            Transaction transaction = session.beginTransaction();
            session.createQuery("update Employee set job = '.......' where Id = .....").executeUpdate();
            transaction.commit();
        }
Колонку поменяли, метод не вызвался. А вот если мы сначала вытащим объект, изменим его через сеттер и пихнем обратно через

persist(employee) // причем persist вызовет UPDATE
То тогда все будет работать и колонка "updated_time" ИЗМЕНИТСЯ !

try (Session session = main.sf.openSession()) {
            Transaction transaction = session.beginTransaction();
            Employee employee = session.get(Employee.class, 1);
            employee.setJob("coolJob!");
            session.persist(employee); // выполнит update
            transaction.commit();
        }
Буде очень рад, если в первом примере я что-то не так делаю, что не дает вызвать метод @PreUpdate ! Поправьте меня
Саша И. Уровень 101 Expert
20 марта 2023
@PreUpdate никогда не будет вызвать через createQuery(), потому что последний по своей сути является обходом Hibernate - вы отправляете SQL-инструкцию в БД вместо автоматизации. Нужно использовать .persist(), .merge() (аналог update) и прочие методы отсюда. чтобы эти аннотации работали:
Anonymous #3322801 Уровень 1 Expert
23 ноября 2023
Спасибо! Очень доходчиво
Руслан Никитин Уровень 109
1 февраля 2025
два пива господину Саша И.