[java] Hibernate 및 MySQL을 사용한 생성 타임 스탬프 및 마지막 업데이트 타임 스탬프

특정 최대 절전 모드 엔터티의 경우 생성 시간과 마지막 업데이트 시간을 저장해야합니다. 이것을 어떻게 설계 하시겠습니까?

  • 데이터베이스에서 어떤 데이터 유형을 사용 하시겠습니까 (MySQL, 아마도 다른 시간대의 JVM이라고 가정)? 데이터 유형이 시간대를 인식합니까?

  • 당신이 자바에서 어떤 데이터 유형을 사용 ( Date, Calendar, long, …)?

  • 타임 스탬프 (데이터베이스, ORM 프레임 워크 (Hibernate) 또는 애플리케이션 프로그래머)를 설정하는 담당자는 누구입니까?

  • 매핑에 어떤 주석을 사용 @Temporal하시겠습니까 (예 🙂 ?

나는 작업 솔루션을 찾고있을뿐만 아니라 안전하고 잘 설계된 솔루션을 찾고 있습니다.



답변

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클래스 에서 주석을 사용하고 이벤트 코드를 외부 클래스에 배치 할 수 있습니다 .


답변

당신은 사용할 수 있습니다 @CreationTimestamp@UpdateTimestamp:

@CreationTimestamp
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "create_date")
private Date createDate;

@UpdateTimestamp
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "modify_date")
private Date modifyDate;


답변

이 게시물의 리소스를 다른 소스에서 왼쪽과 오른쪽으로 가져온 정보와 함께이 우아한 솔루션과 함께 다음과 같은 추상 클래스를 만들었습니다.

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 {
...
}


답변

1. 사용해야 할 데이터베이스 열 유형

첫 번째 질문은

데이터베이스에서 어떤 데이터 유형을 사용 하시겠습니까 (MySQL, 아마도 다른 시간대의 JVM이라고 가정)? 데이터 유형이 시간대를 인식합니까?

MySQL에서 TIMESTAMP열 유형은 JDBC 드라이버 로컬 시간대에서 데이터베이스 시간대로 이동하지만 최대 시간 소인까지만 타임 스탬프를 저장할 수 있습니다.'2038-01-19 03:14:07.999999 있으므로 향후 최선의 선택은 아닙니다.

따라서이 DATETIME상한을 갖지 않는 대신 사용 하는 것이 좋습니다 . 그러나 DATETIME시간대를 인식하지 못합니다. 따라서 데이터베이스 측면에서 UTC를 사용하고 hibernate.jdbc.time_zoneHibernate 속성을 사용하는 것이 가장 좋습니다 .

hibernate.jdbc.time_zone설정 에 대한 자세한 내용은 이 기사를 확인 하십시오 .

2. 어떤 엔티티 속성 유형을 사용해야합니까

두 번째 질문은 다음과 같습니다.

Java (Date, Calendar, long, …)에서 어떤 데이터 유형을 사용 하시겠습니까?

Java 측에서는 Java 8을 사용할 수 있습니다 LocalDateTime. 레거시를 사용할 수도 Date있지만 Java 8 날짜 / 시간 유형은 변경 불가능하므로 더 좋으며 로깅 할 때 시간대를 로컬 시간대로 이동하지 않습니다.

Hibernate가 지원하는 Java 8 날짜 / 시간 유형에 대한 자세한 내용은 이 기사를 확인 하십시오 .

이제이 질문에 대답 할 수도 있습니다.

