doma2を使ってみた (3) - @Repositoryを使う -
doma2の機能ではなくSpring Frameworkの@Repositoryで定義した場合どうなるのか気になったのでやってみた
SampleRepository.java
正式的?だと@Repositoryを付けるのはインターフェースだという概念があるっぽいけど、めんどくさいので普通にクラスにつけちゃう
package sample.repository;
import java.util.List;
import org.springframework.stereotype.Repository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import sample.dao.SampleDao;
import sample.entity.Sample;
@Repository
public class SampleRepository {
@Autowired
private SampleDao dao;
public List<Sample> findAll() throws Exception {
List<Sample> samples = dao.findAll();
if (samples.size() <= 0) throw new Exception();
return samples;
}
public Sample find(int id) {
return dao.find(id);
}
@Transactional
public int save(Sample sample) throws Exception {
int status = dao.save(sample);
// わざと例外が起きるようにしてrollbackされるかを確認する
if (status > 0) {
throw new Exception("error");
}
return status;
}
}
まぁ@Repositoryを付けてDAOとのやりとりをした結果を返すだけのしょうもないのを作っておく(前回は@Serviceにそういうのをやってたけど、あれはあれで微妙なんじゃないかって思う所もあるので)
でこの状態のまま前回のテストをちょいと書き換えてテストを実行する
SampleDaoTest.java
package sample.dao;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.beans.factory.annotation.Autowired;
import sample.entity.Sample;
import sample.repository.SampleRepository;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class SampleDaoTest {
@Autowired
SampleRepository dao;
@Test
public void test_save() throws Exception {
Sample sample = new Sample();
sample.setName("hoge fuga foobar");
// 問題がなければこれで例外が送出されてrollbackされる
dao.save(sample);
}
}
まぁsave側でExceptionをスローしているからテストは落ちる。でもその例外が
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
となる。詳解な原因がちゃんと特定できた訳じゃないのだけど、恐らくはTransactionInterceptor等によるトランザクションが重なってしまっているのではないかと。要は<tx:annotation-driven />によるTransactionInterceptorと<aop:advice/>によるTransactionInterceptorによってトランザクションが重なりメソッドで例外を創出するとsetRollbackOnlyはしているので問題なくロールバックはされるが結果的にその上のトランザクションスコープによってcommit実行しようとして結果上記のような例外が送出されてしまうのではないかという予測(あくまで予測なので確証はありません)
っていうことでこれを回避するには
- <tx:annotation-driven />を止める (@Transactionalは効かなくなる?)
- <aop:advice>によるトランザクション属性のインターセプトをしない (@Transactionalで属性を自前で定義する必要性が出てくる)
のどっちかをやれば良い
applicationContext.xml
<?xml version="1.0" ?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx ="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:annotation-config />
<context:component-scan base-package="sample" />
<bean id="dataSource" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
<constructor-arg>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/sample" />
<property name="username" value="kinjouj" />
<property name="password" value="1234" />
</bean>
</constructor-arg>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven />
</beans>
んまぁ<aop:advice>を使わないので@Transactionalには適切な属性を設定する必要がある
SampleRepository.java (修正)
package sample.repository;
import java.sql.SQLException;
import java.util.List;
import org.springframework.stereotype.Repository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.annotation.Propagation;
import sample.dao.SampleDao;
import sample.entity.Sample;
@Repository
public class SampleRepository {
@Autowired
private SampleDao dao;
public List<Sample> findAll() throws Exception {
List<Sample> samples = dao.findAll();
if (samples.size() <= 0) throw new Exception();
return samples;
}
public Sample find(int id) {
return dao.find(id);
}
// 属性を正しく設定しないと例外が送出されて正しくロークバックされないなる
@Transactional(rollbackFor=Exception.class, propagation=Propagation.REQUIRED)
public int save(Sample sample) throws Exception {
int status = dao.save(sample);
if (status > 0) {
throw new Exception("error");
}
return status;
}
}
てな感じで実行すれば上記のUnexpectedRollbackExceptionは発生しなくなる。一応、上記でのエラーの時点でTransaction PropagationをREQUIREDからREQUIRES_NEW等にすればこの問題は出なくなる。でも例えばひとつのメソッドで他のトランザクションを実行する処理をやる場合にその他のメソッド単位でトランザクションが管理される為部分的に例外等が発生しても部分的にロールバックされなくなるのではないかと
まぁとりあえずそういう問題でちょっとハマってたのでメモ。
ちなみに@Serviceならこういう問題は出てこない