Hibernateとトランザクション管理に関する課題 †HibernateベースのDAOを作った場合に、トランザクション処理をどのように記述するかが問題になることがあります。 たとえば下記のような二つのテーブル に対してそれぞれMemberDAOとWorkGroupDAOを作ると思います。さらにそのDAOを使用するビジネスメソッド、たとえば なんてのが定義されると思います。 このとき、ある処理の場合はメソッド単位でトランザクション管理し、ほかの処理の場合は複数のメソッドにまたがってトランザクション管理したい場合があります。上の例だと、
とした場合、
となります。一つめの例だとaddMember内でトランザクションの開始・終了の処理を書けばよいですが、二つめの例を考えるとaddMember内でトランザクション開始と終了の処理をしちゃうとまずいわけですね。つまりあるメソッドに対してトランザクションのスコープ(境界?)が場面によって異なることがあるのですが、Springではこの辺をトランザクション処理を宣言的に記述することで解決しています。 実際にやってみる †この辺を考えるために具体例で行きます。今回考えるクラス群の全体像は以下の通りです。例を単純にするために、一つのテーブルに対するトランザクション管理を考えます。 UserBL->UserDao->データベース という構成になっていて、UserBLのトランザクション指定をどうやるか、ということですね。 対応するテーブルは以下の通り作成しました。 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のタグ付けは以下のように変更されました。 間に挟まれた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のセッションは開いて閉じてをちゃんとやってくれてますね。 関連情報 †この記事は 現在のアクセス:59346 |