Penguncian Optimis di JPA

1. Perkenalan

Dalam hal aplikasi perusahaan, sangat penting untuk mengelola akses bersamaan ke database dengan benar. Ini berarti kami harus dapat menangani banyak transaksi dengan cara yang efektif dan yang terpenting, bukti kesalahan.

Terlebih lagi, kami perlu memastikan bahwa data tetap konsisten antara pembacaan dan pembaruan secara bersamaan.

Untuk mencapai itu kita dapat menggunakan mekanisme penguncian optimis yang disediakan oleh Java Persistence API. Ini mengarah bahwa beberapa pembaruan yang dilakukan pada data yang sama pada saat yang sama tidak mengganggu satu sama lain.

2. Memahami Penguncian Optimis

Untuk menggunakan penguncian optimis, kita perlu memiliki entitas yang menyertakan properti dengan anotasi @Version . Saat menggunakannya, setiap transaksi yang membaca data menyimpan nilai properti versi.

Sebelum transaksi ingin melakukan pembaruan, ia memeriksa kembali properti versi.

Jika nilainya telah berubah sementara itu, OptimisticLockException akan muncul. Jika tidak, transaksi melakukan pembaruan dan menambah properti versi nilai.

3. Penguncian Pesimistis vs. Penguncian Optimis

Senang rasanya mengetahui bahwa berbeda dengan penguncian optimis, JPA memberi kita penguncian pesimistis. Ini adalah mekanisme lain untuk menangani akses data secara bersamaan.

Kami membahas penguncian pesimistis di salah satu artikel kami sebelumnya - Penguncian Pesimistis di JPA. Mari kita cari tahu apa perbedaannya dan bagaimana kita bisa mendapatkan keuntungan dari setiap jenis penguncian.

Seperti yang telah kami katakan sebelumnya, penguncian optimis didasarkan pada pendeteksian perubahan pada entitas dengan memeriksa atribut versinya . Jika terjadi pembaruan bersamaan, OptmisticLockException terjadi. Setelah itu, kami dapat mencoba memperbarui data.

Kita bisa membayangkan bahwa mekanisme ini cocok untuk aplikasi yang melakukan lebih banyak membaca daripada memperbarui atau menghapus. Terlebih lagi, ini berguna dalam situasi di mana entitas harus dilepaskan untuk beberapa waktu dan kunci tidak dapat ditahan.

Sebaliknya, mekanisme penguncian pesimis melibatkan penguncian entitas pada tingkat database.

Setiap transaksi dapat memperoleh kunci data. Selama memegang kunci, tidak ada transaksi yang dapat membaca, menghapus, atau memperbarui data yang dikunci. Kita dapat berasumsi bahwa menggunakan penguncian pesimis dapat mengakibatkan kebuntuan. Namun, ini memastikan integritas data yang lebih besar daripada penguncian yang optimis.

4. Atribut Versi

Atribut versi adalah properti dengan anotasi @Version . Mereka diperlukan untuk mengaktifkan penguncian yang optimis. Mari kita lihat kelas entitas sampel:

@Entity public class Student { @Id private Long id; private String name; private String lastName; @Version private Integer version; // getters and setters }

Ada beberapa aturan yang harus kita ikuti saat mendeklarasikan atribut versi:

  • setiap kelas entitas hanya boleh memiliki satu atribut versi
  • itu harus ditempatkan di tabel utama untuk entitas yang dipetakan ke beberapa tabel
  • jenis atribut versi harus salah satu dari berikut ini: int , Integer , long , Long , short , Short , java.sql.Timestamp

Kita harus tahu bahwa kita dapat mengambil nilai dari atribut versi melalui entitas, tetapi kita tidak boleh memperbarui atau menaikkannya. Hanya penyedia persistensi yang dapat melakukannya, sehingga data tetap konsisten.

Perlu diperhatikan bahwa penyedia persistensi dapat mendukung penguncian yang optimis untuk entitas yang tidak memiliki atribut versi. Namun, ada baiknya untuk selalu menyertakan atribut versi saat bekerja dengan penguncian yang optimis.

Jika kami mencoba mengunci entitas yang tidak berisi atribut tersebut dan penyedia persistensi tidak mendukungnya, ini akan menghasilkan PersitenceException .

5. Mode Kunci

JPA memberi kami dua mode kunci optimis yang berbeda (dan dua alias):

