Untuk entitas Hibernate tertentu, kami memiliki persyaratan untuk menyimpan waktu pembuatannya dan terakhir kali diperbarui. Bagaimana Anda mendesain ini?
Tipe data apa yang akan Anda gunakan dalam database (dengan asumsi MySQL, mungkin dalam zona waktu yang berbeda dari JVM)? Apakah tipe data akan sadar zona waktu?
Data apa jenis yang akan Anda gunakan di Jawa (
Date
,Calendar
,long
, ...)?Dengan siapa Anda bertanggung jawab untuk mengatur stempel waktu — basis data, kerangka kerja ORM (Hibernate), atau pemrogram aplikasi?
Anotasi apa yang akan Anda gunakan untuk pemetaan (misalnya
@Temporal
)?
Saya tidak hanya mencari solusi yang berfungsi, tetapi untuk solusi yang aman dan dirancang dengan baik.
Anda bisa menggunakan
@CreationTimestamp
dan@UpdateTimestamp
:sumber
TemporalType.DATE
dalam kasus pertama danTemporalType.TIMESTAMP
yang kedua.@CreationTimestamp
dan@UpdateTimestamp
satu membutuhkan beberapa@Column(..., columnDefinition = "timestamp default current_timestamp")
, atau menggunakan@PrePersist
dan@PreUpdate
(yang terakhir juga memastikan klien tidak dapat menetapkan nilai yang berbeda).nullable=false
dari@Column(name = "create_date" , nullable=false)
bekerjaMengambil sumber daya dalam posting ini bersama dengan informasi yang diambil kiri dan kanan dari berbagai sumber, saya datang dengan solusi elegan ini, membuat kelas abstrak berikut
dan minta semua entitas Anda memperluasnya, misalnya:
sumber
not-null property references a null or transient value: package.path.ClassName.created
@Column(name = "updated", nullable = false, insertable = false)
untuk membuatnya berfungsi. Menarik bahwa jawaban ini mendapat begitu banyak upvotes ..1. Jenis kolom basis data apa yang harus Anda gunakan
Pertanyaan pertama Anda adalah:
Di MySQL,
TIMESTAMP
tipe kolom bergeser dari zona waktu lokal driver JDBC ke zona waktu database, tetapi hanya dapat menyimpan cap waktu hingga'2038-01-19 03:14:07.999999
, jadi itu bukan pilihan terbaik untuk masa depan.Jadi, lebih baik gunakan
DATETIME
, yang tidak memiliki batasan batas atas ini. Namun,DATETIME
tidak sadar zona waktu. Jadi, untuk alasan ini, lebih baik menggunakan UTC di sisi basis data dan menggunakanhibernate.jdbc.time_zone
properti Hibernate.2. Jenis properti entitas apa yang harus Anda gunakan
Pertanyaan kedua Anda adalah:
Di sisi Java, Anda dapat menggunakan Java 8
LocalDateTime
. Anda juga dapat menggunakan warisanDate
, tetapi tipe Tanggal / Waktu Java 8 lebih baik karena tidak dapat diubah, dan jangan melakukan zona waktu bergeser ke zona waktu lokal saat mencatatnya.Sekarang, kita juga dapat menjawab pertanyaan ini:
Jika Anda menggunakan
LocalDateTime
ataujava.sql.Timestamp
untuk memetakan properti entitas timestamp, maka Anda tidak perlu menggunakan@Temporal
karena HIbernate sudah tahu bahwa properti ini harus disimpan sebagai JDBC Timestamp.Hanya jika Anda menggunakan
java.util.Date
, Anda perlu menentukan@Temporal
anotasi, seperti ini:Tapi, jauh lebih baik jika Anda memetakannya seperti ini:
Cara menghasilkan nilai kolom audit
Pertanyaan ketiga Anda adalah:
Ada banyak cara untuk mencapai tujuan ini. Anda dapat mengizinkan database untuk melakukan itu ..
Untuk
create_on
kolom, Anda bisa menggunakanDEFAULT
batasan DDL, seperti:Untuk
updated_on
kolom, Anda bisa menggunakan pemicu DB untuk mengatur nilai kolom denganCURRENT_TIMESTAMP()
setiap kali baris yang diberikan dimodifikasi.Atau, gunakan JPA atau Hibernate untuk mengaturnya.
Mari kita asumsikan Anda memiliki tabel database berikut:
Dan, setiap tabel memiliki kolom seperti:
created_by
created_on
updated_by
updated_on
Menggunakan Hibernate
@CreationTimestamp
dan@UpdateTimestamp
anotasiHibernate menawarkan
@CreationTimestamp
dan@UpdateTimestamp
anotasi yang dapat digunakan untuk memetakancreated_on
danupdated_on
kolom.Anda bisa menggunakan
@MappedSuperclass
untuk mendefinisikan kelas dasar yang akan diperluas oleh semua entitas:Dan, semua entitas akan memperpanjang
BaseEntity
, seperti ini:Namun, bahkan jika
createdOn
danupdateOn
properti diatur oleh khusus Hibernate@CreationTimestamp
dan@UpdateTimestamp
anotasi,createdBy
danupdatedBy
mengharuskan mendaftarkan panggilan balik aplikasi, seperti yang diilustrasikan oleh solusi JPA berikut.Menggunakan JPA
@EntityListeners
Anda dapat merangkum properti audit dalam Embeddable:
Dan, buat
AuditListener
untuk mengatur properti audit:Untuk mendaftar
AuditListener
, Anda dapat menggunakan@EntityListeners
anotasi JPA:sumber
datetime
lebihtimestamp
. Anda ingin database Anda mengetahui zona waktu cap waktu Anda. Ini mencegah kesalahan konversi zona waktu.timestsmp
tipe tidak menyimpan informasi zona waktu. Itu hanya melakukan percakapan dari aplikasi TZ ke DB TZ. Pada kenyataannya, Anda ingin menyimpan TZ klien secara terpisah dan melakukan percakapan dalam aplikasi sebelum merender UI.timestamp
selalu dalam UTC. MySQL mengonversiTIMESTAMP
nilai dari zona waktu saat ini ke UTC untuk penyimpanan, dan kembali dari UTC ke zona waktu saat ini untuk pengambilan. Dokumentasi MySQL: Tipe DATE, DATETIME, dan TIMESTAMPAnda juga dapat menggunakan interseptor untuk mengatur nilai
Buat antarmuka yang disebut TimeStamped yang diterapkan entitas Anda
Tentukan pencegat
Dan daftarkan ke pabrik sesi
sumber
Dengan solusi Olivier, selama laporan pembaruan Anda dapat mengalami:
Untuk mengatasi ini, tambahkan updatable = false ke penjelasan @Column atribut "dibuat":
sumber
@Version
. Ketika suatu entitas diatur dua panggilan dilakukan satu untuk menyimpan dan satu lagi untuk memperbarui. Saya menghadapi masalah yang sama karena ini. Setelah saya menambahkannya@Column(updatable = false)
memecahkan masalah saya.Terima kasih semuanya yang telah membantu. Setelah melakukan riset sendiri (saya orang yang mengajukan pertanyaan), inilah yang menurut saya paling masuk akal:
Jenis kolom basis data: jumlah zona waktu-agnostik milidetik sejak tahun 1970 direpresentasikan
decimal(20)
karena 2 ^ 64 memiliki 20 digit dan ruang disk murah; mari kita berterus terang. Juga, saya tidak akan menggunakanDEFAULT CURRENT_TIMESTAMP
, atau memicu. Saya tidak menginginkan keajaiban dalam DB.Java tipe field:
long
.long
Cap waktu Unix didukung dengan baik di berbagai lib , tidak memiliki masalah Y2038, aritmatika stempel waktu cepat dan mudah (terutama operator<
dan operator+
, dengan asumsi tidak ada hari / bulan / tahun yang terlibat dalam perhitungan). Dan, yang paling penting, baik primitiflong
maupunjava.lang.Long
s tidak dapat diubah - secara efektif diteruskan oleh nilai - tidak sepertijava.util.Date
s; Saya akan sangat kesal untuk menemukan sesuatu sepertifoo.getLastUpdate().setTime(System.currentTimeMillis())
ketika men-debug kode orang lain.Kerangka kerja ORM harus bertanggung jawab untuk mengisi data secara otomatis.
Saya belum menguji ini, tetapi hanya melihat dokumen saya berasumsi bahwa
@Temporal
akan melakukan pekerjaan; tidak yakin apakah saya akan menggunakan@Version
untuk tujuan ini.@PrePersist
dan@PreUpdate
merupakan alternatif yang baik untuk mengendalikannya secara manual. Menambahkan itu ke supertype layer (kelas dasar umum) untuk semua entitas, adalah ide yang lucu asalkan Anda benar-benar ingin timestamping untuk semua entitas Anda.sumber
Jika Anda menggunakan Session API, panggilan balik PrePersist dan PreUpdate tidak akan berfungsi sesuai dengan jawaban ini .
Saya menggunakan metode persisten () Hibernate Session dalam kode saya sehingga satu-satunya cara saya bisa membuatnya bekerja adalah dengan kode di bawah ini dan mengikuti posting blog ini (juga diposting dalam jawabannya ).
sumber
updated.clone()
komponen lain dapat memanipulasi keadaan internal (tanggal)Sekarang ada juga penjelasan @CreatedDate dan @LastModifiedDate.
=> https://programmingmitra.blogspot.fr/2017/02/automatic-spring-data-jpa-auditing-saving-CreatedBy-createddate-lastmodifiedby-lastmodifieddate-automatically.html
(Kerangka pegas)
sumber
Kode berikut berhasil untuk saya.
sumber
protected Integer id;
sepertiprotected
di kelas induk pada umumnya, karena saya tidak bisa menggunakannya dalam kasus pengujian saya sebagai.getId()
Pendekatan yang baik adalah memiliki kelas dasar yang sama untuk semua entitas Anda. Di kelas dasar ini, Anda dapat memiliki properti id Anda jika itu umumnya dinamai di semua entitas Anda (desain umum), kreasi Anda dan properti tanggal pembaruan terakhir.
Untuk tanggal pembuatan, Anda cukup menyimpan properti java.util.Date . Pastikan, untuk selalu menginisialisasi dengan Date baru () .
Untuk bidang pembaruan terakhir, Anda dapat menggunakan properti Timestamp, Anda perlu memetakannya dengan @Version. Dengan Anotasi ini, properti akan diperbarui secara otomatis oleh Hibernate. Berhati-hatilah bahwa Hibernate juga akan menerapkan penguncian optimis (itu adalah hal yang baik).
sumber
Hanya untuk memperkuat:
java.util.Calender
bukan untuk Stempel Waktu .java.util.Date
untuk sesaat, agnostik hal-hal regional seperti zona waktu. Sebagian besar database menyimpan hal-hal dengan cara ini (bahkan jika mereka tampaknya tidak melakukannya; ini biasanya merupakan pengaturan zona waktu dalam perangkat lunak klien; datanya baik)sumber
Sebagai tipe data di JAWA saya sangat merekomendasikan untuk menggunakan java.util.Date. Saya mengalami masalah zona waktu yang cukup buruk saat menggunakan Kalender. Lihat Utas ini .
Untuk mengatur cap waktu saya akan merekomendasikan menggunakan pendekatan AOP atau Anda bisa menggunakan Pemicu di atas meja (sebenarnya ini adalah satu-satunya hal yang saya temukan penggunaan pemicu dapat diterima).
sumber
Anda mungkin mempertimbangkan menyimpan waktu sebagai DateTime, dan dalam UTC. Saya biasanya menggunakan DateTime alih-alih Timestamp karena fakta bahwa MySql mengubah tanggal menjadi UTC dan kembali ke waktu lokal ketika menyimpan dan mengambil data. Saya lebih suka menyimpan logika semacam itu di satu tempat (lapisan Bisnis). Saya yakin ada situasi lain di mana menggunakan Timestamp lebih disukai.
sumber
Kami memiliki situasi yang serupa. Kami menggunakan Mysql 5.7.
Ini berhasil untuk kita.
sumber
@PrePersist
dan@PrePersist
jangan menutupi kasus seperti itu.Jika kita menggunakan @Transaksional dalam metode kami, @CreationTimestamp dan @UpdateTimestamp akan menyimpan nilai dalam DB tetapi akan mengembalikan nol setelah menggunakan save (...).
Dalam situasi ini, menggunakan saveAndFlush (...) berhasil
sumber
Saya pikir lebih baik tidak melakukan ini dalam kode Java, Anda cukup mengatur nilai default kolom dalam definisi tabel MySql.
sumber