Top / Hibernate / Springを使ってトランザクション処理を記述する

Hibernateとトランザクション管理に関する課題

HibernateベースのDAOを作った場合に、トランザクション処理をどのように記述するかが問題になることがあります。

たとえば下記のような二つのテーブル

schema.png

に対してそれぞれMemberDAOとWorkGroupDAOを作ると思います。さらにそのDAOを使用するビジネスメソッド、たとえば

bl.png

なんてのが定義されると思います。

このとき、ある処理の場合はメソッド単位でトランザクション管理し、ほかの処理の場合は複数のメソッドにまたがってトランザクション管理したい場合があります。上の例だと、

  • addMemberはMemberを登録する
  • addGroupはWorkGroup?を登録する
  • addはMemberをGroupに追加する。引数のメンバーがいなかったらaddMemberでMemberを登録する。引数のグループなかったら、例外をThrowする

とした場合、

  • メンバーを登録しようとしてaddMemberを呼び出した場合は、addMember内でトランザクションが開始され、終了すればいい。
  • add内でサブルーチン(<-古っ)としてaddMemberが呼ばれる場合は、addでトランザクションが開始され、終了されるのでaddMemberはすでにあるトランザクションに参加すればよい

となります。一つめの例だとaddMember内でトランザクションの開始・終了の処理を書けばよいですが、二つめの例を考えるとaddMember内でトランザクション開始と終了の処理をしちゃうとまずいわけですね。つまりあるメソッドに対してトランザクションのスコープ(境界?)が場面によって異なることがあるのですが、Springではこの辺をトランザクション処理を宣言的に記述することで解決しています。

実際にやってみる

この辺を考えるために具体例で行きます。今回考えるクラス群の全体像は以下の通りです。例を単純にするために、一つのテーブルに対するトランザクション管理を考えます。

UserBL->UserDao->データベース

という構成になっていて、UserBLのトランザクション指定をどうやるか、ということですね。

before.png

対応するテーブルは以下の通り作成しました。

create table MKINO.USER_ATTR (
 USERID varchar2(100) not null,
 NAME varchar2(1000), primary key (USERID)
);

確認してみます。

-bash-3.00$ sqlplus mkino/xxxx
SQL*Plus: Release 10.2.0.1.0 - Production on 木 5月 3 21:51:40 2007
Copyright (c) 1982, 2005, Oracle.  All rights reserved.
Oracle Database 10g Express Edition Release 10.2.0.1.0 - Production
に接続されました。
SQL> desc user_attr;
 名前                                      NULL?    型
 ----------------------------------------- -------- ----------------------------
 USERID                                    NOT NULL VARCHAR2(100)
 NAME                                               VARCHAR2(1000)

SQL>

OKです。ちなみにこの辺のユーザは以下のように作ってます。あプロダクトはOracle 10g XEです。

-bash-3.00$ . /usr/lib/oracle/xe/app/oracle/product/10.2.0/server/bin/oracle_env.sh
-bash-3.00$ sqlplus / as sysdba
Oracle Database 10g Express Edition Release 10.2.0.1.0 - Production
に接続されました。
SQL> CREATE USER "MKINO"  PROFILE "DEFAULT"
     IDENTIFIED BY "xxxx" DEFAULT TABLESPACE "USERS"
     TEMPORARY TABLESPACE "TEMP"
     QUOTA UNLIMITED
     ON "USERS"
     ACCOUNT UNLOCK;  2    3    4    5    6

ユーザーが作成されました。
SQL> create role MKINO_ROLE;

ロールが作成されました。
SQL>  GRANT
 CREATE SESSION,
 CREATE ANY INDEX,
 CREATE ANY VIEW,
 CREATE ANY SYNONYM,
 CREATE ANY SEQUENCE,
 CREATE ANY TABLE,
 SELECT ANY TABLE,
 INSERT ANY TABLE,
 UPDATE ANY TABLE,
 DELETE ANY TABLE
 TO MKINO_ROLE;  2    3    4    5    6    7    8    9   10   11   12

権限付与が成功しました。
SQL>
GRANT MKINO_ROLE TO MKINO;
 SQL>
権限付与が成功しました。
SQL>
SQL> quit
Oracle Database 10g Express Edition Release 10.2.0.1.0 - Productionとの接続が切断されました。
-bash-3.00$

Springを使ってトランザクション処理を記述する

さて、SpringではBLのビジネスメソッドに対してトランザクション処理を宣言的に追加することができるといいましたが、以下のようにやります。

まず、もとのBLの名前をuserBLからuserBLTargetに変更し、userBLという名前のBean設定を新規に作成します。その新たなJavaBeans?の型はSpringが提供してくれるProxyクラスで、外部から見たインタフェースは元のuserBLと同じになります。つまり見た目は外部から見たら全く変わっていないことになります。

そのProxyのタグ付けですが、トランザクション設定や、ターゲットになるJavaBeans?(元のuserBLのこと)の指定などを記述しておきます。

さて処理の流れですが、実際BLを使うプログラムはuserBLというキーでSpringからJavaBeans?を取得するので、処理シーケンスは

