国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Java > 正文

Java的Hibernate框架數據庫操作中鎖的使用和查詢類型

2019-11-26 14:39:44
字體:
來源:轉載
供稿:網友

 Hibernate與數據庫鎖
一、為什么要使用鎖?

要想弄清楚鎖機制存在的原因,首先要了解事務的概念。
事務是對數據庫一系列相關的操作,它必須具備ACID特征:

  • A(原子性):要么全部成功,要么全部撤銷。
  • C(一致性):要保持數據庫的一致性。
  • I(隔離性):不同事務操作相同數據時,要有各自的數據空間。
  • D(持久性):一旦事務成功結束,它對數據庫所做的更新必須永久保持。

我們常用的關系型數據庫RDBMS實現了事務的這些特性。其中,原子性、
一致性和持久性都是采用日志來保證的。而隔離性就是由今天我們關注的
鎖機制來實現的,這就是為什么我們需要鎖機制。

如果沒有鎖,對隔離性不加控制,可能會造成哪些后果呢?

  1. 更新丟失:事務1提交的數據被事務2覆蓋。
  2. 臟讀:事務2查詢到了事務1未提交的數據。
  3. 虛讀:事務2查詢到了事務1提交的新建數據。
  4. 不可重復讀:事務2查詢到了事務1提交的更新數據。

下面來看Hibernate的例子,兩個線程分別開啟兩個事務操作tb_account表中
的同一行數據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();        }  } 

事務1將col_balance減小100,而事務2將其減少100,最終結果可能是0,也
可能是200,事務1或2的更新可能會丟失。log輸出也印證了這一點,事務1和2
的log交叉打印。

T1 - Begin trasactionT2 - Begin trasactionHibernate: 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=100T2 - balance=100T2 - Change balance:0T1 - Change balance:200Hibernate: update tb_account set col_balance=? where col_id=?Hibernate: update tb_account set col_balance=? where col_id=?T1 - Commit transactionT2 - Commit transactionBoth T1 and T2 are dead.

由此可見,隔離性是一個需要慎重考慮的問題,理解鎖很有必要。


二、有多少種鎖?

常見的有共享鎖、更新鎖和獨占鎖。

1.共享鎖:用于讀數據操作,允許其他事務同時讀取。當事務執行select語句時,
數據庫自動為事務分配一把共享鎖來鎖定讀取的數據。
2.獨占鎖:用于修改數據,其他事務不能讀取也不能修改。當事務執行insert、
update和delete時,數據庫會自動分配。
3.更新鎖:用于避免更新操作時共享鎖造成的死鎖,比如事務1和2同時持有
共享鎖并等待獲得獨占鎖。當執行update時,事務先獲得更新鎖,然后將
更新鎖升級成獨占鎖,這樣就避免了死鎖。

此外,這些鎖都可以施加到數據庫中不同的對象上,即這些鎖可以有不同的粒度。
如數據庫級鎖、表級鎖、頁面級鎖、鍵級鎖和行級鎖。

所以鎖是有很多種的,這么多鎖要想完全掌握靈活使用太難了,我們又不是DBA。
怎么辦?還好,鎖機制對于我們一般用戶來說是透明的,數據庫會自動添加合適的
鎖,并在適當的時機自動升級、降級各種鎖,真是太周到了!我們只需要做的就是
學會根據不同的業務需求,設置好隔離級別就可以了。


三、怎樣設置隔離級別?

一般來說,數據庫系統會提供四種事務隔離級別供用戶選擇:

1.Serializable(串行化):當兩個事務同時操縱相同數據時,事務2只能停下來等。

2.Repeatable Read(可重復讀):事務1能看到事務2新插入的數據,不能看到對
已有數據的更新。

3.Read Commited(讀已提交數據):事務1能看到事務2新插入和更新的數據。

4.Read Uncommited(讀未提交數據):事務1能看到事務2沒有提交的插入和更新
數據。


四、應用程序中的鎖

當數據庫采用Read Commited隔離級別時,可以在應用程序中采用悲觀鎖或樂觀鎖。

1.悲觀鎖:假定當前事務操作的數據肯定還會有其他事務訪問,因此悲觀地在應用
程序中顯式指定采用獨占鎖來鎖定數據資源。在MySQL、Oracle中支持以下形式:

   select ... for update

