Java の Hibernate フレームワーク データベース操作におけるロックとクエリ タイプの使用

高洛峰
リリース: 2016-12-27 13:35:51
オリジナル
1796 人が閲覧しました

休止状態とデータベース ロック
1. ロックを使用する理由は何ですか?

ロックメカニズムが存在する理由を理解するには、まずトランザクションの概念を理解する必要があります。
トランザクションは、データベース上の一連の関連操作であり、ACID 特性を持つ必要があります。

A (原子性): すべてが成功するか、すべてが取り消されるかのいずれかです。

C (一貫性): データベースの一貫性を維持します。

I (分離): 異なるトランザクションが同じデータで動作する場合、それらは独自のデータ領域を持たなければなりません。

D (耐久性): トランザクションが正常に終了すると、データベースに対して行われた更新は永続的に保持される必要があります。

私たちが一般的に使用しているリレーショナル データベース RDBMS は、トランザクションのこれらの特性を実装しています。その中で、原子性、一貫性、耐久性はすべてログによって保証されます。絶縁は今回注目するロック機構によって実現されます。これがロック機構が必要な理由です。

ロックがなく、隔離を制御できない場合、どのような結果が考えられますか?

更新が失われました: トランザクション 1 によって送信されたデータは、トランザクション 2 によって上書きされました。

ダーティ リード: トランザクション 2 は、トランザクション 1 のコミットされていないデータをクエリします。

ダミー読み取り: トランザクション 2 は、トランザクション 1 によって送信された新しく作成されたデータをクエリします。

非反復読み取り: トランザクション 2 は、トランザクション 1 によって送信された更新されたデータをクエリします。

Hibernate の例を見てみましょう。2 つのスレッドが 2 つのトランザクションを開始して、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 は、col_balance を 100 減少させます。最終結果は 0 になるか、
200 になる可能性があり、トランザクション 1 または 2 からの更新が失われる可能性があります。ログ出力でも、トランザクション 1 と 2

のログがクロスプリントされています。

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. 排他的ロック: データの変更に使用され、他のトランザクションはそのデータを読み取ったり変更したりすることはできません。データベースは、トランザクションが挿入、更新、削除を実行するときに自動的に割り当てられます。

3. 更新ロック: 更新操作中に共有ロックによって引き起こされるデッドロックを回避するために使用されます。たとえば、トランザクション 1 と 2 は同時に

共有ロックを保持し、排他的ロックの取得を待機します。更新を実行するとき、トランザクションは最初に更新ロックを取得し、次に更新ロックを排他ロックにアップグレードすることで、デッドロックを回避します。

さらに、これらのロックはデータベース内のさまざまなオブジェクトに適用できます。つまり、これらのロックは異なる粒度を持つことができます。
データベースレベルのロック、テーブルレベルのロック、ページレベルのロック、キーレベルのロック、行レベルのロックなど。

つまり、ロックにはさまざまな種類があり、これほど多くのロックを完全にマスターして柔軟に使用するのは非常に困難です。
どうすればいいですか?幸いなことに、ロックのメカニズムは私たち一般ユーザーには透過的です。データベースは適切なロックを自動的に追加し、適切なタイミングでさまざまなロックを自動的にアップグレードおよびダウングレードします。私たちがしなければならないのは、さまざまなビジネス ニーズに応じて分離レベルを設定する方法を学ぶことだけです。


3. 分離レベルを設定するには?

一般的に、データベース システムはユーザーが選択できる 4 つのトランザクション分離レベルを提供します。

1. シリアル化可能 (シリアル化可能): 2 つのトランザクションが同時に同じデータを操作する場合、トランザクション 2 は停止して待機することしかできません。

2.Repeatable Read: トランザクション 1 はトランザクション 2 の新しく挿入されたデータを確認できますが、

既存データの更新は確認できません。

3.Read Committed (コミットされたデータの読み取り): トランザクション 1 は、トランザクション 2 の新しく挿入および更新されたデータを確認できます。

4. Read Uncommited (コミットされていないデータの読み取り): トランザクション 1 は、トランザクション 2 のコミットされていない挿入および更新データを参照できます。

4. アプリケーションのロック

データベースが Read Commited 分離レベルを採用している場合、アプリケーションで悲観的ロックまたは楽観的ロックを使用できます。

1. 悲観的ロック: 現在のトランザクションで操作されているデータは必ず他のトランザクションからアクセスされると想定されるため、アプリケーションプログラム内で悲観的に排他的ロックを指定してデータリソースをロックします。 MySQL と Oracle では次の形式がサポートされています:

select ... for update
ログイン後にコピー

明示的に select に排他ロックを使用させて、クエリされたレコードをロックします。他のトランザクションが

によってロックされたデータをクエリ、更新、または削除したい場合は、トランザクションが終了するまで待つ必要があります。


Hibernate では、ロード時に LockMode.UPGRADE を渡して悲観的ロックを使用できます。前の例を変更し、トランザクション 1 と 2 の get メソッドの呼び出しポイントでもう 1 つ LockMode パラメーターを渡します。ログからわかるように、トランザクション 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 アノテーションを使用してバージョン番号フィールドを定義します。

Java の Hibernate フレームワーク データベース操作におけるロックとクエリ タイプの使用

将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的数据了,所以
抛出了异常。

Java の Hibernate フレームワーク データベース操作におけるロックとクエリ タイプの使用

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语句的控制,具体选择
哪种查询方式就要看具体应用了。

更多Java的Hibernate框架数据库操作中锁的使用和查询类型相关文章请关注PHP中文网!

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート