Временная метка создания и временная метка последнего обновления с Hibernate и MySQL
Для определенного объекта Hibernate у нас есть требование сохранить время его создания и последний раз, когда он был обновлен. Как бы вы это разработали?
-
Какие типы данных вы использовали бы в базе данных (предполагая MySQL, возможно, в другой часовой пояс, что JVM)? Будут ли типы данных осведомлены о часовом поясе?
-
Какие типы данных вы используете в Java (Date
, Calendar
, long
,...)?
-
Кому бы вы не ответили за установку временных меток, базы данных, структуры ORM (Hibernate) или прикладного программиста?
-
Какие аннотации вы использовали бы для сопоставления (например, @Temporal
)?
Я ищу не только рабочее решение, но и безопасное и хорошо разработанное решение.
Ответы
Ответ 1
Если вы используете аннотации JPA, вы можете использовать @PrePersist
и @PreUpdate
перехватчики событий:
@Entity
@Table(name = "entities")
public class Entity {
...
private Date created;
private Date updated;
@PrePersist
protected void onCreate() {
created = new Date();
}
@PreUpdate
protected void onUpdate() {
updated = new Date();
}
}
или вы можете использовать аннотацию @EntityListener
в классе и поместить код события во внешний класс.
Ответ 2
Вы можете просто использовать @CreationTimestamp
и @UpdateTimestamp
:
@CreationTimestamp
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "create_date")
private Date createDate;
@UpdateTimestamp
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "modify_date")
private Date modifyDate;
Ответ 3
Принимая ресурсы в этом сообщении вместе с информацией, полученной слева и справа из разных источников, я пришел с этим элегантным решением, создаю следующий абстрактный класс
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@MappedSuperclass
public abstract class AbstractTimestampEntity {
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created", nullable = false)
private Date created;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "updated", nullable = false)
private Date updated;
@PrePersist
protected void onCreate() {
updated = created = new Date();
}
@PreUpdate
protected void onUpdate() {
updated = new Date();
}
}
и все его сущности расширяют его, например:
@Entity
@Table(name = "campaign")
public class Campaign extends AbstractTimestampEntity implements Serializable {
...
}
Ответ 4
Вы также можете использовать перехватчик для установки значений
Создайте интерфейс TimeStamped, который реализует ваши объекты
public interface TimeStamped {
public Date getCreatedDate();
public void setCreatedDate(Date createdDate);
public Date getLastUpdated();
public void setLastUpdated(Date lastUpdatedDate);
}
Определите перехватчик
public class TimeStampInterceptor extends EmptyInterceptor {
public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState,
Object[] previousState, String[] propertyNames, Type[] types) {
if (entity instanceof TimeStamped) {
int indexOf = ArrayUtils.indexOf(propertyNames, "lastUpdated");
currentState[indexOf] = new Date();
return true;
}
return false;
}
public boolean onSave(Object entity, Serializable id, Object[] state,
String[] propertyNames, Type[] types) {
if (entity instanceof TimeStamped) {
int indexOf = ArrayUtils.indexOf(propertyNames, "createdDate");
state[indexOf] = new Date();
return true;
}
return false;
}
}
И зарегистрируйте его с помощью сеанса factory
Ответ 5
При использовании решения Olivier во время операций обновления вы можете столкнуться с:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Столбец "created" не может быть null
Чтобы решить эту проблему, добавьте updatable = false в аннотацию @Column атрибута "created":
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created", nullable = false, updatable=false)
private Date created;
Ответ 6
Спасибо всем, кто помог. После самостоятельного исследования (я тот парень, который задал вопрос), вот что я нашел наиболее полезным:
-
Тип столбца базы данных: агрегированное по часовой стрелке количество миллисекунд с 1970 года представлено как decimal(20)
, потому что 2 ^ 64 имеет 20 цифр, а дисковое пространство дешево; пусть будет просто. Кроме того, я не буду использовать ни DEFAULT CURRENT_TIMESTAMP
, ни триггеры. Я не хочу магии в БД.
-
Тип поля Java: long
. Временная метка Unix хорошо поддерживается в разных библиотеках, long
не имеет проблем с Y2038, арифметика timestamp выполняется быстро и просто (в основном оператор <
и оператор +
, если в вычислениях не задействованы никакие дни/месяцы/годы). И, что самое главное, и примитивные long
, и java.lang.Long
являются неизменяемыми — эффективно передаются по значению — в отличие от java.util.Date
s; Я бы очень разозлился, чтобы найти что-то вроде foo.getLastUpdate().setTime(System.currentTimeMillis())
при отладке кода другого.
-
Структура ORM должна отвечать за автоматическое заполнение данных.
-
Я еще не тестировал это, но только глядя на документы, я предполагаю, что @Temporal
выполнит эту работу; не знаю, могу ли я использовать @Version
для этой цели. @PrePersist
и @PreUpdate
- хорошие альтернативы для управления этим вручную. Добавление этого к супертипу уровня (общий базовый класс) для всех объектов - это симпатичная идея, при условии, что вам действительно нужна временная привязка для всех ваших объектов.
Ответ 7
Если вы используете API сеанса, обратные вызовы PrePersist и PreUpdate не будут работать в соответствии с этим ответом .
Я использую метод persist() для Hibernate Session в моем коде, поэтому единственный способ, которым я мог выполнить эту работу, - это код ниже и следующий сообщение в блоге (также опубликовано в ответ ).
@MappedSuperclass
public abstract class AbstractTimestampEntity {
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created")
private Date created=new Date();
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "updated")
@Version
private Date updated;
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public Date getUpdated() {
return updated;
}
public void setUpdated(Date updated) {
this.updated = updated;
}
}
Ответ 8
Теперь есть также аннотации @CreatedDate и @LastModifiedDate.
=> https://programmingmitra.blogspot.fr/2017/02/automatic-spring-data-jpa-auditing-saving-CreatedBy-createddate-lastmodifiedby-lastmodifieddate-automatically.html
(Весенние рамки)
Ответ 9
Хороший подход состоит в том, чтобы иметь общий базовый класс для всех ваших объектов. В этом базовом классе вы можете иметь свое свойство id, если оно обычно называется во всех ваших сущностях (общий дизайн), ваших свойствах создания и последнего обновления.
Для даты создания вы просто сохраняете свойство java.util.Date. Убедитесь, что всегда инициализируйте его новой Date().
В последнем поле обновления вы можете использовать свойство Timestamp, вам нужно сопоставить его с @Version. С помощью этой аннотации свойство автоматически обновится с помощью Hibernate. Помните, что Hibernate также применит оптимистичную блокировку (это хорошо).
Ответ 10
Просто для усиления: java.util.Calender
не относится к отметкам времени. java.util.Date
на какое-то время, агностик региональных вещей, таких как часовые пояса. Большинство баз данных хранят вещи таким образом (даже если они выглядят не такими, обычно это настройка часового пояса в клиентском программном обеспечении, данные хорошие)
Ответ 11
Следующий код работал для меня.
package com.my.backend.models;
import java.util.Date;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import lombok.Getter;
import lombok.Setter;
@MappedSuperclass
@Getter @Setter
public class BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
@CreationTimestamp
@ColumnDefault("CURRENT_TIMESTAMP")
protected Date createdAt;
@UpdateTimestamp
@ColumnDefault("CURRENT_TIMESTAMP")
protected Date updatedAt;
}
Ответ 12
Как тип данных в JAVA, я настоятельно рекомендую использовать java.util.Date. При использовании Calendar я столкнулся с довольно неприятными проблемами с часовым поясом. Смотрите Тема.
Для установки временных меток я бы рекомендовал использовать либо подход AOP, либо просто использовать триггеры в таблице (на самом деле это единственное, что я когда-либо обнаружил, что использование триггеров приемлемо).
Ответ 13
Возможно, вы захотите сохранить время как DateTime и в UTC. Обычно я использую DateTime вместо Timestamp из-за того, что MySql преобразует даты в UTC и обратно в локальное время при хранении и извлечении данных. Я бы предпочел придерживаться такой логики в одном месте (бизнес-уровень). Я уверен, что есть и другие ситуации, когда использование Timestamp предпочтительнее.
Ответ 14
Решение с собственной функциональностью MySQL:
Обновление и создание временных меток с MySQL
Прекрасно работает с java и hibernate.
Ответ 15
У нас была аналогичная ситуация. Мы использовали Mysql 5.7.
CREATE TABLE my_table (
...
updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
Это сработало для нас.
Ответ 16
Если мы используем @Transactional в наших методах, @CreationTimestamp и @UpdateTimestamp сохранят значение в БД, но вернут значение null после использования save (...).
В этой ситуации, используя saveAndFlush (...), добились цели
Ответ 17
Я думаю, что лучше не делать это в коде Java, вы можете просто установить значение столбца по умолчанию в определении таблицы MySql.
![enter image description here]()