Spring4勉強会 第五回
前回、MyBatisと連携し、一通りのDMLは体感しました。
今回は「Transaction制御」の紹介です。
- 環境構築
- 動かしてみる
- @Transactionalについて
早速
環境構築
トランザクション制御をMySQLで叶えるために、二つの設定をして頂きます。
- auto_commitのOFF
- @Transactionalの有効化
どちらの作業も「mvc-config.xml」に追記するだけです。
以下、適用後のxmlファイルになります。
<?xml version="1.0" encoding="UTF-8"?>
<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:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd">
<context:component-scan base-package="jp.co.kenshu" />
<mvc:annotation-driven />
<!-- 入力値検証の日本語リソース設定 -->
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:ValidatorMessages" />
</bean>
<!-- ControllerからViewをforwardする際にどこのViewを検索するかの設定 -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- Example: a logical view name of 'showMessage' is mapped to '/WEB-INF/jsp/showMessage.jsp' -->
<property name="prefix" value="/WEB-INF/view/" />
<property name="suffix" value=".jsp" />
</bean>
<!-- DBの設定 -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost/test" />
<property name="username" value="xxx" />
<property name="password" value="xxx" />
+ <!-- デフォルトでautoコミットがONなのでOFFにする -->
+ <property name="defaultAutoCommit" value="false" />
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="jp.co.kenshu.mapper" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
<!-- どこのパッケージをmapperとして認識するかの設定 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="jp.co.kenshu.mapper" />
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
+ <!-- @Transactionalを使用するために定義 -->
+ <tx:annotation-driven />
</beans>
「+」が付いている箇所が今回追加した設定箇所です。
+ <!-- デフォルトでautoコミットがONなのでOFFにする -->
+ <property name="defaultAutoCommit" value="false" />
コメントに記述した通りなのですが、今回はMySQLを使用しています。
MySQLはデフォルトでAUTO_COMMITがONになっているので、JDBCコネクション時にOFFにします。
これをしないと、トランザクションが開始しません。
+ <!-- @Transactionalを使用するために定義 -->
+ <tx:annotation-driven />
この記述のおかげで、今回やろうとしている「アノテーションだけでTransaction制御を行う」が実現可能になります。
これがないと、アノテーションは記述できるけど、機能しない事態に陥ります。
環境構築は以上です。
動かしてみる
準備ができたので、動作確認用のController、Service、Dao等を記述して参りましょう!!
今回は動作確認するために、以下のような処理を記述します。
- GETパラメータで指定されたIDを受取、delete処理を行う(正常削除)
- 続いてinsert処理を行うが、存在しないテーブルに対してinesrtする(insert失敗・かつエラー画面表示)
- 1の削除処理がrollbackされる
- http://localhost:8080/SpringKenshu/test/にアクセスし、1で消したはずのIDが生き残っていることを確認する。
▼画面イメージ
まずはID27が存在することを確認
続いてhttp://localhost:8080/SpringKenshu/test/transaction/27にアクセスし、
IDが27のtestを削除しようとします。
しかし、deleteが成功しますが、insert時に「testestなんてテーブルは存在しない」というエラーでこけます。
そして、その後http://localhost:8080/SpringKenshu/test/にアクセスし、1で消したはずのIDが生き残っていることを確認します。
早速実装してみましょう。
Controllerの実装
TestControlerに以下を追加
// トランザクションのサンプル
@RequestMapping(value = "/test/transaction/{id}", method = RequestMethod.GET)
public String testTransaction(Model model, @PathVariable int id) {
TestDto dto = new TestDto();
dto.setId(id);
testService.deleteAllAndInsert(dto);
return "redirect:/test/";
}
続いてService
TestServiceに以下を追加
// transactionのテスト
@Transactional
public void deleteAllAndInsert(TestDto dto) {
int delCount = testMapper.deleteTest(dto.getId());
Logger.getLogger(TestService.class).log(Level.INFO, "削除件数は" + delCount + "件です。");
// ここのinsertは失敗する。deleteがrollBackされるかどうか。
int insCount = testMapper.insertFailTest(null);
Logger.getLogger(TestService.class).log(Level.INFO, "挿入件数は" + insCount + "件です。");
}
Serviceはdeleteを行ってから、insertを行ってます。
delete成功時にLogを吐き出しているので、ここで成功しているかも確認できます。
@Transactional
このアノテーションがあるメソッドはトランザクション管理が発生します。
アノテーションはクラス単位でも付与できるため、クラス全体でトランザクション制御も設定可能です。
続いて、失敗するためのDaoを書きます。
TestMapper.javaに以下を追記。
// トランザクションテスト用の失敗メソッド
int insertFailTest(Object object);
TestMapper.xmlにも以下を追記
<!-- トランザクション失敗用insert -->
<insert id="insertFailTest" parameterType="String">
insert into testtest(name)
values (#{name})
</insert>
これを見ると、意図的に
insert into testtest
と、「testest」と存在しないテーブルを指定しています。
これで毎回必ずinsertに失敗します!!
準備は以上です。
手順通りに実際に動かしてみてください。
deleteは成功しているのに、insertでこけるので、@Transactionalが働き、ID27が削除されていないことがわかるかと思います。
Serviceのこの記述が全てですね。
@Transactional
public void deleteAllAndInsert(TestDto dto) {
たったアノテーションの記述一行でトランザクション処理が実現できました。
@Transactionalについて
@Transactionalを記述しただけで、トランザクション制御が実現可能になりました。
クラス単位でも、メソッド単位でも指定できます。
細かい挙動の設定をするにはリファレンスを参照してください。
http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/transaction/annotation/Transactional.html
今回は以上です。