Временная метка создания и временная метка последнего обновления с 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;
    }
}

Ответ 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 предпочтительнее.

Ответ 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