Java的Hibernate框架數(shù)據(jù)庫操作中鎖的使用和查詢類型
Hibernate與數(shù)據(jù)庫鎖
一、為什么要使用鎖?
要想弄清楚鎖機制存在的原因,首先要了解事務(wù)的概念。
事務(wù)是對數(shù)據(jù)庫一系列相關(guān)的操作,它必須具備ACID特征:
- A(原子性):要么全部成功,要么全部撤銷。
- C(一致性):要保持?jǐn)?shù)據(jù)庫的一致性。
- I(隔離性):不同事務(wù)操作相同數(shù)據(jù)時,要有各自的數(shù)據(jù)空間。
- D(持久性):一旦事務(wù)成功結(jié)束,它對數(shù)據(jù)庫所做的更新必須永久保持。
我們常用的關(guān)系型數(shù)據(jù)庫RDBMS實現(xiàn)了事務(wù)的這些特性。其中,原子性、
一致性和持久性都是采用日志來保證的。而隔離性就是由今天我們關(guān)注的
鎖機制來實現(xiàn)的,這就是為什么我們需要鎖機制。
如果沒有鎖,對隔離性不加控制,可能會造成哪些后果呢?
- 更新丟失:事務(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ù)。
下面來看Hibernate的例子,兩個線程分別開啟兩個事務(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的更新可能會丟失。log輸出也印證了這一點,事務(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.
由此可見,隔離性是一個需要慎重考慮的問題,理解鎖很有必要。
二、有多少種鎖?
常見的有共享鎖、更新鎖和獨占鎖。
1.共享鎖:用于讀數(shù)據(jù)操作,允許其他事務(wù)同時讀取。當(dāng)事務(wù)執(zhí)行select語句時,
數(shù)據(jù)庫自動為事務(wù)分配一把共享鎖來鎖定讀取的數(shù)據(jù)。
2.獨占鎖:用于修改數(shù)據(jù),其他事務(wù)不能讀取也不能修改。當(dāng)事務(wù)執(zhí)行insert、
update和delete時,數(shù)據(jù)庫會自動分配。
3.更新鎖:用于避免更新操作時共享鎖造成的死鎖,比如事務(wù)1和2同時持有
共享鎖并等待獲得獨占鎖。當(dāng)執(zhí)行update時,事務(wù)先獲得更新鎖,然后將
更新鎖升級成獨占鎖,這樣就避免了死鎖。
此外,這些鎖都可以施加到數(shù)據(jù)庫中不同的對象上,即這些鎖可以有不同的粒度。
如數(shù)據(jù)庫級鎖、表級鎖、頁面級鎖、鍵級鎖和行級鎖。
所以鎖是有很多種的,這么多鎖要想完全掌握靈活使用太難了,我們又不是DBA。
怎么辦?還好,鎖機制對于我們一般用戶來說是透明的,數(shù)據(jù)庫會自動添加合適的
鎖,并在適當(dāng)?shù)臅r機自動升級、降級各種鎖,真是太周到了!我們只需要做的就是
學(xué)會根據(jù)不同的業(yè)務(wù)需求,設(shè)置好隔離級別就可以了。
三、怎樣設(shè)置隔離級別?
一般來說,數(shù)據(jù)庫系統(tǒng)會提供四種事務(wù)隔離級別供用戶選擇:
1.Serializable(串行化):當(dāng)兩個事務(wù)同時操縱相同數(shù)據(jù)時,事務(wù)2只能停下來等。
2.Repeatable Read(可重復(fù)讀):事務(wù)1能看到事務(wù)2新插入的數(shù)據(jù),不能看到對
已有數(shù)據(jù)的更新。
3.Read Commited(讀已提交數(shù)據(jù)):事務(wù)1能看到事務(wù)2新插入和更新的數(shù)據(jù)。
4.Read Uncommited(讀未提交數(shù)據(jù)):事務(wù)1能看到事務(wù)2沒有提交的插入和更新
數(shù)據(jù)。
四、應(yīng)用程序中的鎖
當(dāng)數(shù)據(jù)庫采用Read Commited隔離級別時,可以在應(yīng)用程序中采用悲觀鎖或樂觀鎖。
1.悲觀鎖:假定當(dāng)前事務(wù)操作的數(shù)據(jù)肯定還會有其他事務(wù)訪問,因此悲觀地在應(yīng)用
程序中顯式指定采用獨占鎖來鎖定數(shù)據(jù)資源。在MySQL、Oracle中支持以下形式:
select ... for update
顯式地讓select采用獨占鎖鎖定查詢的記錄,其他事務(wù)要查詢、更新或刪除這些被
鎖定的數(shù)據(jù),都要等到該事務(wù)結(jié)束后才行。
在Hibernate中,可以在load時傳入LockMode.UPGRADE來采用悲觀鎖。修改前面的例子,
在事務(wù)1和2的get方法調(diào)用處,多傳入一個LockMode參數(shù)。從log中可以看出,事務(wù)1和2
不再是交叉運行,事務(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對于SQLServer 2005會執(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.樂觀鎖:假定當(dāng)前事務(wù)操作的數(shù)據(jù)不會有其他事務(wù)同時訪問,因此完全依靠數(shù)據(jù)庫
的隔離級別來自動管理鎖的工作。在應(yīng)用程序中采用版本控制來避免可能低概率出現(xiàn)
的并發(fā)問題。
在Hibernate中,使用Version注解來定義版本號字段。

將DirtyLock中的Account對象替換成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.
由于樂觀鎖完全將事務(wù)隔離交給數(shù)據(jù)庫來控制,所以事務(wù)1和2交叉運行了,事務(wù)1提交
成功并將col_version改為1,然而事務(wù)2提交時已經(jīng)找不到col_version為0的數(shù)據(jù)了,所以
拋出了異常。

Hibernate查詢方法比較
Hibernate主要有三種查詢方法:
1.HQL (Hibernate Query Language)
和SQL很類似,支持分頁、連接、分組、聚集函數(shù)和子查詢等特性,
但HQL是面向?qū)ο蟮?,而不是面向關(guān)系數(shù)據(jù)庫中的表。正因查詢語句
是面向Domain對象的,所以使用HQL可以獲得跨平臺的好處,Hibernate
會自動幫我們根據(jù)不同的數(shù)據(jù)庫翻譯成不同的SQL語句。這在需要支持
多種數(shù)據(jù)庫或者數(shù)據(jù)庫遷移的應(yīng)用中是十分方便的。
但得到方便的同時,由于SQL語句是由Hibernate自動生成的,所以這不
利于SQL語句的效率優(yōu)化和調(diào)試,當(dāng)數(shù)據(jù)量很大時可能會有效率問題,
出了問題也不便于排查解決。
2.QBC/QBE (Query by Criteria/Example)
QBC/QBE是通過組裝查詢條件或者模板對象來執(zhí)行查詢的。這在需要
靈活地支持許多查詢條件自由組合的應(yīng)用中是比較方便的。同樣的問題
是由于查詢語句是自由組裝的,創(chuàng)建一條語句的代碼可能很長,并且
包含許多分支條件,很不便于優(yōu)化和調(diào)試。
3.SQL
Hibernate也支持直接執(zhí)行SQL的查詢方式。這種方式犧牲了Hibernate跨
數(shù)據(jù)庫的優(yōu)點,手工地編寫底層SQL語句,從而獲得最好的執(zhí)行效率,
相對前兩種方法,優(yōu)化和調(diào)試方便了一些。
下面來看一組簡單的例子。
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對于生成的SQL語句的控制,具體選擇
哪種查詢方式就要看具體應(yīng)用了。
相關(guān)文章
Google Kaptcha 框架實現(xiàn)登錄驗證碼功能(SSM 和 SpringBoot)
這篇文章主要介紹了Google Kaptcha 實現(xiàn)登錄驗證碼(SSM 和 SpringBoot)功能,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2018-12-12
Java線程監(jiān)聽,意外退出線程后自動重啟的實現(xiàn)方法
下面小編就為大家?guī)硪黄狫ava線程監(jiān)聽,意外退出線程后自動重啟的實現(xiàn)方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-03-03