顯式地讓select采用獨占鎖鎖定查詢的記錄,其他事務要查詢、更新或刪除這些被
鎖定的數據,都要等到該事務結束后才行。

在Hibernate中,可以在load時傳入LockMode.UPGRADE來采用悲觀鎖。修改前面的例子,
在事務1和2的get方法調用處,多傳入一個LockMode參數。從log中可以看出,事務1和2
不再是交叉運行,事務2等待事務1結束后才可以讀取數據,所以最終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 trasactionT2 - Begin trasactionHibernate: 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=100T2 - Change balance:0Hibernate: update tb_account set col_balance=? where col_id=?T2 - Commit transactionT1 - balance=0T1 - Change balance:100Hibernate: update tb_account set col_balance=? where col_id=?T1 - Commit transactionBoth T1 and T2 are dead.

Hibernate對于SQLServer 2005會執行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=?


為選定的col_id為1的數據行加上行鎖和更新鎖。

2.樂觀鎖:假定當前事務操作的數據不會有其他事務同時訪問,因此完全依靠數據庫
的隔離級別來自動管理鎖的工作。在應用程序中采用版本控制來避免可能低概率出現
的并發問題。

在Hibernate中,使用Version注解來定義版本號字段。

201612285333338.png (351×120)

將DirtyLock中的Account對象替換成AccountVersion,其他代碼不變,執行出現異常。

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 trasactionT2 - Begin trasactionHibernate: 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=1000T2 - balance=1000T1 - Change balance:900T2 - Change balance:1100Hibernate: 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 transaction2264 [Thread-2] ERROR org.hibernate.event.def.AbstractFlushingEventListener - Could not synchronize database state with sessionorg.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.

由于樂觀鎖完全將事務隔離交給數據庫來控制,所以事務1和2交叉運行了,事務1提交
成功并將col_version改為1,然而事務2提交時已經找不到col_version為0的數據了,所以
拋出了異常。

201612285414974.png (353×131)

Hibernate查詢方法比較
Hibernate主要有三種查詢方法:

1.HQL (Hibernate Query Language)

和SQL很類似,支持分頁、連接、分組、聚集函數和子查詢等特性,
但HQL是面向對象的,而不是面向關系數據庫中的表。正因查詢語句
是面向Domain對象的,所以使用HQL可以獲得跨平臺的好處,Hibernate
會自動幫我們根據不同的數據庫翻譯成不同的SQL語句。這在需要支持
多種數據庫或者數據庫遷移的應用中是十分方便的。

但得到方便的同時,由于SQL語句是由Hibernate自動生成的,所以這不
利于SQL語句的效率優化和調試,當數據量很大時可能會有效率問題,
出了問題也不便于排查解決。

2.QBC/QBE (Query by Criteria/Example)

QBC/QBE是通過組裝查詢條件或者模板對象來執行查詢的。這在需要
靈活地支持許多查詢條件自由組合的應用中是比較方便的。同樣的問題
是由于查詢語句是自由組裝的,創建一條語句的代碼可能很長,并且
包含許多分支條件,很不便于優化和調試。

3.SQL

Hibernate也支持直接執行SQL的查詢方式。這種方式犧牲了Hibernate跨
數據庫的優點,手工地編寫底層SQL語句,從而獲得最好的執行效率,
相對前兩種方法,優化和調試方便了一些。

下面來看一組簡單的例子。

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語句的控制,具體選擇
哪種查詢方式就要看具體應用了。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 剑川县| 岱山县| 重庆市| 伽师县| 遵义市| 伊宁市| 二手房| 龙游县| 陇西县| 于都县| 县级市| 韩城市| 莎车县| 凤翔县| 岳阳市| 舞钢市| 东阿县| 呼伦贝尔市| 宁都县| 香格里拉县| 胶南市| 平泉县| 来宾市| 宜兴市| 台江县| 丹东市| 吉首市| 临夏市| 沿河| 新巴尔虎左旗| 富蕴县| 澎湖县| 凉山| 洪江市| 龙游县| 县级市| 吉木乃县| 韶山市| 桐梓县| 泊头市| 中阳县|