Help us understand the problem. What is going on with this article?

Spring4勉強会 第五回

More than 3 years have passed since last update.

Spring4勉強会 第五回

前回、MyBatisと連携し、一通りのDMLは体感しました。
今回は「Transaction制御」の紹介です。

早速

環境構築

トランザクション制御をMySQLで叶えるために、二つの設定をして頂きます。

  1. auto_commitのOFF
  2. @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等を記述して参りましょう!!

今回は動作確認するために、以下のような処理を記述します。

  1. GETパラメータで指定されたIDを受取、delete処理を行う(正常削除)
  2. 続いてinsert処理を行うが、存在しないテーブルに対してinesrtする(insert失敗・かつエラー画面表示)
  3. 1の削除処理がrollbackされる
  4. http://localhost:8080/SpringKenshu/test/にアクセスし、1で消したはずのIDが生き残っていることを確認する。

▼画面イメージ

tran.png


まずはID27が存在することを確認
続いてhttp://localhost:8080/SpringKenshu/test/transaction/27にアクセスし、
IDが27のtestを削除しようとします。

inserterror.png


しかし、deleteが成功しますが、insert時に「testestなんてテーブルは存在しない」というエラーでこけます。
そして、その後http://localhost:8080/SpringKenshu/test/にアクセスし、1で消したはずのIDが生き残っていることを確認します。

tran.png


早速実装してみましょう。

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

今回は以上です。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away