Java - JPA - Anotasi @Version

118

Bagaimana cara @Versionkerja anotasi di JPA?

Saya menemukan berbagai jawaban yang ekstraknya adalah sebagai berikut:

JPA menggunakan kolom versi di entitas Anda untuk mendeteksi modifikasi serentak ke data datastore yang sama. Ketika runtime JPA mendeteksi upaya untuk mengubah rekaman yang sama secara bersamaan, itu membuat pengecualian ke transaksi yang mencoba untuk melakukan terakhir.

Tapi saya masih belum yakin bagaimana cara kerjanya.


Juga seperti dari baris berikut:

Anda harus menganggap bidang versi tidak dapat diubah. Mengubah nilai bidang memiliki hasil yang tidak ditentukan.

Apakah ini berarti bahwa kita harus mendeklarasikan field versi kita sebagai final?

Yatendra Goel
sumber
1
Semua hal ini adalah cek / update versi di setiap query update: UPDATE myentity SET mycolumn = 'new value', version = version + 1 WHERE version = [old.version]. Jika seseorang memperbarui catatan, old.versiontidak akan lagi cocok dengan yang ada di DB dan klausul where akan mencegah pembaruan terjadi. 'Baris yang diperbarui' akan menjadi 0, yang dapat dideteksi JPA untuk menyimpulkan bahwa modifikasi bersamaan terjadi.
Stijn de Witt

Jawaban:

189

Tapi tetap saja saya tidak yakin bagaimana cara kerjanya?

Misalkan sebuah entitas MyEntitymemiliki versionproperti beranotasi :

@Entity
public class MyEntity implements Serializable {    

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @Version
    private Long version;

    //...
}

Saat diperbarui, bidang yang dianotasi @Versionakan bertambah dan ditambahkan ke WHEREklausa, seperti ini:

UPDATE MYENTITY SET ..., VERSION = VERSION + 1 WHERE ((ID = ?) AND (VERSION = ?))

Jika WHEREklausa gagal untuk mencocokkan rekaman (karena entitas yang sama telah diperbarui oleh utas lain), penyedia persistensi akan melempar OptimisticLockException.

Apakah itu berarti kita harus mendeklarasikan field versi kita sebagai final

Tidak, tetapi Anda dapat mempertimbangkan untuk membuat setter dilindungi karena Anda tidak seharusnya menyebutnya.

Pascal Thivent
sumber
5
Saya mendapat pengecualian null-pointer dengan potongan kode ini, karena Long diinisialisasi sebagai null, mungkin harus diinisialisasi secara eksplisit dengan 0L.
Markus
2
Jangan mengandalkan auto-unboxing longatau hanya memanggilnya longValue(). Anda membutuhkan nullpemeriksaan eksplisit . Saya tidak akan melakukan inisialisasi secara eksplisit karena kolom ini seharusnya dikelola oleh penyedia ORM. Saya pikir ini akan diatur untuk 0Lpertama kali dimasukkan ke dalam DB, jadi jika diatur ke nullini memberitahu Anda bahwa catatan ini belum disimpan.
Stijn de Witt
Apakah ini akan mencegah pembuatan entitas (dicoba) dibuat lebih dari sekali? Maksud saya, misalkan pesan topik JMS memicu pembuatan entitas dan ada beberapa contoh aplikasi yang mendengarkan pesan tersebut. Saya hanya ingin menghindari kesalahan pelanggaran kendala unik ..
Manu
Maaf, saya menyadari bahwa ini adalah utas lama, tetapi apakah @Version juga mempertimbangkan cache kotor di lingkungan berkerumun?
Shawn Eion Smith
3
Jika menggunakan Spring Data JPA, mungkin lebih baik menggunakan pembungkus Longdaripada primitif longuntuk @Versionbidang tersebut, karena SimpleJpaRepository.save(entity)kemungkinan akan memperlakukan bidang versi null sebagai indikator bahwa entitas tersebut baru. Jika entitas tersebut baru (seperti yang ditunjukkan oleh versi menjadi null), Spring akan memanggil em.persist(entity). Tetapi jika versi memiliki nilai, maka itu akan memanggil em.merge(entity). Ini juga alasan mengapa bidang versi yang dideklarasikan sebagai jenis pembungkus harus dibiarkan tidak diinisialisasi.
rdguam
33

Meskipun jawaban @Pascal benar-benar valid, dari pengalaman saya, saya menemukan kode di bawah ini membantu untuk mencapai penguncian yang optimis:

