SpringJDBCでトランザクションを扱うには以下2種類の方法がある。
Spring設定ファイルを利用する場合、AOPの定義を記述する。アノテーションを利用する場合、プログラムに@Transactionalを記述する。今回は両方のサンプルを作成してみる。
SpringJDBCでトランザクションを扱う場合は、Springのjar以外に以下のjarが必要。
両方ともMaven Repositoryのサイト(http://mvnrepository.com/)からダウンロードできる。(それぞれ"aopalliance"、"AspectJ Weaver"などで検索すると表示される。)
アノテーションペースの場合、「aspectjweaver.jar」は不要のようである。一方設定ファイルでAOPを使用する場合は必要で、「aspectjweaver.jar」がクラスパスにないと以下の実行時エラーになる。
Caused by: java.lang.ClassNotFoundException: org.aspectj.weaver.reflect.ReflectionWorld$ReflectionWorldException
at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
... 38 more
テストで使用するDB(sampledb)を用意する。DBにはsampletblテーブルが存在し、2件データが入っている状態。
mysql> select * from sampletbl;
+------+-------+
| id | name |
+------+-------+
| 1 | Bill |
| 2 | Kelly |
+------+-------+
2 rows in set (0.00 sec)
mysql>
Springの設定ファイルを作成する。
④~⑥がトランザクションを使用するための定義。また<tx:advice>と<aop:config>を使うので、先頭にtx、aop用の宣言も必要。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<-- ①component-scanの定義 -->
<context:component-scan base-package="test" />
<-- ②データソースの定義 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource" >
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost/sampledb" />
<property name="username" value="testuser" />
<property name="password" value="testpassword" />
</bean>
<-- ③JdbcTemplateクラス -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg ref="dataSource" />
</bean>
<-- ④トランザクション管理クラス -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<-- ⑤AOPのAdvice定義(トランザクション属性の定義) -->
<tx:advice id="txAdvice" transaction-manager="txManager" >
<tx:attributes>
<tx:method name="*" isolation="READ_COMMITTED" />
<tx:method name="*" propagation="REQUIRED" />
<tx:method name="*" rollback-for="RuntimeException" />
</tx:attributes>
</tx:advice>
<-- ⑥AOPのPointcut定義(トランザクション適用の条件) -->
<aop:config>
<aop:advisor pointcut="execution(* test.*.*(..))" advice-ref="txAdvice" />
</aop:config>
</beans>
[説明]
①~③はトランザクション機能固有の定義ではなく、データベースを扱うための定義などのため説明は省略。詳しくはこちらのページ参照。
④トランザクション管理クラス
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
トランザクション管理クラスのbean定義。class属性にはDataSourceTransactionManagerクラスを指定する。また②のデータソース(dataSource)を参照させる。
⑤AOPのAdvice定義(トランザクション属性の定義)
<tx:advice id="txAdvice" transaction-manager="txManager" >
<tx:attributes>
<tx:method name="*" isolation="READ_COMMITTED" />
<tx:method name="*" propagation="REQUIRED" />
<tx:method name="*" rollback-for="RuntimeException" />
</tx:attributes>
</tx:advice>
AOPのAdvice定義で、分離レベルやロールバック例外などトランザクションの属性を設定する。設定できる属性と値は以下のとおり。なおtransaction-manager属性には④のid属性と同じ値を設定する。但し④のid属性が「transactionManager」の場合は、省略可能。
属性名 | 説明 | 値 | 説明 |
---|---|---|---|
name | メソッド名 | メソッド名 | 各種属性を適用するメソッド名を記述する。ワイルドカードの使用が可能。 |
isolation | 分離レベル | DEFAULT | データストア(つまりDB)の分離レベルに従う |
READ_UNCOMMITTED | 未コミット読み取り。未コミットのデータが読み取り可能。 | ||
READ_COMMITTED | コミット読み取り。コミット済みのデータのみ読み取り可能 | ||
REPEATABLE_READ | 反復読み取り可能。コミット済みのデータのみ読み取り可能で、同じ問い合わせの場合、新たに挿入されたレコード以外は必ず同じ結果になる。 | ||
SERIALIZABLE | シリアライズ可能。コミット済みのデータのみ読み取り可能で、同じ問い合わせの場合、必ず同じ結果になる。 | ||
propagation | トランザクション の伝搬 | REQUIRED | トランザクションが存在する場合、そのトランザクションを利用する。トランザクションが存在しない場合、新たなトランザクションを開始する。 |
SUPPORTS | トランザクションが存在する場合、そのトランザクションを利用する。トランザクションが存在しない場合、トランザクションを利用しない。 | ||
MANDATORY | トランザクションが存在する場合、そのトランザクションを利用する。トランザクションが存在しない場合、例外スロー。 | ||
NESTED | トランザクションが存在する場合、ネストされたトランザクションを開始する。トランザクションが存在しない場合、新たなトランザクションを開始する。ネストされたトランザクションとは親のトランザクションと同じトランザクションだが、部分的にロールバック可能なトランザクションのこと。セーブポイントを使ってるのと同じ。 | ||
REQUIRES_NEW | 新しいトランザクションを開始する。 | ||
NOT_SUPPORTED | トランザクションを利用しない。 | ||
NEVER | トランザクションを利用しない。既にトランザクションが存在する場合、例外スロー。 | ||
read-only | 読み取り専用 | true | データの更新不可。検索のみ可能。 |
false | データの更新も検索も可能。 | ||
timeout | タイムアウト | 秒数 | トランザクションのタイムアウト時間(秒数)。指定しなかった場合、トランザクションシステムに依存する。 |
rollback-for | ロールバック例外 | 例外クラス | Springではロールバックしたい場合、例外クラスをスローさせる。ここではロールバックさせたい例外クラスを記述する。カンマ区切りで複数指定可能。 |
no-rollback-for | ロールバック対象外例外 | 例外クラス | rollback-forの逆で、ロールバックさせたくない例外クラスを記述する。カンマ区切りで複数指定可能。 |
⑥AOPのPointcut定義(トランザクション適用の条件)
<aop:config>
<aop:advisor pointcut="execution(* test.*.*(..))" advice-ref="txAdvice" />
</aop:config>
トランザクションを利用するメソッドを定義する。戻り値、パッケージ、メソッド名、引数を記述する。ワイルドカードなども使用可能。advice-ref属性には⑤のid属性を設定する。
まずDBに対して更新処理を行うDAOクラス(SampleDao.java)を作成する。データ確認用に検索メソッドも用意。
package test;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class SampleDao {
@Autowired
private JdbcTemplate jdbcTemplate;
// ①更新処理
public void update(){
jdbcTemplate.update("update sampletbl set name='John' where id='1'");
jdbcTemplate.update("update sampletbl set name='Sean' where id='2'");
return;
}
// ②検索処理
public List select(){
List ret = jdbcTemplate.queryForList("select name from sampletbl", String.class);
return ret;
}
}
①更新メソッド。2件更新する。
②検索メソッド。データ確認用に作成。
次に上記で作成したDAOを実行するクラス(SpringSample.java)を作成する。
package test;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringSample {
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
SampleDao dao = context.getBean(SampleDao.class);
try{
// ①更新処理
dao.update();
System.out.println("更新成功");
}catch (RuntimeException e){
System.out.println("更新失敗");
}
// ②検索処理
List names = dao.select();
System.out.println(names);
}
}
①DAOの更新メソッドを呼ぶ。更新失敗に備えて、RuntimeExceptionをキャッチさせる。
②DAOの検索メソッドを呼び、テーブルの中身を確認する。
実行すると下記のように2件とも更新されているのがわかる。
更新成功
[John, Sean]
これだけだとトランザクションが効いてるのか判断つかないので、updateメソッドで例外を発生させてみる。ソースを下記のように修正する。
package test;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class SampleDao {
@Autowired
private JdbcTemplate jdbcTemplate;
// 更新処理
public void update(){
jdbcTemplate.update("update sampletbl set name='John' where id='1'");
jdbcTemplate.update("update sampletbl set name='Sean' where id='2'");
// RuntimeExceptionをスローさせる
if(true){
throw new RuntimeException();
}
return;
}
// 検索処理
public List select(){
List ret = jdbcTemplate.queryForList("select name from sampletbl", String.class);
return ret;
}
}
下記出力結果のとおり、例外がスローされたことにより、データがロールバックされていることが確認できる。
更新失敗
[Bill, Kelly]
アノテーションベースの場合、AOP定義が不要となるため、前節の⑤⑥を削除。代りに「<tx:annotation-driven />」を記述。またトランザクション管理クラスのid属性は「transactionManager」にする。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">
<-- ①component-scanの定義 -->
<context:component-scan base-package="test" />
<-- ②データソースの定義 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource" >
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost/sampledb" />
<property name="username" value="testuser" />
<property name="password" value="testpassword" />
</bean>
<-- ③JdbcTemplateクラス -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg ref="dataSource" />
</bean>
<-- ④トランザクション管理クラス -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<-- ⑤アノテーション利用の定義 -->
<tx:annotation-driven />
</beans>
DAOにはトランザクションをかけたいメソッドに@Transactionalアノテーションを記述する。指定できるパラメータは前述の設定ファイルで実施する方法の<tx:attributes>の属性とほぼ一緒。
package test;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class SampleDao {
@Autowired
private JdbcTemplate jdbcTemplate;
// ①更新処理
@Transactional(isolation=Isolation.READ_COMMITTED,
rollbackFor=RuntimeException.class,
propagation=Propagation.REQUIRED)
public void update(){
jdbcTemplate.update("update sampletbl set name='John' where id='1'");
jdbcTemplate.update("update sampletbl set name='Sean' where id='2'");
return;
}
// ②検索処理
public List select(){
List ret = jdbcTemplate.queryForList("select name from sampletbl", String.class);
return ret;
}
}
その他は変更なし。出力結果は省略。