Anotasi Versi Hebat di JPA

Pendahuluan


Jadi mari kita mulai! Apa yang dimaksud dengan anotasi Versi di JPA?

Singkatnya, dia bertanggung jawab untuk memblokir di JPA. Anotasi ini memecahkan salah satu masalah yang mungkin timbul sebagai akibat dari transaksi paralel.

Masalah apa yang bisa muncul?


  1. Pembaruan yang hilang dapat terjadi dalam situasi di mana dua transaksi yang berjalan secara paralel mencoba memperbarui data yang sama.
  2. Pembacaan kotor terjadi ketika transaksi melihat perubahan yang belum disimpan oleh transaksi lain. Dalam kasus seperti itu, masalah dapat terjadi karena kemunduran transaksi kedua, tetapi data telah dibaca terlebih dahulu.
  3. Pembacaan non-berulang terjadi ketika transaksi pertama telah menerima data, dan transaksi kedua telah mengubahnya dan berhasil melakukan itu sampai akhir transaksi pertama. Dengan kata lain, ketika dalam transaksi yang sama permintaan yang sama untuk mendapatkan, misalnya, seluruh tabel, mengembalikan hasil yang berbeda.
  4. Pembacaan hantu adalah masalah yang mirip dengan bacaan berulang, dengan pengecualian bahwa jumlah baris yang berbeda dikembalikan.

Secara singkat tentang keputusan mereka


  1. READ UNCOMMITED - diselesaikan menggunakan anotasi Versi di JPA (ini persis artikel)
  2. READ COMMITED - memungkinkan hanya membaca perubahan yang dilakukan
  3. BACA YANG BERULANG - sedikit lebih rumit di sini. Transaksi kami "tidak melihat" perubahan pada data yang sebelumnya dibaca, dan transaksi lainnya tidak dapat mengubah data yang masuk ke dalam transaksi kami.
  4. SERIALIZABLE - eksekusi transaksi berurutan

Setiap paragraf berikutnya mencakup semua yang sebelumnya, dengan kata lain, itu dapat menggantikan solusi yang ditunjukkan sebelumnya. Dengan demikian, SERIALIZABLE memiliki tingkat isolasi tertinggi, dan READ UNCOMMITED memiliki tingkat isolasi terendah.

Versi


Versi memecahkan masalah dengan pembaruan yang hilang . Bagaimana tepatnya, sekarang kita akan lihat.

Sebelum beralih ke kode, ada baiknya menyebutkan bahwa ada dua jenis kunci: optimis dan pesimistis . Perbedaannya adalah bahwa yang pertama dipandu oleh situasi di mana banyak transaksi mencoba mengubah satu bidang pada saat yang sama, jarang terjadi, sementara yang lain dipandu oleh situasi yang berlawanan. Sesuai dengan ini, ada perbedaan dalam logika eksekusi mereka.

Dalam kunci optimis , ketika melakukan ke database, nilai bidang yang ditandai sebagai versi dibandingkan pada saat data diterima dan saat ini. Jika sudah berubah, yaitu, beberapa transaksi lain ada di depan kami dan berhasil mengubah data, maka dalam hal ini transaksi kami membuat kesalahan, dan Anda harus memulai kembali.

Saat menggunakan kunci optimis , tingkat daya saing yang lebih tinggi dipastikan ketika mengakses database, tetapi dalam hal ini Anda harus mengulangi transaksi yang tidak punya waktu untuk melakukan perubahan sebelum yang lain.

Dalam yang pesimistis , kunci diberlakukan segera sebelum modifikasi data yang diduga pada semua lini yang diduga mempengaruhi modifikasi tersebut.

Dan ketika menggunakan kunci pesimistis , tidak adanya kontradiksi selama pelaksanaan transaksi dijamin, karena menempatkan sisanya ke mode siaga (tetapi butuh waktu), akibatnya, menurunkan tingkat kompetisi.

LockModeType atau cara mengatur kunci


Kunci dapat diatur dengan memanggil metode tampilan EntityManager.

entityManager.lock(myObject, LockModeType.OPTIMISTIC); 

LockModeType mendefinisikan strategi penguncian.

LockModeType dapat terdiri dari 6 jenis (2 di antaranya optimis , dan 3 tipe pesimistis ):

  1. NONE - tidak ada kunci
  2. OPTIMIS
  3. OPTIMISTIC_FORCE_INCREMENT
  4. PESSIMISTIC_READ
  5. PESSIMISTIC_WRITE
  6. PESSIMISTIC_FORCE_INCREMENT

Buat Entitas kami
 import lombok.Getter; import lombok.Setter; import javax.persistence.*; @EntityListeners(OperationListenerForMyEntity.class) @Entity public class MyEntity{ @Version private long version; @Id @GeneratedValue @Getter @Setter private Integer id; @Getter @Setter private String value; @Override public String toString() { return "MyEntity{" + "id=" + id + ", version=" + version + ", value='" + value + '\'' + '}'; } } 