BLを使うプログラム -> userBL

だったのが

BLを使うプログラム -> userBL -> userBLTarget

となります。

外部から見たら見た目は同じだけど、実際のBLの間に別のクラス(上のProxyクラスですね)が挟まれる用になります。結局、Springのタグ付けは以下のように変更されました。

bean.png

間に挟まれたPOJOは以下のようにSpringでInjectionされています。

<bean id="userBL" 
  class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
  <property name="transactionManager">
    <ref local="transactionManager" />
  </property>
  <property name="target"> ↓ターゲットにするPOJOを指定
    <ref bean="userBLTarget" />
  </property>
  <property name="transactionAttributes">
    <props>   ↓POJOのメソッドに対してトランザクションの設定を記述
      <prop key="*">PROPAGATION_REQUIRED</prop>
      <!-- <prop key="create">PROPAGATION_MANDATORY</prop> -->
    </props>
  </property>
</bean>

トランザクション管理方法について

さて使用する側から見えるPOJOですが、もともとはUserBLクラスだったのですが今は間に

org.springframework.transaction.interceptor.TransactionProxyFactoryBean

が挟まれている形となっています。このクラスを経由することでターゲットとなるUserBLクラスの各メソッドにトランザクションの指定を書くことができるというワケです。上の例は

  <property name="transactionAttributes">
    <props>   ↓POJOのメソッドに対してトランザクションの設定を記述
      <prop key="*">PROPAGATION_REQUIRED</prop>
    </props>
  </property>

となっていて、全てのメソッドをPROPAGATION_REQUIREDにする、という指定になっています。PROPAGATION_REQUIREDは、このBLのメソッドが呼ばれたときに、すでにトランザクションが開始されていたらそのトランザクションに参加する、開始されてなかったらトランザクションを開始する、という意味になります。つまりこのメソッドが呼ばれたときにトランザクションが開始されてなかったら、BLのメソッドのはじめでトランザクションが開始され、全ての処理が問題なく終わったら勝手にコミットする、ということをSpringがやってくれるわけです。これ、すごいことですね。

実際にBLのメソッドを呼び出してみる

実際にやってみます。Spring経由でBLを取得し、BLのcreateメソッドでデータベースにデータをInsertしています。

public class SpringMain {
  public static void main(String[] args) throws IOException {
    DOMConfigurator.configure("log4j.xml");
    ApplicationContext context = new FileSystemXmlApplicationContext(
        new String[] { "beans.xml", "hibernate-spring_test.xml" });
    String name = getUniqueKey();
    IUserBL bl = (IUserBL) context.getBean("userBL");
    bl.create(new UserAttr(name, "サンプルユーザ1"));
  }

  /**
   * PKを生成したいだけ。本質的な意味ナシ
   */
  private static String getUniqueKey() throws IOException {
    File temp = File.createTempFile("abc", "");
    temp.deleteOnExit();
    String name = temp.getName();
    return name;
  }
}

実行結果は以下の通り

org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean 
  definitions from file [T:\workspace_rad\SpringDBSamples\beans.xml]
org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean 
  definitions from file [T:\workspace_rad\SpringDBSamples\hibernate-spring_test.xml]
org.springframework.orm.hibernate3.LocalSessionFactoryBean - Building new Hibernate SessionFactory
org.hibernate.cfg.SettingsFactory - RDBMS: Oracle, version: Oracle Database 10g 
  Express Edition Release 10.2.0.1.0 - Production
org.hibernate.cfg.SettingsFactory - JDBC driver: Oracle JDBC driver, version: 10.2.0.1.0
org.hibernate.dialect.Dialect - Using dialect: org.hibernate.dialect.OracleDialect
org.hibernate.transaction.TransactionFactoryFactory - Using default transaction strategy (direct JDBC transactions)
org.hibernate.transaction.TransactionManagerLookupFactory - No TransactionManagerLookup 
  configured (in JTA environment, use of read-write or transactional second-level cache is not recommended)
org.springframework.orm.hibernate3.HibernateTransactionManager - Using DataSource
  [org.apache.commons.dbcp.BasicDataSource@6e1dec] of Hibernate SessionFactory for HibernateTransactionManager
org.springframework.orm.hibernate3.HibernateTransactionManager - Using transaction
  object [org.springframework.orm.hibernate3.HibernateTransactionManager$HibernateTransactionObject@dc86eb]
org.springframework.orm.hibernate3.HibernateTransactionManager - Creating new 
  transaction with name [nu.mine.kino.bl.IUserBL.create]
org.springframework.orm.hibernate3.HibernateTransactionManager - Opened new 
  Session [org.hibernate.impl.SessionImpl@e29820] for Hibernate transaction
org.springframework.orm.hibernate3.HibernateTransactionManager - Preparing JDBC 
  Connection of Hibernate Session [org.hibernate.impl.SessionImpl@e29820]
-----------------------------afterTransactionBegin
org.springframework.orm.hibernate3.HibernateTransactionManager - Exposing Hibernate transaction 
  as JDBC transaction [org.apache.commons.dbcp.PoolableConnection@1c286e2]