매핑에 어떤 주석을 사용 @Temporal하시겠습니까 (예 🙂 ?

당신은을 사용하는 경우 LocalDateTime또는 java.sql.Timestamp타임 스탬프 개체 속성을 매핑, 당신은 사용할 필요가 없습니다@Temporal Hibernate는 이미이 속성은 JDBC 타임 스탬프로 저장하는 것을 알고 있기 때문이다.

을 사용하는 경우에만 다음 과 같이 주석 java.util.Date을 지정해야합니다 @Temporal.

@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_on")
private Date createdOn;

그러나 다음과 같이 매핑하면 훨씬 좋습니다.

@Column(name = "created_on")
private LocalDateTime createdOn;

감사 열 값을 생성하는 방법

세 번째 질문은 다음과 같습니다.

타임 스탬프 (데이터베이스, ORM 프레임 워크 (Hibernate) 또는 애플리케이션 프로그래머)를 설정하는 담당자는 누구입니까?

매핑에 어떤 주석을 사용 하시겠습니까 (예 : @Temporal)?

이 목표를 달성 할 수있는 방법은 여러 가지가 있습니다. 데이터베이스가 그렇게하도록 허용 할 수 있습니다.

를 들어 create_on열, 당신은 사용할 수 DEFAULT, DDL 제약을 같이 :

ALTER TABLE post
ADD CONSTRAINT created_on_default
DEFAULT CURRENT_TIMESTAMP() FOR created_on;

를 들어 updated_on열, 당신과 함께 열 값을 설정하는 DB 트리거를 사용할 수 있습니다CURRENT_TIMESTAMP() 주어진 행이 수정 될 때마다.

또는 JPA 또는 Hibernate를 사용하여 설정하십시오.

다음과 같은 데이터베이스 테이블이 있다고 가정합니다.

감사 열이있는 데이터베이스 테이블

그리고 각 테이블에는 다음과 같은 열이 있습니다.

  • created_by
  • created_on
  • updated_by
  • updated_on

최대 절전 모드 @CreationTimestamp@UpdateTimestamp주석 사용

최대 절전 모드는 및 열 을 매핑하는 데 사용할 수있는 @CreationTimestamp@UpdateTimestamp주석을 제공합니다 .created_onupdated_on

@MappedSuperclass모든 엔티티에 의해 확장 될 기본 클래스를 정의하는 데 사용할 수 있습니다 .

@MappedSuperclass
public class BaseEntity {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "created_on")
    @CreationTimestamp
    private LocalDateTime createdOn;

    @Column(name = "created_by")
    private String createdBy;

    @Column(name = "updated_on")
    @UpdateTimestamp
    private LocalDateTime updatedOn;

    @Column(name = "updated_by")
    private String updatedBy;

    //Getters and setters omitted for brevity
}

그리고 모든 엔티티는 다음 BaseEntity과 같이 확장됩니다 .

@Entity(name = "Post")
@Table(name = "post")
public class Post extend BaseEntity {

    private String title;

    @OneToMany(
        mappedBy = "post",
        cascade = CascadeType.ALL,
        orphanRemoval = true
    )
    private List<PostComment> comments = new ArrayList<>();

    @OneToOne(
        mappedBy = "post",
        cascade = CascadeType.ALL,
        orphanRemoval = true,
        fetch = FetchType.LAZY
    )
    private PostDetails details;

    @ManyToMany
    @JoinTable(
        name = "post_tag",
        joinColumns = @JoinColumn(
            name = "post_id"
        ),
        inverseJoinColumns = @JoinColumn(
            name = "tag_id"
        )
    )
    private List<Tag> tags = new ArrayList<>();

    //Getters and setters omitted for brevity
}

사용에 대한 자세한 내용은 이 문서를@MappedSuperclass 확인 하십시오 .

그러나,해도 createdOnupdateOn속성이 최대 절전 모드 전용으로 설정 @CreationTimestamp하고 @UpdateTimestamp주석의 createdByupdatedBy 다음 JPA 용액에 의해 도시 된 바와 같이, 애플리케이션 콜백 등록을 필요로한다.

JPA 사용 @EntityListeners

감사 속성을 Embeddable에 캡슐화 할 수 있습니다.

@Embeddable
public class Audit {

    @Column(name = "created_on")
    private LocalDateTime createdOn;

    @Column(name = "created_by")
    private String createdBy;

    @Column(name = "updated_on")
    private LocalDateTime updatedOn;

    @Column(name = "updated_by")
    private String updatedBy;

    //Getters and setters omitted for brevity
}

AuditListener감사 속성을 설정하려면을 만듭니다 .

public class AuditListener {