  • OPTIMISTIS - memperoleh kunci baca yang optimis untuk semua entitas yang berisi atribut versi
  • OPTIMISTIC_FORCE_INCREMENT - ini memperoleh kunci optimis yang sama seperti OPTIMISTIC dan juga menambah nilai atribut versi
  • BACA - itu sinonim untuk OPTIMISTIS
  • TULIS - ini sinonim untuk OPTIMISTIC_FORCE_INCREMENT

Kita dapat menemukan semua tipe yang tercantum di atas di kelas LockModeType .

5.1. OPTIMISTIC (READ)

As we already know, OPTIMISTIC and READ lock modes are synonyms. However, JPA specification recommends us to use OPTIMISTIC in new applications.

Whenever we request the OPTIMISTIC lock mode, a persistence provider will prevent our data from dirty reads as well as non-repeatable reads.

Put simply, it should ensure any transaction fails to commit any modification on data that another transaction:

  • has updated or deleted but not committed
  • has updated or deleted successfully in the meantime

5.2. OPTIMISTIC_INCREMENT (WRITE)

The same as previously, OPTIMISTIC_INCREMENT and WRITE are synonyms, but the former is preferable.

OPTIMISTIC_INCREMENT must meet the same conditions as OPTIMISTIC lock mode. Additionally, it increments the value of a version attribute. However, it's not specified whether it should be done immediately or may be put off until commit or flush.

It's worth to know that a persistence provider is allowed to provide OPTIMISTIC_INCREMENT functionality when OPTIMISTIC lock mode is requested.

6. Using Optimistic Locking

We should remember that for versioned entities optimistic locking is available by default. Yet there are several ways of requesting it explicitly.

6.1. Find

To request optimistic locking we can pass the proper LockModeType as an argument to find method of EntityManager:

entityManager.find(Student.class, studentId, LockModeType.OPTIMISTIC);

6.2. Query

Another way to enable locking is using the setLockMode method of Query object:

Query query = entityManager.createQuery("from Student where id = :id"); query.setParameter("id", studentId); query.setLockMode(LockModeType.OPTIMISTIC_INCREMENT); query.getResultList()

6.3. Explicit Locking

We can set a lock by calling EnitityManager's lock method:

Student student = entityManager.find(Student.class, id); entityManager.lock(student, LockModeType.OPTIMISTIC);

6.4. Refresh

We can call the refresh method the same way as the previous method:

Student student = entityManager.find(Student.class, id); entityManager.refresh(student, LockModeType.READ);

6.5. NamedQuery

The last option is to use @NamedQuery with the lockMode property:

@NamedQuery(name="optimisticLock", query="SELECT s FROM Student s WHERE s.id LIKE :id", lockMode = WRITE)

7. OptimisticLockException

When the persistence provider discovers optimistic locking conflicts on entities, it throws OptimisticLockException. We should be aware that due to the exception the active transaction is always marked for rollback.

It's good to know how we can react to OptimisticLockException. Conveniently, this exception contains a reference to the conflicting entity. However, it's not mandatory for the persistence provider to supply it in every situation. There is no guarantee that the object will be available.

There is a recommended way of handling the described exception, though. We should retrieve the entity again by reloading or refreshing. Preferably in a new transaction. After that, we can try to update it once more.

8. Conclusion

In this tutorial, we got familiar with a tool which can help us orchestrate concurrent transactions. Optimistic locking uses version attributes included in entities to control concurrent modifications on them.

Therefore, it ensures that any updates or deletes won't be overwritten or lost silently. Opposite to pessimistic locking, it doesn't lock entities on the database level and consequently, it isn't vulnerable to DB deadlocks.

Kami telah mempelajari bahwa penguncian optimis diaktifkan untuk entitas berversi secara default. Namun, ada beberapa cara untuk memintanya secara eksplisit dengan menggunakan berbagai jenis mode kunci.

Fakta lain yang harus kita ingat adalah bahwa setiap kali ada update yang bertentangan pada entitas, kita harus mengharapkan OptimisticLockException .

Terakhir, kode sumber dari tutorial ini tersedia di GitHub.