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;
}