Hibernate dan database lock 1. Mengapa menggunakan kunci?
Untuk mengetahui mengapa mekanisme kunci ada, terlebih dahulu Anda harus memahami konsep transaksi.
Transaksi adalah serangkaian operasi terkait pada database, dan harus memiliki karakteristik asam:
RDBMS database relasional kami yang umum digunakan mengimplementasikan karakteristik transaksi ini. Di antara mereka, atomisitas,
Konsistensi dan kegigihan dijamin dengan logging. Isolasi dicapai oleh mekanisme penguncian yang kita khawatirkan hari ini, itulah sebabnya kita membutuhkan mekanisme penguncian.
Jika tidak ada kunci dan tidak ada kontrol atas isolasi, konsekuensi apa yang mungkin disebabkan?
Mari kita lihat contoh Hibernate. Dua utas masing -masing memulai dua operasi transaksi. Baris data yang sama dalam tabel tb_account adalah col_id = 1.
paket com.cdai.orm.hibernate.annotation; impor java.io.serializable; impor javax.persistence.column; impor javax.persistence.entity; impor javax.persistence.id; impor javax.persistence.table; @Entity @table (name = "tb_account") Kelas publik menerapkan serializable {private static final long serialversionuid = 5018821760412231859l; @ID @Column (name = "col_id") Private Long ID; @Column (name = "col_balance") keseimbangan panjang pribadi; akun publik () {} akun publik (ID panjang, saldo panjang) {this.id = id; ini. Balance = Balance; } public long getId () {return id; } public void setId (Long ID) {this.id = id; } public long getalance () {return balance; } public void setBalance (Balance panjang) {this.balance = balance; } @Override Public String ToString () {return "Account [id =" + id + ", balance =" + balance + "]"; }} paket com.cdai.orm.hibernate.transaction; impor org.hibernate.Session; impor org.hibernate.SessionFactory; impor org.hibernate.transaction; impor org.hibernate.cfg.annotationConfiguration; impor com.cda.orm.hibernate.annotation.account; Kelas Publik DirtyRead {public static void main (string [] args) {final sessionFactory sessionFactory = new annotationConfiguration (). addFile ("hibernate/hibernate.cfg.xml"). configure (). addPackage ("com.cdai.orm.hibernate.annotation"). AddannotatedClass (Account.class). BuildsessionFactory (); Thread t1 = utas baru () {@Override public void run () {session session1 = sessionFactory.opensession (); Transaksi tx1 = null; coba {tx1 = session1.begintransaction (); System.out.println ("T1 - Begin Trasaction"); Thread.sleep (500); Akun akun = (akun) session1.get (Account.class, new long (1)); System.out.println ("t1 - balance =" + Account.getalance ()); Thread.sleep (500); Account.setBalance (Account.getBalance () + 100); System.out.println ("T1 - Ubah Saldo:" + Account.getBalance ()); tx1.commit (); System.out.println ("T1 - Commit Transaction"); Thread.sleep (500); } catch (Exception e) {E.PrintStackTrace (); if (tx1! = null) tx1.rollback (); } akhirnya {session1.close (); }}}}; // 3.Run transaksi 2 utas t2 = utas baru () {@Override public void run () {session session2 = sessionFactory.opensession (); Transaksi tx2 = null; coba {tx2 = session2.begintransaction (); System.out.println ("T2 - Begin Trasaction"); Thread.sleep (500); Akun akun = (akun) session2.get (akun.class, baru long (1)); System.out.println ("T2 - Balance =" + Account.getBalance ()); Thread.sleep (500); Account.setBalance (Account.getBalance () - 100); System.out.println ("T2 - Ubah Saldo:" + Account.getBalance ()); tx2.commit (); System.out.println ("T2 - Commit Transaction"); Thread.sleep (500); } catch (Exception e) {E.PrintStackTrace (); if (tx2! = null) tx2.rollback (); } akhirnya {session2.close (); }}}}; t1.start (); t2.start (); while (t1.isalive () || t2.isalive ()) {coba {thread.sleep (2000l); } catch (InterruptedException e) {}} System.out.println ("Baik T1 dan T2 sudah mati."); sessionfactory.close (); }} Transaksi 1 mengurangi Col_Balance hingga 100, sedangkan Transaksi 2 menguranginya dengan 100, hasil akhirnya mungkin 0 atau 200, dan pembaruan transaksi 1 atau 2 dapat hilang. Output log juga mengkonfirmasi ini, transaksi 1 dan 2
Log cross-print.
T1 - Begin trasactiont2 - Mulai trasactionhibernate: pilih Account0_.col_id sebagai col1_0_0_, akun0_.col_balance sebagai col2_0_0_ dari TB_Account Account0_ di mana col1_.col_id =? Hibernate: Select Account0_.col_id sebagai col1_.col_id = Hibernate: Account0_.col_id sebagai COL1_0_0_.0.cal2. TB_ACCOUNT ACCOUND0_ WHERE ACCOUND0_.COL_ID =? T1 - Balance = 100T2 - Balance = 100T2 - Ubah Saldo: 0T1 - Ubah Saldo: 200Hibernate: Perbarui TB_ACCOUNT SET COL_BALANCE =? di mana col_id =? hibernate: perbarui tb_account set col_balance =? di mana col_id =? T1 - commit transactiont2 - commit transactionboth t1 dan t2 sudah mati.
Dapat dilihat bahwa isolasi adalah masalah yang perlu dipertimbangkan dengan cermat dan perlu untuk memahami kunci.
2. Ada berapa jenis kunci?
Yang umum adalah kunci bersama, memperbarui kunci dan kunci eksklusif.
1. Kunci Bersama: Digunakan untuk Membaca Operasi Data, memungkinkan transaksi lain dibaca secara bersamaan. Saat transaksi menjalankan pernyataan pilih,
Database secara otomatis memberikan kunci bersama ke transaksi untuk mengunci data yang dibaca.
2. Kunci Eksklusif: Digunakan untuk memodifikasi data, transaksi lain tidak dapat dibaca atau dimodifikasi. Saat transaksi mengeksekusi insert,
Saat pembaruan dan hapus diperbarui, database akan dialokasikan secara otomatis.
3. Pembaruan Kunci: Digunakan untuk menghindari kebuntuan yang disebabkan oleh kunci bersama selama operasi pembaruan, seperti transaksi 1 dan 2 memegang kunci bersama pada saat yang sama dan menunggu untuk mendapatkan kunci eksklusif. Saat melakukan pembaruan, transaksi pertama kali memperoleh kunci pembaruan dan kemudian meningkatkan kunci pembaruan ke kunci eksklusif, sehingga menghindari kebuntuan.
Selain itu, semua kunci ini dapat diterapkan pada objek yang berbeda dalam database, yaitu, kunci ini dapat memiliki granularitas yang berbeda.
Seperti kunci tingkat basis data, kunci tingkat meja, kunci tingkat halaman, kunci tingkat kunci dan kunci tingkat baris.
Jadi ada banyak jenis kunci. Terlalu sulit untuk sepenuhnya menguasai dan menggunakan begitu banyak kunci secara fleksibel. Kami bukan DBA.
Apa yang harus dilakukan? Untungnya, mekanisme kunci transparan untuk pengguna biasa. Basis data akan secara otomatis menambahkan kunci yang sesuai dan secara otomatis meningkatkan dan menurunkan berbagai kunci pada waktu yang tepat. Ini sangat bijaksana! Yang perlu kita lakukan adalah belajar mengatur tingkat isolasi sesuai dengan kebutuhan bisnis yang berbeda.
3. Bagaimana cara mengatur tingkat isolasi?
Secara umum, sistem database menyediakan empat tingkat isolasi transaksi untuk dipilih pengguna:
1.Serializable: Ketika dua transaksi memanipulasi data yang sama secara bersamaan, Transaksi 2 hanya dapat berhenti dan menunggu.
2. Bacaan yang Dapat Diulang (berulang): Transaksi 1 dapat melihat data yang baru dimasukkan dari Transaksi 2, dan tidak dapat melihat pembaruan untuk data yang ada.
3. Baca berkomitmen (baca data yang berkomitmen): Transaksi 1 dapat melihat data yang baru dimasukkan dan diperbarui dari transaksi 2.
4. Baca tidak berkomitmen (baca data yang tidak berkomitmen): Transaksi 1 dapat melihat penyisipan dan memperbarui data yang belum dilakukan oleh Transaksi 2.
4. Kunci dalam aplikasi
Ketika database mengadopsi tingkat isolasi Komisi Baca, kunci pesimistis atau kunci optimis dapat digunakan dalam aplikasi.
1. Pesimistic Lock: Asumsikan bahwa data operasi transaksi saat ini pasti akan memiliki akses transaksi lain, jadi secara pesimistis menentukan bahwa kunci eksklusif digunakan dalam aplikasi untuk mengunci sumber daya data. Mendukung formulir berikut di MySQL dan Oracle:
Pilih ... untuk pembaruan
Secara eksplisit biarkan kunci penggunaan kunci eksklusif untuk mengunci catatan kueri. Untuk transaksi lain untuk meminta, memperbarui atau menghapus data yang terkunci ini, mereka harus menunggu sampai transaksi selesai.
Di Hibernate, Anda dapat lulus di LockMode.Upgrade saat memuat untuk mengadopsi kunci pesimistis. Ubah contoh sebelumnya,
Pada metode GET, panggilan transaksi 1 dan 2, parameter LockMode tambahan diteruskan. Seperti yang dapat dilihat dari log, transaksi 1 dan 2
Tidak lagi berjalan silang, Transaction 2 dapat menunggu transaksi 1 untuk menyelesaikan sebelum membaca data, sehingga nilai col_balance akhir benar 100.
paket com.cdai.orm.hibernate.transaction; impor org.hibernate.lockmode; impor org.hibernate.Session; impor org.hibernate.SessionFactory; impor org.hibernate.transaction; impor com.cda.orm.hibernate.annotation.account; impor com.cda.orm.hibernate.annotation.annotationhibernate; Upgradelock kelas publik {@suppresswarnings ("Deprecation") public static void main (string [] args) {sessionfactory finalFactory = annotationhibernate.createSessionFactory (); // Jalankan transaksi 1 utas t1 = utas baru () {@Override public void run () {session session1 = sessionFactory.opensession (); Transaksi tx1 = null; coba {tx1 = session1.begintransaction (); System.out.println ("T1 - Begin Trasaction"); Thread.sleep (500); Akun akun = (akun) session1.get (akun.class, baru long (1), lockmode.upgrade); System.out.println ("t1 - balance =" + Account.getalance ()); Thread.sleep (500); Account.setBalance (Account.getBalance () + 100); System.out.println ("T1 - Ubah Saldo:" + Account.getBalance ()); tx1.commit (); System.out.println ("T1 - Commit Transaction"); Thread.sleep (500); } catch (Exception e) {E.PrintStackTrace (); if (tx1! = null) tx1.rollback (); } akhirnya {session1.close (); }}}; // Jalankan transaksi 2 utas T2 = utas baru () {@Override public void run () {session session2 = sessionFactory.opensession (); Transaksi tx2 = null; coba {tx2 = session2.begintransaction (); System.out.println ("T2 - Begin Trasaction"); Thread.sleep (500); Akun akun = (akun) session2.get (akun.class, baru long (1), lockmode.upgrade); System.out.println ("T2 - Balance =" + Account.getBalance ()); Thread.sleep (500); Account.setBalance (Account.getBalance () - 100); System.out.println ("T2 - Ubah Saldo:" + Account.getBalance ()); tx2.commit (); System.out.println ("T2 - Commit Transaction"); Thread.sleep (500); } catch (Exception e) {E.PrintStackTrace (); if (tx2! = null) tx2.rollback (); } akhirnya {session2.close (); }}}}; t1.start (); t2.start (); while (t1.isalive () || t2.isalive ()) {coba {thread.sleep (2000l); } catch (InterruptedException e) {}} System.out.println ("Baik T1 dan T2 sudah mati."); sessionfactory.close (); }}T1 - Begin trasactionT2 - Begin trasactionhibernate: pilih Account0_.col_id sebagai col1_0_0_, akun0_.col_balance sebagai col2_0_0_ dari akun TB_ACCOUNT0_ dengan (Updlock, Rowlock) di mana Account0_.col_id =? Hibernate: Select Account0_.colock) di mana Account0_.col_id =? Hibernate: Select Account0_.colock) col2_0_0_ dari TB_Account Account0_ dengan (Updlock, Rowlock) Di mana akun0_.col_id =? T2 - Saldo = 100T2 - Ubah Saldo: 0Hibernate: Perbarui tb_account set col_balance =? di mana col_id =? T2 - commit transactiont1 - balance = 0t1 - Ubah saldo: 100hibernate: perbarui tb_account set col_balance =? Di mana col_id =? T1 - Komit transaksi T1 dan T2 mati.
Hibernate mengeksekusi SQL untuk SQLServer 2005:
Salinan kode adalah sebagai berikut:
Pilih akun0_.col_id sebagai col1_0_0_, akun0_.col_balance sebagai col2_0_0_ dari tb_account akun0_ dengan (updlock, rowlock) di mana akun0_.col_id =?
2. Kunci Optimis: Asumsikan bahwa data operasi transaksi saat ini tidak akan diakses secara bersamaan dengan transaksi lain, sehingga tingkat isolasi database sepenuhnya diandalkan pada database untuk secara otomatis mengelola pekerjaan kunci. Mengadopsi kontrol versi dalam aplikasi untuk menghindari masalah konkurensi yang mungkin terjadi pada probabilitas rendah.
Di Hibernate, gunakan anotasi versi untuk menentukan bidang nomor versi.
Ganti objek akun di Dirtylock dengan AccountVersion, dan kode lainnya tetap tidak berubah, dan pengecualian terjadi ketika eksekusi terjadi.
paket com.cdai.orm.hibernate.transaction; impor javax.persistence.column; impor javax.persistence.entity; impor javax.persistence.id; impor javax.persistence.table; impor javax.persistence.version; @Entity @table (name = "tb_account_version") kelas public AccountVersion {@id @column (name = "col_id") private long id; @Column (name = "col_balance") keseimbangan panjang pribadi; @Version @Column (name = "col_version") versi int private; Public AccountVersion () {} Public AccountVersion (ID Panjang, Saldo Panjang) {this.id = id; ini. Balance = Balance; } public long getId () {return id; } public void setId (Long ID) {this.id = id; } public long getalance () {return balance; } public void setBalance (Balance panjang) {this.balance = balance; } public int getVersion () {Versi return; } public void setVersion (versi int) {this.version = versi; }}Log adalah sebagai berikut:
T1 - Begin trasactiont2 - Begin trasactionhibernate: pilih accountver0_.col_id sebagai col1_0_0_, accountver0_.col_balance sebagai col2_0_, accountver0_.col_version sebagai col3_0_0_ dari tb_account_version_version. col1_0_0_, accountver0_.col_balance sebagai col2_0_0_, accountver0_.col_version sebagai col3_0_0_ dari tb_account_version Accountver0_ di mana Accountver0_.col_id =? T1 - Saldo = 1000T2 - Saldo = 1000T1 - Change Balance: 900t2 -T1 - Saldo = 1000T2 - Saldo = 1000T1 - Change Balance: 900t2 -T1 - Saldo = 1000T2 - Saldo = 1000T1 - Change Balance: 900t2 - col_balance =?, col_version =? dimana col_id =? dan col_version =? Hibernate: perbarui tb_account_version col_balance =?, col_version =? dimana col_id =? dan col_version =? T1 - commit transaction2264 [thread -2] error org.hibernate.event.def.AbstractflushingEventListener - tidak dapat menyinkronkan status basis data dengan sessionorg.hibernate.StaleObjectStateException: Row diperbarui atau dihapus oleh transaksi lain yang tidak diselenggarakan: VALUE yang tidak diselenggarakan. [com.cda.orm.hibernate.transaction.accountversion#1] di org.hibernate.persister.entity.abstractentitypersister.check (abstractentityPersister.java:1934) di org.hibernate.persister.entity.abstractentitypersister.update.update (abstracter.persister.persister.abstractitypersister. org.hibernate.persister.entity.AbstractentityPersister.updateorinsert (AbstractentityPersister.java:2478) di org.hibernate.persister.entity.abstractentitypersister.update (abstractentityPersister.java:2805) di org.hibernate.Action.entityUpdateAction.Execute (entityupdateAction.java:114) di org.hibernate.engine.actionqueue.execute (actionqueue.java:268) di org.hibernate.engine.actionue. org.hibernate.engine.actionqueue.executeactions (actionqueue.java:180) di org.hibernate.event.def.abstractflushingeventListener.performexecutions (AbstractFlushingEventListener.java:321) di org.hibernate.event.def.defaultfLusheventListener.onFlush (defaultflusheventListener.java:51) di org.hibernate.impl.SessionImpl.flush (SesiMPL.JAVA:1206) di org.hibernate.impl.SessionImpl.ManREGREDFLAGEDFAUREDLUAGEMAGED :MREADLUREDHUREDLUAGEMUREDFAURDE.1206) di org.hibernate.Impl.SessionImpl.ManREGREDFAUREDLUAGEDMREADE:1206) di org.hibernate.Impl.SessiesImpl.ManREGREDLUAGEDLUSHURED org.hibernate.transaction.jdbctransaction.Commit (jdbctransaction.java:137) di com.cdai.orm.hibernate.transaction.versionlock $ 2.run (versionlock.java:93) Baik T1 dan T2 sudah mati.
Karena kunci optimis sepenuhnya meninggalkan isolasi transaksi ke database untuk kontrol, transaksi 1 dan 2 berjalan silang, transaksi 1 berhasil dilakukan dan col_version diubah menjadi 1. Namun, ketika transaksi 2 berkomitmen, data dengan col_version 0 tidak dapat lagi ditemukan, sehingga pengecualian dilemparkan.
Perbandingan metode kueri hibernasi
Ada tiga metode kueri utama untuk Hibernate:
1.hql (bahasa kueri hibernasi)
Ini sangat mirip dengan SQL, dan mendukung fitur seperti paging, koneksi, pengelompokan, fungsi agregasi dan subquery.
Tetapi HQL berorientasi objek, bukan tabel dalam database relasional. Karena pernyataan kueri berorientasi pada objek domain, menggunakan HQL dapat memperoleh manfaat lintas platform. Hibernate
Ini akan secara otomatis membantu kami menerjemahkan ke dalam pernyataan SQL yang berbeda sesuai dengan database yang berbeda. Ini sangat nyaman dalam aplikasi yang perlu mendukung beberapa database atau migrasi basis data.
Tetapi sementara membuatnya nyaman, karena pernyataan SQL secara otomatis dihasilkan oleh Hibernate, ini tidak kondusif untuk optimasi efisiensi dan debugging pernyataan SQL. Ketika jumlah data besar, mungkin ada masalah efisiensi.
Jika ada masalah, tidak nyaman untuk menyelidiki dan menyelesaikannya.
2.QBC/QBE (kueri berdasarkan kriteria/contoh)
QBC/QBE melakukan kueri dengan merakit kondisi kueri atau objek templat. Ini nyaman dalam aplikasi yang membutuhkan dukungan fleksibel untuk banyak kombinasi gratis kondisi kueri. Masalah yang sama adalah bahwa karena pernyataan kueri dirakit secara bebas, kode untuk membuat pernyataan bisa panjang dan berisi banyak kondisi percabangan, yang sangat tidak nyaman untuk optimasi dan debugging.
3.SQL
Hibernate juga mendukung metode kueri yang secara langsung menjalankan SQL. Metode ini mengorbankan keunggulan hibernate silang-database dan secara manual menulis pernyataan SQL yang mendasari untuk mencapai efisiensi eksekusi terbaik.
Dibandingkan dengan dua metode pertama, optimasi dan debugging lebih nyaman.
Mari kita lihat satu set contoh sederhana.
paket com.cdai.orm.hibernate.query; impor java.util.arrays; impor java.util.list; impor org.hibernate.criteria; impor org.hibernate.query; impor org.hibernate.Session; impor org.hibernate.SessionFactory; impor org.hibernate.cfg.annotationConfiguration; impor org.hibernate.criterion.Criterion; impor org.hibernate.criterion.example; impor org.hibernate.criterion.Expression; impor com.cda.orm.hibernate.annotation.account; Public Class BasicQuery {public static void main (string [] args) {sessionFactory sessionFactory = new annotationConfiguration (). addFile ("hibernate/hibernate.cfg.xml"). configure (). addPackage ("com.cdai.orm.hibernate.annotation"). AddannotatedClass (Account.class). BuildsessionFactory (); Sesi sesi = sessionfactory.opensession (); // 1.HQL kueri kueri = session.createqueery ("dari akun sebagai where a.id =: id"); query.setlong ("id", 1); Daftar hasil = query.list (); untuk (baris objek: hasil) {System.out.println (baris); } // 2.QBC CRITERIA CRITERIA = SESION.CreateCriteria (Account.class); criteria.add (ekspresi.eq ("id", baru long (2))); hasil = criteria.list (); untuk (baris objek: hasil) {System.out.println (baris); } // 3.QBE Contoh Akun = Akun baru (); example.setBalance (100); Hasil = session.createCriteria (Account.class). Tambah (Contoh.Create (contoh)). daftar(); untuk (baris objek: hasil) {System.out.println (baris); } // 4.sql query = session.createSqlQuery ("Pilih Top 10 * dari TB_Account Order oleh Col_id Desc"); hasil = query.list (); untuk (baris objek: hasil) {System.out.println (arrays.tostring ((objek []) baris)); } session.close (); }}Hibernate: Pilih akun0_.col_id sebagai col1_0_, account0_.col_balance sebagai col2_0_ dari TB_ACCOUNT ACCOUND0_ di mana akun0_.col_id =? Account [id = 1, saldo = 100] hibernate: THE hibernate: col1_c dari col1_0_0_, this_col_cole_cole_col_id dari col1_10 this_.col_id =? akun [id = 2, saldo = 100] hibernate: pilih this_.col_id sebagai col1_0_0_, this_.col_balance sebagai col2_0_0_ dari tb_account this_ di mana (this_.col_balance =?) Akun [id = 1, akun = 100] [id = 2, saling = saling = 100] hereCe = 1, ID = 100] [ID = 2, SALANG = 2, SALACE = 1, ID = 1, ID = 2, ID = 2, ID = 2, ID = 2, ID [ID = 2, ID [ID = 2, ID [ID = 2, ID [ID = 2, ID [ID = 2, ID [id = 2, Desc [2, 100] [1, 100]
Dari log, Anda dapat dengan jelas melihat kontrol Hibernate atas pernyataan SQL yang dihasilkan. Metode kueri spesifik untuk dipilih tergantung pada aplikasi spesifik.