Java的Hibernate框架數(shù)據(jù)庫(kù)操作中鎖的使用和查詢類型
Hibernate與數(shù)據(jù)庫(kù)鎖
一、為什么要使用鎖?
要想弄清楚鎖機(jī)制存在的原因,首先要了解事務(wù)的概念。
事務(wù)是對(duì)數(shù)據(jù)庫(kù)一系列相關(guān)的操作,它必須具備ACID特征:
- A(原子性):要么全部成功,要么全部撤銷。
- C(一致性):要保持?jǐn)?shù)據(jù)庫(kù)的一致性。
- I(隔離性):不同事務(wù)操作相同數(shù)據(jù)時(shí),要有各自的數(shù)據(jù)空間。
- D(持久性):一旦事務(wù)成功結(jié)束,它對(duì)數(shù)據(jù)庫(kù)所做的更新必須永久保持。
我們常用的關(guān)系型數(shù)據(jù)庫(kù)RDBMS實(shí)現(xiàn)了事務(wù)的這些特性。其中,原子性、
一致性和持久性都是采用日志來(lái)保證的。而隔離性就是由今天我們關(guān)注的
鎖機(jī)制來(lái)實(shí)現(xiàn)的,這就是為什么我們需要鎖機(jī)制。
如果沒(méi)有鎖,對(duì)隔離性不加控制,可能會(huì)造成哪些后果呢?
- 更新丟失:事務(wù)1提交的數(shù)據(jù)被事務(wù)2覆蓋。
- 臟讀:事務(wù)2查詢到了事務(wù)1未提交的數(shù)據(jù)。
- 虛讀:事務(wù)2查詢到了事務(wù)1提交的新建數(shù)據(jù)。
- 不可重復(fù)讀:事務(wù)2查詢到了事務(wù)1提交的更新數(shù)據(jù)。
下面來(lái)看Hibernate的例子,兩個(gè)線程分別開(kāi)啟兩個(gè)事務(wù)操作tb_account表中
的同一行數(shù)據(jù)col_id=1。
package com.cdai.orm.hibernate.annotation; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name = "tb_account") public class Account implements Serializable { private static final long serialVersionUID = 5018821760412231859L; @Id @Column(name = "col_id") private long id; @Column(name = "col_balance") private long balance; public Account() { } public Account(long id, long balance) { this.id = id; this.balance = balance; } public long getId() { return id; } public void setId(long id) { this.id = id; } public long getBalance() { return balance; } public void setBalance(long balance) { this.balance = balance; } @Override public String toString() { return "Account [id=" + id + ", balance=" + balance + "]"; } }
package com.cdai.orm.hibernate.transaction; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.AnnotationConfiguration; import com.cdai.orm.hibernate.annotation.Account; public class 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 = new Thread() { @Override public void run() { Session session1 = sessionFactory.openSession(); Transaction tx1 = null; try { tx1 = session1.beginTransaction(); System.out.println("T1 - Begin trasaction"); Thread.sleep(500); Account account = (Account) session1.get(Account.class, new Long(1)); System.out.println("T1 - balance=" + account.getBalance()); Thread.sleep(500); account.setBalance(account.getBalance() + 100); System.out.println("T1 - Change balance:" + account.getBalance()); tx1.commit(); System.out.println("T1 - Commit transaction"); Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); if (tx1 != null) tx1.rollback(); } finally { session1.close(); } } }; // 3.Run transaction 2 Thread t2 = new Thread() { @Override public void run() { Session session2 = sessionFactory.openSession(); Transaction tx2 = null; try { tx2 = session2.beginTransaction(); System.out.println("T2 - Begin trasaction"); Thread.sleep(500); Account account = (Account) session2.get(Account.class, new Long(1)); System.out.println("T2 - balance=" + account.getBalance()); Thread.sleep(500); account.setBalance(account.getBalance() - 100); System.out.println("T2 - Change balance:" + account.getBalance()); tx2.commit(); System.out.println("T2 - Commit transaction"); Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); if (tx2 != null) tx2.rollback(); } finally { session2.close(); } } }; t1.start(); t2.start(); while (t1.isAlive() || t2.isAlive()) { try { Thread.sleep(2000L); } catch (InterruptedException e) { } } System.out.println("Both T1 and T2 are dead."); sessionFactory.close(); } }
事務(wù)1將col_balance減小100,而事務(wù)2將其減少100,最終結(jié)果可能是0,也
可能是200,事務(wù)1或2的更新可能會(huì)丟失。log輸出也印證了這一點(diǎn),事務(wù)1和2
的log交叉打印。
T1 - Begin trasaction T2 - Begin trasaction Hibernate: select account0_.col_id as col1_0_0_, account0_.col_balance as col2_0_0_ from tb_account account0_ where account0_.col_id=? Hibernate: select account0_.col_id as col1_0_0_, account0_.col_balance as col2_0_0_ from tb_account account0_ where account0_.col_id=? T1 - balance=100 T2 - balance=100 T2 - Change balance:0 T1 - Change balance:200 Hibernate: update tb_account set col_balance=? where col_id=? Hibernate: update tb_account set col_balance=? where col_id=? T1 - Commit transaction T2 - Commit transaction Both T1 and T2 are dead.
由此可見(jiàn),隔離性是一個(gè)需要慎重考慮的問(wèn)題,理解鎖很有必要。
二、有多少種鎖?
常見(jiàn)的有共享鎖、更新鎖和獨(dú)占鎖。
1.共享鎖:用于讀數(shù)據(jù)操作,允許其他事務(wù)同時(shí)讀取。當(dāng)事務(wù)執(zhí)行select語(yǔ)句時(shí),
數(shù)據(jù)庫(kù)自動(dòng)為事務(wù)分配一把共享鎖來(lái)鎖定讀取的數(shù)據(jù)。
2.獨(dú)占鎖:用于修改數(shù)據(jù),其他事務(wù)不能讀取也不能修改。當(dāng)事務(wù)執(zhí)行insert、
update和delete時(shí),數(shù)據(jù)庫(kù)會(huì)自動(dòng)分配。
3.更新鎖:用于避免更新操作時(shí)共享鎖造成的死鎖,比如事務(wù)1和2同時(shí)持有
共享鎖并等待獲得獨(dú)占鎖。當(dāng)執(zhí)行update時(shí),事務(wù)先獲得更新鎖,然后將
更新鎖升級(jí)成獨(dú)占鎖,這樣就避免了死鎖。
此外,這些鎖都可以施加到數(shù)據(jù)庫(kù)中不同的對(duì)象上,即這些鎖可以有不同的粒度。
如數(shù)據(jù)庫(kù)級(jí)鎖、表級(jí)鎖、頁(yè)面級(jí)鎖、鍵級(jí)鎖和行級(jí)鎖。
所以鎖是有很多種的,這么多鎖要想完全掌握靈活使用太難了,我們又不是DBA。
怎么辦?還好,鎖機(jī)制對(duì)于我們一般用戶來(lái)說(shuō)是透明的,數(shù)據(jù)庫(kù)會(huì)自動(dòng)添加合適的
鎖,并在適當(dāng)?shù)臅r(shí)機(jī)自動(dòng)升級(jí)、降級(jí)各種鎖,真是太周到了!我們只需要做的就是
學(xué)會(huì)根據(jù)不同的業(yè)務(wù)需求,設(shè)置好隔離級(jí)別就可以了。
三、怎樣設(shè)置隔離級(jí)別?
一般來(lái)說(shuō),數(shù)據(jù)庫(kù)系統(tǒng)會(huì)提供四種事務(wù)隔離級(jí)別供用戶選擇:
1.Serializable(串行化):當(dāng)兩個(gè)事務(wù)同時(shí)操縱相同數(shù)據(jù)時(shí),事務(wù)2只能停下來(lái)等。
2.Repeatable Read(可重復(fù)讀):事務(wù)1能看到事務(wù)2新插入的數(shù)據(jù),不能看到對(duì)
已有數(shù)據(jù)的更新。
3.Read Commited(讀已提交數(shù)據(jù)):事務(wù)1能看到事務(wù)2新插入和更新的數(shù)據(jù)。
4.Read Uncommited(讀未提交數(shù)據(jù)):事務(wù)1能看到事務(wù)2沒(méi)有提交的插入和更新
數(shù)據(jù)。
四、應(yīng)用程序中的鎖
當(dāng)數(shù)據(jù)庫(kù)采用Read Commited隔離級(jí)別時(shí),可以在應(yīng)用程序中采用悲觀鎖或樂(lè)觀鎖。
1.悲觀鎖:假定當(dāng)前事務(wù)操作的數(shù)據(jù)肯定還會(huì)有其他事務(wù)訪問(wèn),因此悲觀地在應(yīng)用
程序中顯式指定采用獨(dú)占鎖來(lái)鎖定數(shù)據(jù)資源。在MySQL、Oracle中支持以下形式:
select ... for update
顯式地讓select采用獨(dú)占鎖鎖定查詢的記錄,其他事務(wù)要查詢、更新或刪除這些被
鎖定的數(shù)據(jù),都要等到該事務(wù)結(jié)束后才行。
在Hibernate中,可以在load時(shí)傳入LockMode.UPGRADE來(lái)采用悲觀鎖。修改前面的例子,
在事務(wù)1和2的get方法調(diào)用處,多傳入一個(gè)LockMode參數(shù)。從log中可以看出,事務(wù)1和2
不再是交叉運(yùn)行,事務(wù)2等待事務(wù)1結(jié)束后才可以讀取數(shù)據(jù),所以最終col_balance值是正確
的100。
package com.cdai.orm.hibernate.transaction; import org.hibernate.LockMode; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import com.cdai.orm.hibernate.annotation.Account; import com.cdai.orm.hibernate.annotation.AnnotationHibernate; public class UpgradeLock { @SuppressWarnings("deprecation") public static void main(String[] args) { final SessionFactory sessionFactory = AnnotationHibernate.createSessionFactory(); // Run transaction 1 Thread t1 = new Thread() { @Override public void run() { Session session1 = sessionFactory.openSession(); Transaction tx1 = null; try { tx1 = session1.beginTransaction(); System.out.println("T1 - Begin trasaction"); Thread.sleep(500); Account account = (Account) session1.get(Account.class, new Long(1), LockMode.UPGRADE); System.out.println("T1 - balance=" + account.getBalance()); Thread.sleep(500); account.setBalance(account.getBalance() + 100); System.out.println("T1 - Change balance:" + account.getBalance()); tx1.commit(); System.out.println("T1 - Commit transaction"); Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); if (tx1 != null) tx1.rollback(); } finally { session1.close(); } } }; // Run transaction 2 Thread t2 = new Thread() { @Override public void run() { Session session2 = sessionFactory.openSession(); Transaction tx2 = null; try { tx2 = session2.beginTransaction(); System.out.println("T2 - Begin trasaction"); Thread.sleep(500); Account account = (Account) session2.get(Account.class, new Long(1), LockMode.UPGRADE); System.out.println("T2 - balance=" + account.getBalance()); Thread.sleep(500); account.setBalance(account.getBalance() - 100); System.out.println("T2 - Change balance:" + account.getBalance()); tx2.commit(); System.out.println("T2 - Commit transaction"); Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); if (tx2 != null) tx2.rollback(); } finally { session2.close(); } } }; t1.start(); t2.start(); while (t1.isAlive() || t2.isAlive()) { try { Thread.sleep(2000L); } catch (InterruptedException e) { } } System.out.println("Both T1 and T2 are dead."); sessionFactory.close(); } }
T1 - Begin trasaction T2 - Begin trasaction Hibernate: select account0_.col_id as col1_0_0_, account0_.col_balance as col2_0_0_ from tb_account account0_ with (updlock, rowlock) where account0_.col_id=? Hibernate: select account0_.col_id as col1_0_0_, account0_.col_balance as col2_0_0_ from tb_account account0_ with (updlock, rowlock) where account0_.col_id=? T2 - balance=100 T2 - Change balance:0 Hibernate: update tb_account set col_balance=? where col_id=? T2 - Commit transaction T1 - balance=0 T1 - Change balance:100 Hibernate: update tb_account set col_balance=? where col_id=? T1 - Commit transaction Both T1 and T2 are dead.
Hibernate對(duì)于SQLServer 2005會(huì)執(zhí)行SQL:
select account0_.col_id as col1_0_0_, account0_.col_balance as col2_0_0_ from tb_account account0_ with (updlock, rowlock) where account0_.col_id=?
2.樂(lè)觀鎖:假定當(dāng)前事務(wù)操作的數(shù)據(jù)不會(huì)有其他事務(wù)同時(shí)訪問(wèn),因此完全依靠數(shù)據(jù)庫(kù)
的隔離級(jí)別來(lái)自動(dòng)管理鎖的工作。在應(yīng)用程序中采用版本控制來(lái)避免可能低概率出現(xiàn)
的并發(fā)問(wèn)題。
在Hibernate中,使用Version注解來(lái)定義版本號(hào)字段。
將DirtyLock中的Account對(duì)象替換成AccountVersion,其他代碼不變,執(zhí)行出現(xiàn)異常。
package com.cdai.orm.hibernate.transaction; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; import javax.persistence.Version; @Entity @Table(name = "tb_account_version") public class AccountVersion { @Id @Column(name = "col_id") private long id; @Column(name = "col_balance") private long balance; @Version @Column(name = "col_version") private int version; public AccountVersion() { } public AccountVersion(long id, long balance) { this.id = id; this.balance = balance; } public long getId() { return id; } public void setId(long id) { this.id = id; } public long getBalance() { return balance; } public void setBalance(long balance) { this.balance = balance; } public int getVersion() { return version; } public void setVersion(int version) { this.version = version; } }
log如下:
T1 - Begin trasaction T2 - Begin trasaction Hibernate: select accountver0_.col_id as col1_0_0_, accountver0_.col_balance as col2_0_0_, accountver0_.col_version as col3_0_0_ from tb_account_version accountver0_ where accountver0_.col_id=? Hibernate: select accountver0_.col_id as col1_0_0_, accountver0_.col_balance as col2_0_0_, accountver0_.col_version as col3_0_0_ from tb_account_version accountver0_ where accountver0_.col_id=? T1 - balance=1000 T2 - balance=1000 T1 - Change balance:900 T2 - Change balance:1100 Hibernate: update tb_account_version set col_balance=?, col_version=? where col_id=? and col_version=? Hibernate: update tb_account_version set col_balance=?, col_version=? where col_id=? and col_version=? T1 - Commit transaction 2264 [Thread-2] ERROR org.hibernate.event.def.AbstractFlushingEventListener - Could not synchronize database state with session org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.cdai.orm.hibernate.transaction.AccountVersion#1] at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1934) at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2578) at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2478) at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2805) at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:114) at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:268) at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:260) at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:180) at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321) at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51) at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1206) at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:375) at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:137) at com.cdai.orm.hibernate.transaction.VersionLock$2.run(VersionLock.java:93) Both T1 and T2 are dead.
由于樂(lè)觀鎖完全將事務(wù)隔離交給數(shù)據(jù)庫(kù)來(lái)控制,所以事務(wù)1和2交叉運(yùn)行了,事務(wù)1提交
成功并將col_version改為1,然而事務(wù)2提交時(shí)已經(jīng)找不到col_version為0的數(shù)據(jù)了,所以
拋出了異常。
Hibernate查詢方法比較
Hibernate主要有三種查詢方法:
1.HQL (Hibernate Query Language)
和SQL很類似,支持分頁(yè)、連接、分組、聚集函數(shù)和子查詢等特性,
但HQL是面向?qū)ο蟮?,而不是面向關(guān)系數(shù)據(jù)庫(kù)中的表。正因查詢語(yǔ)句
是面向Domain對(duì)象的,所以使用HQL可以獲得跨平臺(tái)的好處,Hibernate
會(huì)自動(dòng)幫我們根據(jù)不同的數(shù)據(jù)庫(kù)翻譯成不同的SQL語(yǔ)句。這在需要支持
多種數(shù)據(jù)庫(kù)或者數(shù)據(jù)庫(kù)遷移的應(yīng)用中是十分方便的。
但得到方便的同時(shí),由于SQL語(yǔ)句是由Hibernate自動(dòng)生成的,所以這不
利于SQL語(yǔ)句的效率優(yōu)化和調(diào)試,當(dāng)數(shù)據(jù)量很大時(shí)可能會(huì)有效率問(wèn)題,
出了問(wèn)題也不便于排查解決。
2.QBC/QBE (Query by Criteria/Example)
QBC/QBE是通過(guò)組裝查詢條件或者模板對(duì)象來(lái)執(zhí)行查詢的。這在需要
靈活地支持許多查詢條件自由組合的應(yīng)用中是比較方便的。同樣的問(wèn)題
是由于查詢語(yǔ)句是自由組裝的,創(chuàng)建一條語(yǔ)句的代碼可能很長(zhǎng),并且
包含許多分支條件,很不便于優(yōu)化和調(diào)試。
3.SQL
Hibernate也支持直接執(zhí)行SQL的查詢方式。這種方式犧牲了Hibernate跨
數(shù)據(jù)庫(kù)的優(yōu)點(diǎn),手工地編寫(xiě)底層SQL語(yǔ)句,從而獲得最好的執(zhí)行效率,
相對(duì)前兩種方法,優(yōu)化和調(diào)試方便了一些。
下面來(lái)看一組簡(jiǎn)單的例子。
package com.cdai.orm.hibernate.query; import java.util.Arrays; import java.util.List; import org.hibernate.Criteria; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.AnnotationConfiguration; import org.hibernate.criterion.Criterion; import org.hibernate.criterion.Example; import org.hibernate.criterion.Expression; import com.cdai.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(); Session session = sessionFactory.openSession(); // 1.HQL Query query = session.createQuery("from Account as a where a.id=:id"); query.setLong("id", 1); List result = query.list(); for (Object row : result) { System.out.println(row); } // 2.QBC Criteria criteria = session.createCriteria(Account.class); criteria.add(Expression.eq("id", new Long(2))); result = criteria.list(); for (Object row : result) { System.out.println(row); } // 3.QBE Account example= new Account(); example.setBalance(100); result = session.createCriteria(Account.class). add(Example.create(example)). list(); for (Object row : result) { System.out.println(row); } // 4.SQL query = session.createSQLQuery( " select top 10 * from tb_account order by col_id desc "); result = query.list(); for (Object row : result) { System.out.println(Arrays.toString((Object[]) row)); } session.close(); } }
Hibernate: select account0_.col_id as col1_0_, account0_.col_balance as col2_0_ from tb_account account0_ where account0_.col_id=? Account [id=1, balance=100] Hibernate: select this_.col_id as col1_0_0_, this_.col_balance as col2_0_0_ from tb_account this_ where this_.col_id=? Account [id=2, balance=100] Hibernate: select this_.col_id as col1_0_0_, this_.col_balance as col2_0_0_ from tb_account this_ where (this_.col_balance=?) Account [id=1, balance=100] Account [id=2, balance=100] Hibernate: select top 10 * from tb_account order by col_id desc [2, 100] [1, 100]
從log中可以清楚的看到Hibernate對(duì)于生成的SQL語(yǔ)句的控制,具體選擇
哪種查詢方式就要看具體應(yīng)用了。
相關(guān)文章
使用@Valid+BindingResult進(jìn)行controller參數(shù)校驗(yàn)方式
這篇文章主要介紹了使用@Valid+BindingResult進(jìn)行controller參數(shù)校驗(yàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12通過(guò)java api實(shí)現(xiàn)解壓縮zip示例
這篇文章主要介紹了通過(guò)java api實(shí)現(xiàn)解壓縮zip示例,需要的朋友可以參考下2014-04-04深入解析反編譯字節(jié)碼文件中的代碼邏輯JVM中的String操作
這篇文章主要介紹了深入解析反編譯字節(jié)碼文件中的代碼邏輯JVM中的String操作,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10Java代碼實(shí)現(xiàn)簡(jiǎn)單酒店管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了Java代碼實(shí)現(xiàn)簡(jiǎn)單酒店管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06java并發(fā)容器CopyOnWriteArrayList實(shí)現(xiàn)原理及源碼分析
這篇文章主要為大家詳細(xì)介紹了java并發(fā)容器CopyOnWriteArrayList實(shí)現(xiàn)原理及源碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05java集合中HashSet LinkedHashSet TreeSet三者異同面試精講
這篇文章主要為大家介紹了java集合中HashSet LinkedHashSet TreeSet三者異同面試精講,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10MyBatis-Plus枚舉和自定義主鍵ID的實(shí)現(xiàn)步驟
這篇文章主要給大家介紹了關(guān)于MyBatis-Plus枚舉和自定義主鍵ID的相關(guān)資料,文中通過(guò)實(shí)例代碼以及圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-02-02