    @PrePersist
    public void setCreatedOn(Auditable auditable) {
        Audit audit = auditable.getAudit();

        if(audit == null) {
            audit = new Audit();
            auditable.setAudit(audit);
        }

        audit.setCreatedOn(LocalDateTime.now());
        audit.setCreatedBy(LoggedUser.get());
    }

    @PreUpdate
    public void setUpdatedOn(Auditable auditable) {
        Audit audit = auditable.getAudit();

        audit.setUpdatedOn(LocalDateTime.now());
        audit.setUpdatedBy(LoggedUser.get());
    }
}

를 등록 AuditListener하기 위해 @EntityListenersJPA 주석을 사용할 수 있습니다 .

@Entity(name = "Post")
@Table(name = "post")
@EntityListeners(AuditListener.class)
public class Post implements Auditable {

    @Id
    private Long id;

    @Embedded
    private Audit audit;

    private String title;

    @OneToMany(
        mappedBy = "post",
        cascade = CascadeType.ALL,
        orphanRemoval = true
    )
    private List<PostComment> comments = new ArrayList<>();

    @OneToOne(
        mappedBy = "post",
        cascade = CascadeType.ALL,
        orphanRemoval = true,
        fetch = FetchType.LAZY
    )
    private PostDetails details;

    @ManyToMany
    @JoinTable(
        name = "post_tag",
        joinColumns = @JoinColumn(
            name = "post_id"
        ),
        inverseJoinColumns = @JoinColumn(
            name = "tag_id"
        )
    )
    private List<Tag> tags = new ArrayList<>();

    //Getters and setters omitted for brevity
}

JPA로 감사 속성을 구현하는 방법에 대한 자세한 내용은 이 문서를@EntityListener 확인 하십시오 .


답변

인터셉터를 사용하여 값을 설정할 수도 있습니다

엔티티가 구현하는 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;
    }
}

그리고 세션 팩토리에 등록하십시오


답변

Olivier의 솔루션을 사용하면 업데이트 문 중에 다음과 같은 문제가 발생할 수 있습니다.

com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException : ‘created’열은 null 일 수 없습니다.

이 문제를 해결하려면 “작성된”속성의 @Column 주석에 updatable = false를 추가하십시오.

@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created", nullable = false, updatable=false)
private Date created;


답변

도와 주신 모든 분들께 감사드립니다. 나 자신에게 약간의 연구를 한 후에 (나는 질문을 한 사람입니다), 여기 내가 가장 이해하는 것이 있습니다.

  • 데이터베이스 열 유형 : 1970 년 이후 시간대에 관계없이 밀리 초 ( decimal(20)2 초) 는 2 자리 64 개에 20 자리가 있고 디스크 공간이 저렴하기 때문에 나타납니다 . 솔직 해지자 또한, DEFAULT CURRENT_TIMESTAMP트리거도 사용하지 않습니다. 나는 DB에서 마술을 원하지 않는다.

  • Java 필드 유형 : long. Unix 타임 스탬프는 다양한 라이브러리에서 잘 지원되며 longY2038 문제가 없으며 타임 스탬프 산술이 빠르고 쉽습니다 (주로 연산자 <와 연산자 +는 일 / 월 / 년이 계산에 관여하지 않는다고 가정). 그리고 가장 중요한 것은, 프리미티브 longjava.lang.Longs 모두 s와 달리 불변 이며 (가치에 의해 효과적으로 전달됨) java.util.Date; foo.getLastUpdate().setTime(System.currentTimeMillis())다른 사람의 코드를 디버깅 할 때 와 같은 것을 찾기 위해 정말로 화가 났을 것 입니다.

  • ORM 프레임 워크는 데이터를 자동으로 채울 책임이 있습니다.

  • 나는 이것을 아직 테스트하지는 않았지만 내가 할 것이라고 생각하는 문서 만보 고있다 @Temporal. @Version이 목적으로 사용할 수 있는지 확실하지 않습니다 . @PrePersist@PreUpdate수동으로 제어하는 좋은 대안이다. 모든 엔티티에 대한 레이어 슈퍼 타입 (공통 기본 클래스)에 그 추가, 당신이 정말로 위해 타임 스탬프를 원하는 귀여운 아이디어가 제공되며 모든 당신의 엔티티.