Mari kita buat kelas di mana semua metode Callback akan diimplementasikan.
 import javax.persistence.*; public class OperationListenerForMyEntity { @PostLoad public void postLoad(MyEntity obj) { System.out.println("Loaded operation: " + obj); } @PrePersist public void prePersist(MyEntity obj) { System.out.println("Pre-Persistiting operation: " + obj); } @PostPersist public void postPersist(MyEntity obj) { System.out.println("Post-Persist operation: " + obj); } @PreRemove public void preRemove(MyEntity obj) { System.out.println("Pre-Removing operation: " + obj); } @PostRemove public void postRemove(MyEntity obj) { System.out.println("Post-Remove operation: " + obj); } @PreUpdate public void preUpdate(MyEntity obj) { System.out.println("Pre-Updating operation: " + obj); } @PostUpdate public void postUpdate(MyEntity obj) { System.out.println("Post-Update operation: " + obj); } } 


Main.java
 import javax.persistence.*; import java.util.concurrent.*; //        ,   . public class Main { //  , ..  EntityManagerFactory  ,     . private static EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("ru.easyjava.data.jpa.hibernate"); public static void main(String[] args) { //  10 (  ,       ). ExecutorService es = Executors.newFixedThreadPool(10); try { //  persistFill()   - . persistFill(); for(int i=0; i<10; i++){ int finalI = i; es.execute(() -> { //      updateEntity(finalI) ,  java       .    java -  ,      id,       , id    ,       (  ,      persistFill(),  id       500). updateEntity(finalI); }); } es.shutdown(); try { es.awaitTermination(10, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } } finally { entityManagerFactory.close(); } } //         . private static void updateEntity(int index) { //  EntityManager  ,     ,    . EntityManager em = entityManagerFactory.createEntityManager(); MyEntity myEntity = null; try { em.getTransaction().begin(); //        1. myEntity = em.find(MyEntity.class, 1); //   sout,       "" . System.out.println("load = "+index); //       (  LockModeType.*). em.lock(myEntity, LockModeType.OPTIMISTIC); //   Value,  ,        . myEntity.setValue("WoW_" + index); em.getTransaction().commit(); em.close(); System.out.println("--Greeter updated : " + myEntity +" __--__ "+ index); }catch(RollbackException ex){ System.out.println(", =" + myEntity); } } public static void persistFill() { MyEntity myEntity = new MyEntity(); myEntity.setValue("JPA"); EntityManager em = entityManagerFactory.createEntityManager(); em.getTransaction().begin(); em.persist(myEntity); em.getTransaction().commit(); em.close(); } } 


Pertama kali dijalankan dengan metode updateEntity yang dikomentari
 Pre-Persistiting operation: MyEntity{id=null, version=0, value='JPA'} Post-Persist operation: MyEntity{id=531, version=0, value='JPA'}  .  id   find   . 


LockModeType.OPTIMISTIC

Ini adalah blok optimis , well, ini sangat logis. Seperti yang saya tulis di atas, nilai bidang versi dibandingkan, jika berbeda, kesalahan dilemparkan. Lihat ini.

Hasil:
 Loaded operation: MyEntity{id=531, version=0, value='JPA'} load = 3 Loaded operation: MyEntity{id=531, version=0, value='JPA'} load = 2 Pre-Updating operation: MyEntity{id=531, version=0, value='WoW_2'} Pre-Updating operation: MyEntity{id=531, version=0, value='WoW_3'} Loaded operation: MyEntity{id=531, version=0, value='JPA'} load = 9 Pre-Updating operation: MyEntity{id=531, version=0, value='WoW_9'} Loaded operation: MyEntity{id=531, version=0, value='JPA'} load = 1 Pre-Updating operation: MyEntity{id=531, version=0, value='WoW_1'} Post-Update operation: MyEntity{id=531, version=1, value='WoW_1'} --Greeter updated : MyEntity{id=531, version=1, value='WoW_1'} __--__ 1 , =MyEntity{id=531, version=0, value='WoW_2'} , =MyEntity{id=531, version=0, value='WoW_3'} Loaded operation: MyEntity{id=531, version=1, value='WoW_1'} load = 4 Pre-Updating operation: MyEntity{id=531, version=1, value='WoW_4'} Post-Update operation: MyEntity{id=531, version=2, value='WoW_4'} --Greeter updated : MyEntity{id=531, version=2, value='WoW_4'} __--__ 4 , =MyEntity{id=531, version=0, value='WoW_9'} Loaded operation: MyEntity{id=531, version=2, value='WoW_4'} load = 0 Pre-Updating operation: MyEntity{id=531, version=2, value='WoW_0'} Post-Update operation: MyEntity{id=531, version=3, value='WoW_0'} --Greeter updated : MyEntity{id=531, version=3, value='WoW_0'} __--__ 0 Loaded operation: MyEntity{id=531, version=3, value='WoW_0'} load = 6 Pre-Updating operation: MyEntity{id=531, version=3, value='WoW_6'} Post-Update operation: MyEntity{id=531, version=4, value='WoW_6'} Loaded operation: MyEntity{id=531, version=4, value='WoW_6'} load = 5 Pre-Updating operation: MyEntity{id=531, version=4, value='WoW_5'} Post-Update operation: MyEntity{id=531, version=5, value='WoW_5'} --Greeter updated : MyEntity{id=531, version=4, value='WoW_6'} __--__ 6 --Greeter updated : MyEntity{id=531, version=5, value='WoW_5'} __--__ 5 Loaded operation: MyEntity{id=531, version=5, value='WoW_5'} load = 7 Pre-Updating operation: MyEntity{id=531, version=5, value='WoW_7'} Post-Update operation: MyEntity{id=531, version=6, value='WoW_7'} Loaded operation: MyEntity{id=531, version=5, value='WoW_5'} load = 8 Pre-Updating operation: MyEntity{id=531, version=5, value='WoW_8'} --Greeter updated : MyEntity{id=531, version=6, value='WoW_7'} __--__ 7 , =MyEntity{id=531, version=5, value='WoW_8'} 


