程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Java的Hibernate框架數據庫操作中鎖的應用和查詢類型

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

編輯:關於JAVA

Java的Hibernate框架數據庫操作中鎖的應用和查詢類型。本站提示廣大學習愛好者:(Java的Hibernate框架數據庫操作中鎖的應用和查詢類型)文章只能為提供參考,不一定能成為您想要的結果。以下是Java的Hibernate框架數據庫操作中鎖的應用和查詢類型正文


 Hibernate與數據庫鎖
1、為何要應用鎖?

要想弄清晰鎖機制存在的緣由,起首要懂得事務的概念。
事務是對數據庫一系列相干的操作,它必需具有ACID特點:

  • A(原子性):要末全體勝利,要末全體撤消。
  • C(分歧性):要堅持數據庫的分歧性。
  • I(隔離性):分歧事務操作雷同數據時,要有各自的數據空間。
  • D(耐久性):一旦事務勝利停止,它對數據庫所做的更新必需永遠堅持。

我們經常使用的關系型數據庫RDBMS完成了事務的這些特征。個中,原子性、
分歧性和耐久性都是采取日記來包管的。而隔離性就是由明天我們存眷的
鎖機制來完成的,這就是為何我們須要鎖機制。

假如沒有鎖,對隔離性不加掌握,能夠會形成哪些效果呢?

  1. 更新喪失:事務1提交的數據被事務2籠罩。
  2. 髒讀:事務2查詢到了事務1未提交的數據。
  3. 虛讀:事務2查詢到了事務1提交的新建數據。
  4. 弗成反復讀:事務2查詢到了事務1提交的更新數據。
  5. 上面來看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 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.
    
    

    因而可知,隔離性是一個須要鄭重斟酌的成績,懂得鎖很有需要。


    2、有若干種鎖?

    罕見的有同享鎖、更新鎖和獨有鎖。

    1.同享鎖:用於讀數據操作,許可其他事務同時讀取。當事務履行select語句時,
    數據庫主動為事務分派一把同享鎖來鎖定讀取的數據。
    2.獨有鎖:用於修正數據,其他事務不克不及讀取也不克不及修正。當事務履行insert、
    update和delete時,數據庫會主動分派。
    3.更新鎖:用於防止更新操作時同享鎖形成的逝世鎖,好比事務1和2同時持有
    同享鎖並期待取得獨有鎖。當履行update時,事務先取得更新鎖,然後將
    更新鎖進級成獨有鎖,如許就防止了逝世鎖。

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

    所以鎖是有許多種的,這麼多鎖要想完整控制靈巧應用太難了,我們又不是DBA。
    怎樣辦?還好,鎖機制關於我們普通用戶來講是通明的,數據庫會主動添加適合的
    鎖,並在恰當的機會主動進級、升級各類鎖,真是太周密了!我們只須要做的就是
    學會依據分歧的營業需求,設置好隔離級別便可以了。


    3、如何設置隔離級別?

    普通來講,數據庫體系會供給四種事務隔離級別供用戶選擇:

    1.Serializable(串行化):當兩個事務同時把持雷同數據時,事務2只能停上去等。

    2.Repeatable Read(可反復讀):事務1能看到事務2新拔出的數據,不克不及看到對
    已稀有據的更新。

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

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


    4、運用法式中的鎖

    當數據庫采取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 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會履行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注解來界說版本號字段。

    將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 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.
    
    

    因為悲觀鎖完整將事務隔離交給數據庫來掌握,所以事務1和2穿插運轉了,事務1提交
    勝利並將col_version改成1,但是事務2提交時曾經找不到col_version為0的數據了,所以
    拋出了異常。

    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語句的掌握,詳細選擇
    哪一種查詢方法就要看詳細運用了。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved