การสร้างเวลาประทับและเวลาการอัพเดทล่าสุดด้วย Hibernate และ MySQL


244

สำหรับนิติบุคคลที่ไฮเบอร์เนตเรามีข้อกำหนดในการจัดเก็บเวลาการสร้างและครั้งล่าสุดที่มีการอัปเดต คุณจะออกแบบสิ่งนี้อย่างไร

  • ชนิดข้อมูลใดที่คุณจะใช้ในฐานข้อมูล (สมมติว่า MySQL อาจเป็นในเขตเวลาอื่นที่ JVM) ชนิดข้อมูลจะตระหนักถึงเขตเวลาหรือไม่

  • ข้อมูลประเภทใดบ้างที่คุณจะใช้ในชวา ( Date, Calendar, long, ... )?

  • คุณจะรับผิดชอบการตั้งค่าการประทับเวลา - ฐานข้อมูล, กรอบงาน ORM (ไฮเบอร์เนต) หรือโปรแกรมเมอร์โปรแกรม

  • คุณจะใช้คำอธิบายประกอบใดสำหรับการทำแผนที่ (เช่น@Temporal)

ฉันไม่เพียง แต่กำลังมองหาวิธีแก้ปัญหาการทำงาน แต่เพื่อความปลอดภัยและการออกแบบที่ดี

คำตอบ:


266

หากคุณกำลังใช้คำอธิบายประกอบ JPA คุณสามารถใช้@PrePersistและ@PreUpdatehooks กิจกรรมทำสิ่งนี้:

@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คำอธิบายประกอบในชั้นเรียนและวางรหัสเหตุการณ์ในชั้นเรียนภายนอก


7
ทำงานได้โดยไม่มีปัญหาใด ๆ ใน J2SE เนื่องจาก @PrePersist และ @PerUpdate เป็นคำอธิบายประกอบ JPA
Kdeveloper

2
@Kumar - ในกรณีที่คุณใช้เซสชัน Hibernate ธรรมดา (แทนที่จะเป็น JPA) คุณสามารถลองใช้ฟังเหตุการณ์ไฮเบอร์เนตถึงแม้ว่าจะไม่ใช่คำอธิบายประกอบที่สง่างามและกะทัดรัดของ JPA
Shailendra

43
ในไฮเบอร์เนตปัจจุบันด้วย JPA สามารถใช้ "@CreateTimestamp" และ "@UpdateTimestamp"
Florian Loch

@FlorianLoch มีวันที่เทียบเท่ากับ Timestamp หรือไม่ หรือฉันจะต้องสร้างของตัวเอง?
ไมค์

151

คุณสามารถใช้@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
ขอบคุณครับเช่นสิ่งเล็ก ๆ จำเป็นต้องอัปเดตการประทับเวลา ฉันไม่รู้ คุณบันทึกวันของฉัน
Virendra Sagar

มีTemporalType.DATEในกรณีแรกและTemporalType.TIMESTAMPในกรณีที่สอง
v.ladynev

คุณกำลังบอกว่านี่เป็นการตั้งค่าโดยอัตโนมัติหรือไม่? นั่นไม่ใช่ประสบการณ์ของฉัน ดูเหมือนว่าแม้จะมี@CreationTimestampและ@UpdateTimestampหนึ่งต้องการบางอย่าง@Column(..., columnDefinition = "timestamp default current_timestamp")หรือใช้@PrePersistและ@PreUpdate(หลังยังมั่นใจว่าลูกค้าไม่สามารถกำหนดค่าที่แตกต่างกัน)
Arjan

2
เมื่อฉันอัปเดตวัตถุและยืนยันมัน bd สูญเสีย create_date ... เพราะอะไร
Brenno Leal

1
ฉันเอากรณีของฉันออกnullable=falseจากที่@Column(name = "create_date" , nullable=false)ทำงาน
Shantaram Tupe

113

การใช้ทรัพยากรในโพสต์นี้พร้อมกับข้อมูลที่นำมาทางซ้ายและขวาจากแหล่งข้อมูลที่แตกต่างกันฉันมาพร้อมกับโซลูชันที่สง่างามนี้

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