Pengamatan: Seperti yang Anda lihat dari hasil, aliran 3, 2, 9, dan 1 adalah yang pertama kali dimuat, dan metode panggilan balik Pra-Pembaruan dipanggil untuk itu. Utas pertama tempat metode Post-Update dipanggil adalah 1, seperti yang dapat Anda lihat dari hasil, bidang yang ditandai dengan anotasi Versi telah diubah (bertambah 1). Karenanya, semua utas yang tersisa 2, 3, 9 melempar pengecualian. Dan sebagainya. Hasilnya adalah nilai = WoW_7, versi = 6. Memang, Post-Update terakhir di utas 7 dengan versi = 6.

LockModeType.OPTIMISTIC_FORCE_INCREMENT

Ini bekerja sesuai dengan algoritma yang sama dengan LockModeType.OPTIMISTIC, kecuali bahwa setelah komit nilai bidang Versi secara paksa meningkat sebesar 1. Sebagai hasilnya, setelah setiap komit, bidang akan meningkat sebesar 2 (peningkatan yang dapat dilihat di Post-Update + peningkatan paksa) . Pertanyaan Mengapa Jika setelah komit kami ingin "menyulap" data yang sama, dan kami tidak memerlukan transaksi pihak ketiga yang dapat memecah antara komit pertama dan penutupan transaksi kami.

Penting! Jika Anda mencoba mengubah data menjadi sama, maka dalam hal ini metode Pra-Pembaruan dan Pasca-Pembaruan tidak akan dipanggil. Tutup semua transaksi dapat terjadi. Misalnya, beberapa transaksi dibaca bersamaan dengan kami, tetapi karena panggilan ke metode pra dan pasca (pembaruan) memerlukan waktu, transaksi yang mencoba mengubah data (ke yang sama) akan segera dieksekusi. Ini akan menghasilkan kesalahan dari sisa transaksi.

LockModeType.PESSIMISTIC_READ, LockModeType.PESSIMISTIC_WRITE dan LockModeType.PESSIMISTIC_FORCE_INCREMENT

Karena pekerjaan dari jenis kunci yang tersisa terlihat serupa, maka saya akan menulis semuanya sekaligus dan mempertimbangkan hasilnya hanya oleh PESSIMISTIC_READ.

LockModeType.PESSIMISTIC_READ - kunci baca pesimistis .
LockModeType.PESSIMISTIC_WRITE - kunci tulis pesimis (dan baca).
LockModeType.PESSIMISTIC_FORCE_INCREMENT - kunci tulis pesimis (dan baca) dengan peningkatan paksa bidang Versi.

Sebagai akibat dari penguncian tersebut, penantian yang lama akan terjadi, yang pada gilirannya dapat menyebabkan kesalahan.

Hasil pada LockModeType.PESSIMISTIC_READ (tidak disajikan secara keseluruhan):
 load = 0 Pre-Updating operation: MyEntity{id=549, version=5, value='WoW_0'} Post-Update operation: MyEntity{id=549, version=6, value='WoW_0'} Loaded operation: MyEntity{id=549, version=6, value='WoW_0'} load = 8 Pre-Updating operation: MyEntity{id=549, version=6, value='WoW_8'} Loaded operation: MyEntity{id=549, version=6, value='WoW_0'} load = 4 Pre-Updating operation: MyEntity{id=549, version=6, value='WoW_4'} ... ERROR: :   :  22760    ExclusiveLock  " (0,66)  287733   271341";   20876.  20876    ShareLock  " 8812";   22760. 


Akibatnya, utas 4 dan 8 memblokir satu sama lain, yang menyebabkan konflik yang tidak dapat diselesaikan. Sebelum ini, tidak ada yang mengganggu utas 0. Situasi serupa dengan semua utas hingga 0.

Source: https://habr.com/ru/post/id434836/


All Articles