nu.mine.kino.bl.UserBL - create(UserAttr) - start
nu.mine.kino.bl.UserBL - create(UserAttr) - end
org.springframework.orm.hibernate3.HibernateTransactionManager - Triggering beforeCommit synchronization
org.springframework.orm.hibernate3.HibernateTransactionManager - Triggering beforeCompletion synchronization
org.springframework.orm.hibernate3.HibernateTransactionManager - Initiating transaction commit
org.springframework.orm.hibernate3.HibernateTransactionManager - Committing Hibernate transaction
  on Session [org.hibernate.impl.SessionImpl@e29820]
Hibernate: insert into MKINO.USER_ATTR (NAME, USERID) values (?, ?)
-----------------------------beforeTransactionCompletion
-----------------------------afterTransactionCompletion
org.springframework.orm.hibernate3.HibernateTransactionManager - Triggering afterCompletion synchronization
org.springframework.orm.hibernate3.HibernateTransactionManager - Closing Hibernate 
 Session [org.hibernate.impl.SessionImpl@e29820] after transaction
org.springframework.orm.hibernate3.SessionFactoryUtils - Closing Hibernate Session

なんのこっちゃって感じですけど、BLのメソッドが開始される際にトランザクションが開かれ、メソッドが終わった後にコミットが行われているのがわかると思います。ついでにHibernateのSessionとかも同じタイミングで開いたり閉じたりするんですね。

Runtime例外時は勝手にロールバック

次にメソッド内でわざとRuntime例外をスローした場合のログです。

nu.mine.kino.bl.UserBL - create(UserAttr) - start
org.springframework.orm.hibernate3.HibernateTransactionManager - Triggering beforeCompletion synchronization
org.springframework.orm.hibernate3.HibernateTransactionManager - Initiating transaction rollback
org.springframework.orm.hibernate3.HibernateTransactionManager - Rolling back Hibernate
  transaction on Session [org.hibernate.impl.SessionImpl@e29820]
Exception in thread "main" java.lang.RuntimeException: おー例外
-----------------------------afterTransactionCompletion
org.springframework.orm.hibernate3.HibernateTransactionManager - Triggering afterCompletion synchronization
org.springframework.orm.hibernate3.HibernateTransactionManager - Closing Hibernate
  Session [org.hibernate.impl.SessionImpl@e29820] after transaction
org.springframework.orm.hibernate3.SessionFactoryUtils - Closing Hibernate Session

Springが正しくロールバックしてくれています。自分たちでやる必要はないわけです。

ちなみにSpringではRuntime例外はロールバック、その他の普通の例外はコミット、というのがデフォルトの動きみたいです。先のメソッド指定で、普通の例外もロールバックみたいに挙動を変えることもできます。

その他のトランザクション指定

先のPROPAGATION_REQUIREDはあったらトランザクションに参加、なかったらトランザクションを開始する、でしたが他にも色々指定があります。もしかしてよく使われるのは PROPAGATION_MANDATORYでしょうか。これは、すでに開始されてなかったら例外、というものです。DAOとかをあやまってトランザクション開始しないまま呼び出したりしないようにする場合などに使えるかな。。

やってみます。先のxmlの指定に

  <property name="transactionAttributes">
    <props>   ↓POJOのメソッドに対してトランザクションの設定を記述
      <prop key="*">PROPAGATION_REQUIRED</prop>
      <prop key="create">PROPAGATION_MANDATORY</prop>
             ↑createというメソッドだけは、PROPAGATION_MANDATORYにする
    </props>
  </property>

を追加してやってみます。

Exception in thread "main" org.springframework.transaction.IllegalTransactionStateException: 
    Transaction propagation 'mandatory' but no existing transaction found

という例外がスローされました。

トランザクション指定をしない場合は、、?

最後に

  <property name="transactionAttributes">
    <props>
    </props>
  </property>

などとしてトランザクション指定をしなかった場合はどうなるのでしょうか。実行結果配下の通りです。

nu.mine.kino.bl.UserBL - create(UserAttr) - start
org.springframework.orm.hibernate3.SessionFactoryUtils - Opening Hibernate Session
-----------------------------onSave
-----------------------------preFlush
Hibernate: insert into MKINO.USER_ATTR (NAME, USERID) values (?, ?)
-----------------------------postFlush
org.springframework.orm.hibernate3.SessionFactoryUtils - Closing Hibernate Session
nu.mine.kino.bl.UserBL - create(UserAttr) - end

当たり前ですが、トランザクションが開始されないで処理がなされてます。Hibernateのセッションは開いて閉じてをちゃんとやってくれてますね。

関連情報


この記事は

選択肢 投票
おもしろかった 47  
そうでもない 6  

Top / Hibernate / Springを使ってトランザクション処理を記述する

現在のアクセス:51166


添付ファイル: filebefore.png 796件 [詳細] filebl.png 760件 [詳細] fileschema.png 787件 [詳細] filebean.png 1038件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2017-09-20 (水) 11:31:58 (5d)