5
สิ่งนี้เป็นสิ่งที่ดีจนกระทั่งคุณต้องการเพิ่มพฤติกรรมพิเศษที่แตกต่างให้กับเอนทิตี้ของคุณ (และคุณไม่สามารถขยายคลาสพื้นฐานได้มากกว่าหนึ่งคลาส afaik วิธีเดียวที่จะได้รับผลกระทบเดียวกันโดยไม่ต้องคลาสฐานเป็นแม้ว่ามันกว้างยาวหรือฟังเหตุการณ์ดู @ kieren dixon คำตอบ
gpilotino

3
ฉันจะทำสิ่งนี้โดยใช้ทริกเกอร์ MySQL ดังนั้นแม้ว่าเอนทิตีแบบเต็มจะไม่ถูกบันทึกหรือถูกแก้ไขโดยแอปพลิเคชันภายนอกหรือแบบสอบถามแบบแมนนวลมันจะยังคงอัปเดตฟิลด์เหล่านี้
เว็บเน็ต

3
คุณช่วยยกตัวอย่างการทำงานให้ฉันได้บ้างหรือnot-null property references a null or transient value: package.path.ClassName.created
เปล่า

@rishiAgar ไม่ฉันยังไม่ได้ แต่ตอนนี้ฉันได้กำหนดวันที่ให้กับคุณสมบัติของฉันจากตัวสร้างเริ่มต้น จะแจ้งให้คุณทราบเมื่อฉันพบ
สุมิตร Ramteke

1
เปลี่ยนเป็นเพื่อ@Column(name = "updated", nullable = false, insertable = false)ให้ใช้งานได้ ที่น่าสนใจว่าคำตอบนี้มี upvotes มากมาย ..
displayname

20

1. ควรใช้คอลัมน์ฐานข้อมูลประเภทใด

คำถามแรกของคุณคือ:

ชนิดข้อมูลใดที่คุณจะใช้ในฐานข้อมูล (สมมติว่า MySQL อาจเป็นในเขตเวลาอื่นที่ JVM) ชนิดข้อมูลจะตระหนักถึงเขตเวลาหรือไม่

ใน MySQL TIMESTAMPชนิดคอลัมน์จะเปลี่ยนจากเขตเวลาท้องถิ่นของไดรเวอร์ JDBC ไปเป็นเขตเวลาของฐานข้อมูล แต่สามารถเก็บบันทึกเวลาได้สูงสุด'2038-01-19 03:14:07.999999เท่านั้นดังนั้นจึงไม่ใช่ตัวเลือกที่ดีที่สุดสำหรับอนาคต

ดังนั้นควรใช้DATETIMEแทนดีกว่าซึ่งไม่มีข้อ จำกัด ขอบเขตนี้ อย่างไรก็ตามDATETIMEไม่ทราบเขตเวลา ดังนั้นด้วยเหตุนี้จึงเป็นการดีที่สุดที่จะใช้ UTC ทางด้านฐานข้อมูลและใช้hibernate.jdbc.time_zoneคุณสมบัติ Hibernate

สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับhibernate.jdbc.time_zoneการตั้งค่าให้ตรวจสอบบทความนี้

2. คุณควรใช้ประเภททรัพย์สินประเภทใด

คำถามที่สองของคุณคือ:

ชนิดข้อมูลใดที่คุณจะใช้ใน Java (วันที่, ปฏิทิน, ยาว, ... )

ในด้าน Java คุณสามารถใช้ Java LocalDateTime8 นอกจากนี้คุณยังสามารถใช้แบบดั้งเดิมDateแต่ประเภท Java 8 Date / Time ดีกว่าเนื่องจากไม่เปลี่ยนรูปและไม่ทำการเปลี่ยนเขตเวลาเป็นเขตเวลาท้องถิ่นเมื่อทำการบันทึก

สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับ Java 8 วันที่ / เวลาประเภทการสนับสนุนจาก Hibernate ตรวจสอบบทความนี้

ตอนนี้เราสามารถตอบคำถามนี้ได้:

คุณจะใช้คำอธิบายประกอบใดสำหรับการทำแผนที่ (เช่น@Temporal)

หากคุณกำลังใช้LocalDateTimeหรือjava.sql.Timestampเพื่อแมปคุณสมบัติเอนทิตีการประทับเวลาดังนั้นคุณไม่จำเป็นต้องใช้@Temporalเนื่องจาก HIbernate รู้อยู่แล้วว่าคุณสมบัตินี้จะถูกบันทึกเป็น JDBC Timestamp

เฉพาะในกรณีที่คุณใช้java.util.Dateงานคุณจะต้องระบุ@Temporalคำอธิบายประกอบเช่นนี้:

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

แต่มันจะดีกว่ามากถ้าคุณแมปแบบนี้:

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

วิธีสร้างค่าคอลัมน์การตรวจสอบ

คำถามที่สามของคุณคือ:

คุณจะรับผิดชอบการตั้งค่าการประทับเวลา - ฐานข้อมูล, กรอบงาน ORM (ไฮเบอร์เนต) หรือโปรแกรมเมอร์โปรแกรม

คุณจะใช้คำอธิบายประกอบใดสำหรับการทำแผนที่ (เช่น @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

การใช้ Hibernate @CreationTimestampและ@UpdateTimestampหมายเหตุประกอบ

Hibernate เสนอ@CreationTimestampและ@UpdateTimestampคำอธิบายประกอบที่สามารถใช้ในการแมปcreated_onและupdated_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ตรวจสอบบทความนี้

อย่างไรก็ตามแม้ว่าจะมีการตั้งค่าคุณสมบัติcreatedOnและupdateOnโดยเฉพาะไฮเบอร์เนต@CreationTimestampและ@UpdateTimestampคำอธิบายประกอบcreatedByและยังupdatedByต้องลงทะเบียนแอปพลิเคชันการโทรกลับตามที่แสดงในโซลูชัน JPA ต่อไปนี้

ใช้ JPA @EntityListeners

คุณสามารถสรุปคุณสมบัติการตรวจสอบในแบบฝังได้:

@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คุณสามารถใช้@EntityListenersคำอธิบายประกอบ JPA:

@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ตรวจสอบบทความนี้


คำตอบที่ละเอียดมากขอบคุณ ผมไม่เห็นด้วยเกี่ยวกับการเลือกมากกว่าdatetime timestampคุณต้องการให้ฐานข้อมูลของคุณรู้เขตเวลาของการประทับเวลาของคุณ สิ่งนี้จะป้องกันข้อผิดพลาดในการแปลงเขตเวลา
Ole VV

timestsmpประเภทจะไม่จัดเก็บข้อมูลเขตเวลา เพียงแค่ทำการสนทนาจากแอพ TZ ถึง DB TZ ในความเป็นจริงคุณต้องการจัดเก็บไคลเอ็นต์ TZ แยกต่างหากและทำการสนทนาในแอปพลิเคชันก่อนที่จะแสดงผล UI
Vlad Mihalcea

แก้ไข. MySQL timestampอยู่ใน UTC เสมอ MySQL แปลงTIMESTAMPค่าจากเขตเวลาปัจจุบันเป็น UTC เพื่อจัดเก็บและกลับจาก UTC ไปยังเขตเวลาปัจจุบันเพื่อเรียกคืน เอกสาร MySQL: ประเภท DATE, DATETIME และ TIMESTAMP
Ole VV

17

คุณยังสามารถใช้ตัวดักเพื่อตั้งค่า

สร้างอินเทอร์เฟซชื่อ TimeStamped ซึ่งเอนทิตีของคุณใช้

public interface TimeStamped {
    public Date getCreatedDate();
    public void setCreatedDate(Date createdDate);
    public Date getLastUpdated();
    public void setLastUpdated(Date lastUpdatedDate);
}

กำหนด interceptor

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

และลงทะเบียนกับโรงงานเซสชั่น


1
ได้ผลขอบคุณ ข้อมูลเพิ่มเติมdocs.jboss.org/hibernate/core/4.0/manual/en-US/html_single/ …
Andrii Nemchenko

นี่เป็นทางออกหนึ่งถ้าคุณทำงานกับ SessionFactory แทนที่จะเป็น EntityManager!
olivmir

สำหรับผู้ที่ประสบปัญหาคล้ายกันกับที่ฉันทำในบริบทนี้: ถ้าเอนทิตีของคุณไม่ได้กำหนดเขตข้อมูลพิเศษเหล่านี้ (createdAt, ... ) แต่สืบทอดมาจากคลาสผู้ปกครองคลาสผู้ปกครองนี้จะต้องมีการเพิ่มความคิดเห็น ด้วย @MappedSuperclass - มิฉะนั้นไฮเบอร์เนตจะไม่พบฟิลด์เหล่านี้
olivmir

17

ด้วยวิธีแก้ปัญหาของ Olivier ในระหว่างแถลงการณ์การอัพเดทที่คุณอาจพบ:

com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: คอลัมน์ 'created' ไม่สามารถเป็นค่าว่างได้

ในการแก้ปัญหานี้ให้เพิ่ม updatable = false ในบันทึกย่อ @Column ของแอตทริบิวต์ "created":

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

1
@Versionเราใช้ เมื่อเอนทิตีถูกตั้งค่าการเรียกสองครั้งการโทรหนึ่งเป็นการบันทึกและการอัปเดตอีกรายการหนึ่ง ฉันกำลังเผชิญปัญหาเดียวกันเพราะสิ่งนี้ เมื่อฉันเพิ่ม@Column(updatable = false)มันแก้ไขปัญหาของฉัน
พระพิฆเนศ Satpute

12

ขอบคุณทุกคนที่ช่วย หลังจากทำวิจัยด้วยตัวเอง (ฉันเป็นคนที่ถามคำถาม) นี่คือสิ่งที่ฉันคิดว่าเหมาะสมที่สุด:

  • ประเภทคอลัมน์ฐานข้อมูล: จำนวนผู้ไม่เชื่อเรื่องเขตเวลาของมิลลิวินาทีตั้งแต่ปี 1970 แสดงเป็นdecimal(20)เพราะ 2 ^ 64 มี 20 หลักและพื้นที่ดิสก์มีราคาถูก ขอตรงไปตรงมา นอกจากนี้ฉันจะไม่ใช้DEFAULT CURRENT_TIMESTAMPหรือทริกเกอร์ ฉันไม่ต้องการเวทมนต์ในฐานข้อมูล

  • Java longประเภทข้อมูล: ประทับเวลา Unix การสนับสนุนอย่างดีทั่ว libs ต่างๆlongไม่มี Y2038 ปัญหาการประทับเวลาคณิตศาสตร์เป็นเรื่องง่าย (ส่วนใหญ่ผู้ประกอบการและรวดเร็ว<และผู้ประกอบการ+สมมติว่าไม่มีวัน / เดือน / ปีที่ผ่านมามีส่วนร่วมในการคำนวณ) และที่สำคัญที่สุดทั้งดั้งเดิมlongและjava.lang.Longเอสเปลี่ยนไม่ได้ - ผ่านไปอย่างมีประสิทธิภาพตามมูลค่า - ไม่เหมือนjava.util.Dates; ฉันจะโกรธมากที่จะหาสิ่งที่ชอบfoo.getLastUpdate().setTime(System.currentTimeMillis())เมื่อดีบักรหัสของคนอื่น

  • กรอบ ORM ควรรับผิดชอบในการกรอกข้อมูลโดยอัตโนมัติ

  • ฉันยังไม่ได้ทดสอบสิ่งนี้ แต่เพียงดูที่เอกสารที่ฉันคิดว่า@Temporalจะทำงาน ไม่แน่ใจว่าฉันจะใช้@Versionเพื่อจุดประสงค์นี้หรือไม่ @PrePersistและ@PreUpdateเป็นทางเลือกที่ดีในการควบคุมด้วยตนเอง การเพิ่มลงในเลเยอร์ supertype (คลาสพื้นฐานทั่วไป) สำหรับเอนทิตีทั้งหมดเป็นแนวคิดที่น่ารักหากคุณต้องการประทับเวลาสำหรับเอนทิตีทั้งหมดของคุณ


ในขณะที่ Longs and Longs อาจไม่เปลี่ยนรูป แต่นั่นจะไม่ช่วยคุณในสถานการณ์ที่คุณอธิบาย พวกเขายังสามารถพูดได้ว่า foo.setLastUpdate (Long ใหม่ (System.currentTimeMillis ());
Ian McLaird

2
ไม่เป็นไร. Hibernate ต้องการตัวตั้งค่าอยู่ดี (หรือจะพยายามเข้าถึงฟิลด์โดยตรงผ่านการสะท้อนกลับ) ฉันกำลังพูดถึงความยากลำบากในการไล่ล่าว่าใครกำลังแก้ไขการประทับเวลาจากรหัสแอปพลิเคชันของเรา มันยากหากคุณสามารถทำได้โดยใช้ทะเยอทะยาน
ngn

ฉันเห็นด้วยกับข้อเรียกร้องของคุณว่ากรอบ ORM ควรจะรับผิดชอบในการกรอกวันที่โดยอัตโนมัติ แต่ฉันจะไปอีกขั้นตอนหนึ่งและบอกว่าวันที่ควรจะตั้งจากนาฬิกาของเซิร์ฟเวอร์ฐานข้อมูลมากกว่าลูกค้า ฉันไม่ชัดเจนว่านี่จะบรรลุเป้าหมายนี้หรือไม่ ใน sql ฉันสามารถทำได้โดยใช้ฟังก์ชั่น sysdate แต่ฉันไม่รู้วิธีการทำเช่นนี้ใน Hibernate หรือการใช้ JPA
MiguelMunoz

ฉันไม่ต้องการเวทมนต์ในฐานข้อมูล ฉันเห็นสิ่งที่คุณหมายถึง แต่ฉันชอบที่จะพิจารณาความจริงที่ว่าฐานข้อมูลควรป้องกันตัวเองจากนักพัฒนาที่ไม่ดี / ใหม่ / clueless ความถูกต้องของข้อมูลเป็นสิ่งสำคัญมากใน บริษัท ขนาดใหญ่คุณไม่สามารถพึ่งพาผู้อื่นเพื่อแทรกข้อมูลที่ดีได้ ข้อ จำกัด ค่าเริ่มต้นและ FK จะช่วยให้บรรลุเป้าหมายนั้น
Icegras

6

ในกรณีที่คุณใช้ Session API การเรียก PrePersist และ PreUpdate จะไม่ทำงานตามคำตอบนี้

ฉันใช้เมธอด persist () ของ Hibernate ในรหัสของฉันดังนั้นวิธีเดียวที่ฉันจะทำให้งานนี้มีรหัสด้านล่างและติดตามโพสต์บล็อกนี้(โพสต์ในคำตอบด้วย )

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

ควรส่งคืนวัตถุที่ถูกโคลนเช่นupdated.clone()องค์ประกอบอื่น ๆ สามารถจัดการสถานะภายใน (วันที่)
1ambda


3

รหัสต่อไปนี้ทำงานให้ฉัน

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

สวัสดีครับว่าทำไมเราจะต้องprotected Integer id;เป็นprotectedในระดับผู้ปกครองโดยทั่วไปเพราะผมไม่สามารถใช้ในกรณีทดสอบของฉันเป็น.getId()
ชารีฟ

2

วิธีที่ดีคือการมีคลาสพื้นฐานร่วมกันสำหรับเอนทิตีทั้งหมดของคุณ ในคลาสพื้นฐานนี้คุณสามารถมีคุณสมบัติ id ของคุณหากชื่อนั้นมีชื่ออยู่ในเอนทิตีทั้งหมดของคุณ (การออกแบบทั่วไป) คุณสมบัติการสร้างและวันที่อัปเดตล่าสุดของคุณ

สำหรับวันที่สร้างคุณเพียงเก็บคุณสมบัติjava.util.Dateไว้ ตรวจสอบให้แน่ใจว่าได้เริ่มต้นด้วยDate ใหม่ ()เสมอ

สำหรับฟิลด์อัพเดตล่าสุดคุณสามารถใช้คุณสมบัติ Timestamp คุณต้องแม็พกับ @Version ด้วยหมายเหตุประกอบนี้คุณสมบัติจะได้รับการอัพเดตโดยอัตโนมัติโดยไฮเบอร์เนต ระวังว่าไฮเบอร์เนตจะใช้การล็อคในแง่ดี (มันเป็นเรื่องดี)


2
การใช้คอลัมน์ประทับเวลาสำหรับการล็อคในแง่ดีเป็นความคิดที่ไม่ดี ใช้คอลัมน์รุ่นจำนวนเต็มเสมอ เหตุผลความถูกต้อง 2 JVM อาจแตกต่างกันในเวลาและอาจไม่มีความแม่นยำมิลลิวินาที หากคุณใช้โหมดไฮเบอร์เนตแทนการประทับเวลานั่นหมายถึงการเลือกเพิ่มเติมจากฐานข้อมูล เพียงแค่ใช้หมายเลขรุ่น
sethu

2

เพียงเพื่อเสริมสร้าง: java.util.Calenderไม่ได้สำหรับ Timestamps java.util.Dateมีอยู่ช่วงเวลาหนึ่งผู้ไม่เชื่อเรื่องพระเจ้าในเขตต่างๆเช่นเขตเวลา ฐานข้อมูลส่วนใหญ่เก็บสิ่งต่าง ๆ ในลักษณะนี้ (แม้ว่าจะไม่ปรากฏก็ตามนี่คือการตั้งค่าเขตเวลาในซอฟต์แวร์ไคลเอนต์และข้อมูลดี)


1

เป็นชนิดข้อมูลใน JAVA ฉันขอแนะนำให้ใช้ java.util.Date ฉันพบปัญหาเกี่ยวกับเขตเวลาที่น่ารังเกียจเมื่อใช้ปฏิทิน ดูกระทู้นี้

สำหรับการตั้งค่าการประทับเวลาฉันขอแนะนำให้ใช้วิธี AOP หรือคุณสามารถใช้ Triggers บนโต๊ะ (อันที่จริงนี่เป็นสิ่งเดียวที่ฉันพบว่าการใช้ทริกเกอร์ยอมรับได้)


1

คุณอาจพิจารณาจัดเก็บเวลาเป็น DateTime และใน UTC ฉันมักจะใช้ DateTime แทน Timestamp เนื่องจากข้อเท็จจริงที่ว่า MySql แปลงวันที่เป็น UTC และกลับสู่เวลาท้องถิ่นเมื่อจัดเก็บและดึงข้อมูล ฉันต้องการเก็บตรรกะชนิดใดชนิดหนึ่งไว้ในที่เดียว (ชั้นธุรกิจ) ฉันแน่ใจว่ามีสถานการณ์อื่น ๆ ที่ควรใช้การประทับเวลา


1

เรามีสถานการณ์ที่คล้ายกัน เราใช้ Mysql 5.7

CREATE TABLE my_table (
        ...
      updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
    );

สิ่งนี้ใช้ได้สำหรับเรา


นอกจากนี้ยังทำงานในกรณีที่ข้อมูลถูกแก้ไขโดยแบบสอบถาม SQL โดยตรงในฐานข้อมูล @PrePersistและ@PrePersistไม่ครอบคลุมกรณีดังกล่าว
pidabrow

1

หากเราใช้@Transactionalในวิธีการของเรา @CreateTimestamp และ @UpdateTimestamp จะบันทึกค่าใน DB แต่จะคืนค่า Null หลังจากใช้ save (... )

ในสถานการณ์นี้การใช้ saveAndFlush (... ) ทำเคล็ดลับ


0

ฉันคิดว่ามันเป็น neater ไม่ทำเช่นนี้ในรหัส Java คุณสามารถกำหนดค่าเริ่มต้นคอลัมน์ในคำนิยามตาราง MySql ป้อนคำอธิบายรูปภาพที่นี่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.