@Entity
public class MyEntity implements Serializable {    
    // ...

    @Version
    @Column(name = "optlock", columnDefinition = "integer DEFAULT 0", nullable = false)
    private long version = 0L;

    // ...
}

Mengapa? Karena:

  1. Penguncian yang optimal tidak akan berfungsi jika bidang yang diberi anotasi @Versiondisetel ke tidak sengaja null.
  2. Karena bidang khusus ini belum tentu merupakan versi bisnis dari objek, untuk menghindari penyesatan, saya lebih suka menamai bidang tersebut dengan sesuatu seperti optlockdaripada version.

Titik pertama tidak masalah jika menggunakan aplikasi hanya JPA untuk memasukkan data ke dalam database, sebagai vendor JPA akan menegakkan 0untuk @versionbidang pada waktu penciptaan. Tetapi hampir selalu pernyataan SQL biasa juga digunakan (setidaknya selama pengujian unit dan integrasi).

G. Demecki
sumber
Saya menyebutnya u_lmod(pengguna terakhir diubah) di mana pengguna dapat menjadi manusia atau didefinisikan (otomatis) proses / entitas / aplikasi (jadi menyimpan juga u_lmod_idbisa masuk akal). (Menurut saya, atribut-meta-bisnis yang sangat mendasar). Ini biasanya menyimpan nilainya sebagai DATE (TIME) (artinya info tentang tahun ... milidetik) dalam UTC (jika "lokasi" zona waktu dari editor penting untuk disimpan kemudian dengan zona waktu). juga sangat berguna untuk skenario sinkronisasi DWH.
Andreas Dietrich
7
Saya pikir bigint bukannya integer harus digunakan dalam tipe db agar sesuai dengan tipe java yang panjang.
Dmitry
Poin bagus Dmitry, terima kasih, meskipun dalam hal versi IMHO itu tidak terlalu penting.
G. Demecki
9

Setiap kali entitas diperbarui dalam database, bidang versi akan bertambah satu. Setiap operasi yang memperbarui entitas dalam database akan ditambahkan WHERE version = VERSION_THAT_WAS_LOADED_FROM_DATABASEke kuerinya.

Dalam memeriksa baris yang terpengaruh dari operasi Anda, kerangka kerja jpa dapat memastikan tidak ada modifikasi bersamaan antara memuat dan mempertahankan entitas Anda karena kueri tidak akan menemukan entitas Anda dalam database ketika nomor versinya telah ditingkatkan antara memuat dan bertahan.

stefanglase
sumber
Tidak melalui JPAUpdateQuery itu tidak. Jadi "Setiap operasi yang memperbarui entitas dalam database akan menambahkan WHERE version = VERSION_THAT_WAS_LOADED_FROM_DATABASE ke kueri" tidak benar.
Pedro Borges
2

Versi digunakan untuk memastikan bahwa hanya satu pembaruan dalam satu waktu. Penyedia JPA akan memeriksa versinya, jika versi yang diharapkan sudah meningkat maka orang lain sudah memperbarui entitas sehingga pengecualian akan dilempar.

Jadi memperbarui nilai entitas akan lebih aman, lebih optimis.

Jika nilai sering berubah, maka Anda mungkin mempertimbangkan untuk tidak menggunakan kolom versi. Sebagai contoh "entitas yang memiliki kolom tandingan, yang akan meningkat setiap kali halaman web diakses"

uudashr
sumber
0

Hanya menambahkan sedikit info lagi.

JPA mengelola versi di bawah tenda untuk Anda, namun JPA tidak melakukannya saat Anda memperbarui data melalui JPAUpdateClause, dalam kasus seperti itu Anda perlu menambahkan kenaikan versi secara manual ke kueri.

Hal yang sama dapat dikatakan tentang memperbarui melalui JPQL, yaitu bukan perubahan sederhana ke entitas, tetapi perintah pembaruan ke database meskipun itu dilakukan dengan hibernate

Pedro

Pedro Borges
sumber
3
apa yang JPAUpdateClause ?
Karl Richter
Pertanyaan bagus, jawaban sederhananya adalah: contoh buruk :) JPAUpdateClause adalah QueryDSL spesifik, pikirkan menjalankan pembaruan dengan JPQL sebagai lawan hanya mempengaruhi status entitas dalam kode.
Pedro Borges