Hibernate and Database Lock1。なぜロックを使用するのですか?
ロックメカニズムが存在する理由を把握するには、最初にトランザクションの概念を理解する必要があります。
トランザクションは、データベース上の一連の関連操作であり、酸性の特性が必要です。
一般的に使用されるリレーショナルデータベースRDBMSは、これらのトランザクションの特性を実装しています。その中で、原子性、
一貫性と持続性は、ロギングによって保証されます。分離は、今日私たちが懸念しているロックメカニズムによって達成されるため、ロックメカニズムが必要です。
ロックがなく、隔離を制御できない場合、どのような結果が生じる可能性がありますか?
Hibernateの例を見てみましょう。 2つのスレッドは、それぞれ2つのトランザクション操作を開始します。 TB_Accountテーブルの同じデータの行はcol_id = 1です。
パッケージcom.cdai.orm.hibernate.annotation; java.io.serializableをインポートします。 javax.persistence.columnをインポートします。 javax.persistence.entityをインポートします。 javax.persistence.idをインポートします。 javax.persistence.tableをインポートします。 @entity @table(name = "tb_account")パブリッククラスアカウントは、serializable {private static final long serialversionuid = 5018821760412231859l; @id @column(name = "col_id")private long id; @column(name = "col_balance")プライベートロングバランス; publicアカウント(){} publicアカウント(長い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 + "]"; }}パッケージcom.cdai.orm.hibernate.transaction; org.hibernate.sessionをインポートします。 Import org.hibernate.sessionFactory; org.hibernate.transactionをインポートします。 Import org.hibernate.cfg.annotationConfiguration; com.cdai.orm.hibernate.annotation.accountをインポートします。 public class dirtyread {public static void main(string [] args){final sessionfactory factory = new AnnotationConfiguration()。 addfile( "hibernate/hibernate.cfg.xml")。 configure()。 AddPackage( "com.cdai.orm.hibernate.annotation")。 AddAnnotatedClass(account.class)。 BuildSessionFactory();スレッドT1 = newスレッド(){@Override public void run(){session session1 = sessionfactory.opensession();トランザクションtx1 = null; try {tx1 = session1.begintransaction(); system.out.println( "t1 -begin trasaction"); thread.sleep(500);アカウントアカウント=(account)session1.get(account.class、new long(1)); system.out.println( "t1-残高=" + account.getbalance()); thread.sleep(500); account.setbalance(account.getBalance() + 100); system.out.println( "t1-バランスを変更:" + account.getbalance()); tx1.commit(); system.out.println( "T1-トランザクションのコミット"); thread.sleep(500); } catch(Exception e){e.printstacktrace(); if(tx1!= null)tx1.rollback(); }最後に{session1.close(); }}}}; // 3.Runトランザクション2スレッドT2 = newスレッド(){@Override public void run(){session session2 = sessionfactory.opensession();トランザクションtx2 = null; try {tx2 = session2.begintransaction(); system.out.println( "t2 -begin trasaction"); thread.sleep(500);アカウントアカウント=(account)session2.get(account.class、new long(1)); System.out.println( "T2-残高=" + account.getBalance()); thread.sleep(500); account.setbalance(account.getBalance() - 100); System.out.println( "T2-バランスを変更する:" + account.getBalance()); tx2.commit(); system.out.println( "T2-コミットトランザクション"); thread.sleep(500); } catch(Exception e){e.printstacktrace(); if(tx2!= null)tx2.rollback(); }最後に{session2.close(); }}}}; t1.start(); t2.start(); while(t1.isalive()|| t2.isalive()){try {thread.sleep(2000l); } catch(arternedexception e){}} system.out.println( "T1とT2の両方が死んでいます。"); sessionfactory.close(); }}トランザクション1はcol_balanceを100に減らし、トランザクション2は100減少し、最終結果は0または200になる可能性があり、トランザクション1または2の更新が失われる可能性があります。ログ出力はこれを確認します、トランザクション1と2
ログクロスプリント。
T1 -trasactiont2を開始 - trasactionhibernateを開始:col1_0_0_としてaccount0_.col_idを選択し、col1_0_0_、col2_0_0_としてcol1_0_0_としてcol2_0_0_を選択します。 tb_account account0_ where Account0_.col_id =?t1-残高= 100t2-残高= 100t2-バランスの変更:0t1-変更バランス:200hibernate:更新tb_account set col_balance =?ここでcol_id =?hibernate:update tb_account set col_balance =?ここで、col_id =?t1 -Commit TransactionT2 -T1とT2のコミットトランザクションは死んでいます。
隔離は慎重に検討する必要がある問題であり、ロックを理解する必要があることがわかります。
2。ロックはいくつありますか?
一般的なものは、共有ロック、更新ロック、排他的ロックです。
1。共有ロック:データ操作の読み取りに使用され、他のトランザクションを同時に読み取ることができます。トランザクションがSELECTステートメントを実行するとき、
データベースは、読み取りデータをロックするためにトランザクションに共有ロックを自動的に割り当てます。
2。排他的ロック:データの変更に使用すると、他のトランザクションを読み取りまたは変更できません。トランザクションが挿入を実行するとき、
更新と削除が更新されると、データベースは自動的に割り当てられます。
3.更新ロック:トランザクション1や2の共有ロックを同時に保持し、排他的ロックの取得を待つなど、更新操作中に共有ロックによって引き起こされるデッドロックを回避するために使用されます。更新を実行するとき、トランザクションは最初に更新ロックを取得し、次に更新ロックを排他的ロックにアップグレードし、デッドロックを回避します。
さらに、これらのロックはすべてデータベース内の異なるオブジェクトに適用できます。つまり、これらのロックは異なる粒度を持つことができます。
データベースレベルのロック、テーブルレベルのロック、ページレベルのロック、キーレベルのロック、行レベルのロックなど。
したがって、ロックには多くの種類があります。非常に多くのロックを柔軟にマスターして使用することは難しすぎます。私たちはDBAではありません。
何をするか?幸いなことに、ロックメカニズムは通常のユーザーに対して透明です。データベースは、適切なロックを自動的に追加し、適切なタイミングでさまざまなロックを自動的にアップグレードおよびダウングレードします。とても思慮深い!私たちがする必要があるのは、さまざまなビジネスニーズに応じて隔離レベルを設定することを学ぶことです。
3.分離レベルを設定する方法は?
一般的に言えば、データベースシステムは、ユーザーが選択できる4つのトランザクション分離レベルを提供します。
1.セリア化可能:2つのトランザクションが同じデータを同時に操作する場合、トランザクション2は停止して待機することができます。
2. Repeatable Read(繰り返し):トランザクション1は、トランザクション2から新しく挿入されたデータを表示でき、既存のデータの更新を表示できません。
3. commited(読み取りデータを読む):トランザクション1は、トランザクション2から新しく挿入および更新されたデータを見ることができます。
4.Committedの読み取り(コミットされていないデータを読む):トランザクション1は、トランザクション2がコミットしていない挿入および更新データを確認できます。
4.アプリケーションのロック
データベースが読み取り委員会の分離レベルを採用する場合、アプリケーションでは悲観的なロックまたは楽観的ロックを使用できます。
1.悲観的なロック:現在のトランザクション操作のデータには間違いなく他のトランザクションアクセスがあると仮定するため、データリソースをロックするためにアプリケーションで排他的ロックが使用されることを悲観的に指定します。 MySQLとOracleで次のフォームをサポートしてください。
選択...更新用
SELECTが排他的ロックロックを使用して、クエリのレコードをロックするように明示的に許可します。これらのロックされたデータをクエリ、更新、または削除する他のトランザクションの場合、トランザクションが終了するまで待つ必要があります。
Hibernateでは、LockMode.Upgradeをロードするときに、悲観的なロックを採用するために渡すことができます。前の例を変更すると、
トランザクション1および2のGETメソッド呼び出しでは、追加のロックモードパラメーターが渡されます。ログからわかるように、トランザクション1と2
それはもはやクロスランニングではなく、トランザクション2はデータを読む前にトランザクション1が終了するのを待つことができるため、最終的なcol_balance値は正しい100です。
パッケージcom.cdai.orm.hibernate.transaction; org.hibernate.lockmodeをインポートします。 org.hibernate.sessionをインポートします。 Import org.hibernate.sessionFactory; org.hibernate.transactionをインポートします。 com.cdai.orm.hibernate.annotation.accountをインポートします。 com.cdai.orm.hibernate.annotation.annotationhibernateをインポートします。 public class upgradelock {@suppresswarnings( "deprecation")public static void main(string [] args){final sessionfactory sessionfactory = annotationhibernate.createssessionfactory(); //トランザクション1スレッドT1 = newスレッド(){@Override public void run(){session session1 = sessionfactory.opensession();トランザクションtx1 = null; try {tx1 = session1.begintransaction(); system.out.println( "t1 -begin trasaction"); thread.sleep(500);アカウントアカウント=(account)session1.get(account.class、new long(1)、lockmode.upgrade); system.out.println( "t1-残高=" + account.getbalance()); thread.sleep(500); account.setbalance(account.getBalance() + 100); system.out.println( "t1-バランスを変更:" + account.getbalance()); tx1.commit(); system.out.println( "T1-トランザクションのコミット"); thread.sleep(500); } catch(Exception e){e.printstacktrace(); if(tx1!= null)tx1.rollback(); }最後に{session1.close(); }}}; //トランザクション2スレッドT2 = newスレッド(){@Override public void run(){session session2 = sessionfactory.opensession();トランザクションtx2 = null; try {tx2 = session2.begintransaction(); system.out.println( "t2 -begin trasaction"); thread.sleep(500);アカウントアカウント=(account)session2.get(account.class、new long(1)、lockmode.upgrade); System.out.println( "T2-残高=" + account.getBalance()); thread.sleep(500); account.setbalance(account.getBalance() - 100); System.out.println( "T2-バランスを変更する:" + account.getBalance()); tx2.commit(); system.out.println( "T2-コミットトランザクション"); thread.sleep(500); } catch(Exception e){e.printstacktrace(); if(tx2!= null)tx2.rollback(); }最後に{session2.close(); }}}}; t1.start(); t2.start(); while(t1.isalive()|| t2.isalive()){try {thread.sleep(2000l); } catch(arternedexception e){}} system.out.println( "T1とT2の両方が死んでいます。"); sessionfactory.close(); }}T1 -trasactiont2を開始 - trasactionhibernateを開始:col1_0_0_としてaccount0_.col_idを選択し、col1_0_0_、col2_0_0_としてcol1_0_0_としてcol2_0_0_を選択します(updlock、rowlock) col2_0_0_ tb_account account0_ where(updlock、rowlock)where courcount0_.col_id =?t2-残高= 100t2-バランスを変更:0hibernate:update tb_account set col_balance =?ここで、col_id =?t2 -commit transactiont1-残高= 0t1-バランスを変更:100hibernate:update tb_account set col_balance =?ここで、col_id =?t1 -T1とT2を取得するトランザクションをコミットしています。
HibernateはSQLServer 2005のSQLを実行します:
コードコピーは次のとおりです。
col1_0_0_としてaccount0_.col_idを選択し、account0_.col_id =?
2。楽観的なロック:現在のトランザクション操作のデータが他のトランザクションによって同時にアクセスされないと仮定するため、データベースの分離レベルはデータベースに完全に依存して、ロックの作業を自動的に管理します。低い確率で発生する可能性のある並行性の問題を回避するために、アプリケーションでバージョン制御を採用します。
Hibernateでは、バージョンアノテーションを使用してバージョン番号フィールドを定義します。
DirtyLockのアカウントオブジェクトをAcounterversionに置き換えると、他のコードは変更されておらず、実行が発生したときに例外が発生します。
パッケージcom.cdai.orm.hibernate.transaction; javax.persistence.columnをインポートします。 javax.persistence.entityをインポートします。 javax.persistence.idをインポートします。 javax.persistence.tableをインポートします。 javax.persistence.versionをインポートします。 @entity @table(name = "tb_account_version")public class accountversion {@id @column(name = "col_id")private long id; @column(name = "col_balance")プライベートロングバランス; @version @column(name = "col_version")private intバージョン; public Accountversion(){} public councountversion(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バージョン; } public void Setversion(intバージョン){this.version = version; }}ログは次のとおりです。
T1 -trasactiont2を開始 - trasactionhibernateを開始:col1_0_0_としてaccountver0_.col_idを選択し、col2_0_0_としてcol2_0_0_、col3_0_0_としてtb_acount_version col3_0_0_ as col3_0_0_ as col3_0_0_ as col30_id = coldver0_.col_id? col1_0_0_、Accountver0_.Col_Balance as Col2_0_0_、Accountver0_.Col_versionはtb_account_version accountver0_ from col3_0_0_ colver0_ where where where where where where where where where where where where where where where where where col_id =? col_balance =?、col_version =?どこでcol_id =?およびcol_version =?hibernate:更新tb_account_version set col_balance =?、col_version =?どこでcol_id =? and col_version =?t1-コミットトランザクション2264 [スレッド2]エラーorg.hibernate.event.def.def.abstractflushingeventlistener- sessionorg.hibernateとデータベース状態を同期することはできませんでした。 [com.cdai.orm.hibernate.transaction.accountversion#1] at org.hibernate.persister.entity.abstractentitypersister.check(abstracttentitypersister.java:1934)at org.hibernate.persister.persister.entity. org.hibernate.persister.entity.abstractentitypersister.updateorinsert(abstractentitypersister.java:2478)at org.hibernate.persister.persister.entity.update.update(abstractitypersister.java:2805)at org.hibernate.engine.actionqueue.execute(actionqueupdateaction.java:114)at org.hibernate.engine.engine.action.actionqueue.executions(actionque.java:260)のorg.hibernate.engine.actionqueue.execute(actionqueue.java:268)at org.hibernate.engine.execute.execute.execute.execute.execute.execute.execute.execute.execute.execute.execute org.hibernate.executions(Actionqueue.java:180)at org.hibernate.event.def.abstractflushingeventlistener.performexecutions(abstractflushingeventlistener.java:321)のorg.hiberceue.executes(actionqueue.java:180) 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)T1とT2の両方が死んでいます。
楽観的なロックにより、トランザクションがデータベースに完全に分離され、トランザクション1と2がクロスランを実行するため、トランザクション1が正常にコミットされ、Col_versionが1に変更されます。ただし、トランザクション2がコミットすると、col_versionのデータが見つかりませんでした。
冬眠クエリ方法の比較
冬眠には3つの主要なクエリメソッドがあります。
1.hql(hibernateクエリ言語)
SQLに非常に似ており、ページング、接続、グループ化、集約関数、サブクエリなどの機能をサポートしています。
しかし、HQLはオブジェクト指向であり、リレーショナルデータベースのテーブルではありません。クエリステートメントはドメインオブジェクトに向けられているため、HQLを使用するとクロスプラットフォームの利点が得られます。冬眠
さまざまなデータベースに従って、さまざまなSQLステートメントに翻訳するのに自動的に役立ちます。これは、複数のデータベースまたはデータベースの移行をサポートする必要があるアプリケーションで非常に便利です。
ただし、SQLステートメントはHibernateによって自動的に生成されるため、便利になりますが、これはSQLステートメントの効率の最適化とデバッグを助長しません。データの量が多い場合、効率の問題がある可能性があります。
問題がある場合、それを調査して解決することは便利ではありません。
2.QBC/QBE(基準/例によるクエリ)
QBC/QBEは、クエリ条件またはテンプレートオブジェクトを組み立てることによりクエリを実行します。これは、クエリ条件の多くの無料組み合わせに柔軟なサポートを必要とするアプリケーションで便利です。同じ問題は、クエリステートメントが自由に組み立てられているため、ステートメントを作成するコードは長くなり、多くの分岐条件が含まれている可能性があることです。これは、最適化とデバッグに非常に不便です。
3.SQL
Hibernateは、SQLを直接実行するクエリメソッドもサポートしています。この方法は、Hibernate Cross-Databaseの利点を犠牲にし、基礎となるSQLステートメントを手動で書き、最高の実行効率を達成します。
最初の2つの方法と比較して、最適化とデバッグがより便利です。
一連の簡単な例を見てみましょう。
パッケージcom.cdai.orm.hibernate.query; java.util.arraysをインポートします。 java.util.listをインポートします。 org.hibernate.criteriaをインポートします。 Import org.hibernate.query; org.hibernate.sessionをインポートします。 Import org.hibernate.sessionFactory; Import org.hibernate.cfg.annotationConfiguration; Import org.hibernate.criterion.criterion; Import org.hibernate.criterion.example; org.hibernate.criterion.Expressionをインポートします。 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();セッションセッション= sessionfactory.opensession(); // 1.hql query query = session.createquery( "a.id =:id"); query.setlong( "id"、1);リスト結果= query.list(); for(object row:result){system.out.println(row); } // 2.qbc基準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アカウントの例= new Account(); example.setbalance(100); result = session.createCriteria(account.class)。追加(example.create(例))。リスト(); for(object row:result){system.out.println(row); } // 4.sql query = session.createsqlquery( "tb_account order by col_id desc"から上位10 *を選択します "); result = query.list(); for(object row:result){system.out.println(arrays.tostring((object [])row)); } session.close(); }}hibernate:col1_0_としてaccount0_.col_idを選択し、account0_.col_balance as col2_0_からtb_account account0_からcol2_0_ col2_0_ここで、account0_.col_id =?account [id = 1、balance = 100] hibernate:col1_0_0_からthis_.col_idを選択します。 this_.col_id =?account [id = 2、balance = 100] hibernate:this_.col_idをcol1_0_0_、this_.col_balance as col2_0_0_ from tb_account this_where(this_.col_balance =?)アカウント[id = 1、バランス= 100] col_id desc [2、100] [1、100]
ログから、生成されたSQLステートメントに対するHibernateの制御を明確に確認できます。選択する特定のクエリメソッドは、特定のアプリケーションによって異なります。