doma2を使ってみた (2) - DI Containerを使う場合 -
公式ドキュメント: http://doma.readthedocs.org/ja/latest/config/#id21
参考: http://qiita.com/nyasba/items/1e22c2401f3849f9071d
前回にも記述した通り前回ではdomaで使うConfig実装なAppConfig上でDataSource等の設定をそのまま埋め込んでる。でそういう所をDI Containerに設定されているDataSource等を依存性注入して使うような形の場合には設定がいろいろ異なるっていうことでやってみた(使うDI ContainerはSpring Framework)
build.gradle
apply plugin: "java"
processResources.destinationDir = compileJava.destinationDir
compileJava.dependsOn processResources
repositories {
mavenCentral()
}
dependencies {
runtime "mysql:mysql-connector-java:+"
runtime "org.springframework:spring-jdbc:+"
// AOPでトランザクション情報をaspectする場合は必要
// あとspring-aopは依存性に入れてないけど結局入るので使うなら入れてた方が良いのかと
runtime "org.aspectj:aspectjweaver:+"
compile "org.seasar.doma:doma:2.1.0"
compile "org.springframework:spring-beans:+"
compile "org.springframework:spring-context:+"
compile "org.springframework:spring-tx:+"
testCompile "org.springframework:spring-test:+"
testCompile "junit:junit:+"
testCompile "org.hamcrest:hamcrest-all:+"
}
Spring Frameworkを使うのとそれとSpring JDBCな設定を使うのとSpring Transactionを使うのでそれに必要な依存性を追加するだけ
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="user" />
<property name="password" value="pass" />
</bean>
</constructor-arg>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven />
<!-- AOPでトランザクション情報を設定しない場合には以降は恐らく必要ない -->
<tx:advice id="txDefault" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" rollback-for="Exception" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:advisor advice-ref="txDefault" pointcut="execution(* sample.service.*Service.*(..))" />
</aop:config>
</beans>
まぁDataSource関連とTransaction関連の設定をやっておく
AppConfig.java
package sample;
import javax.sql.DataSource;
import org.seasar.doma.jdbc.Config;
import org.seasar.doma.jdbc.dialect.Dialect;
import org.seasar.doma.jdbc.dialect.MysqlDialect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Autowired;
@Configuration
public class AppConfig {
@Autowired
private DataSource dataSource;
@Bean
public Config domaConfig() {
return new Config() {
@Override
public Dialect getDialect() {
return new MysqlDialect();
}
@Override
public DataSource getDataSource() {
return dataSource;
}
};
}
}
な感じで@AutowiredでDataSourceなインスタンスを引っ張ってきてBean登録したConfigの実装でそれを返すようにするような感じ。上記参考ではgetSqlFileRepositoryとかも実装しているけど、デフォルト実装を使えば良いんだと思われるのであえてオーバーライドはしてないけど必要なら実装しておくと良いんじゃないかと。あんま事細かくチェックしてないので何か問題起きそうな気もしなくもないんだけど
DomaRepository.java
package sample.dao;
import org.seasar.doma.AnnotateWith;
import org.seasar.doma.Annotation;
import org.seasar.doma.AnnotationTarget;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@AnnotateWith(annotations = {
// 生成されたDAO実装クラスに@Component
@Annotation(target = AnnotationTarget.CLASS, type = Component.class),
// 生成されたDAO実装クラスのコンストラクタに@Autowired
@Annotation(target = AnnotationTarget.CONSTRUCTOR, type = Autowired.class)
})
public @interface DomaRepository {
}
簡単に言うとDAOオブジェクトなのがSpringでAutowired出来るようにするにはそれに応じたアノテーションなりを実装するか自前で登録するかのどっちかをしなきゃならないんだけど、DAO実装クラスは自動生成されるのでそれに応じたアノテーションを付与するようにしなきゃならない。でDAO毎にこの設定が必要になるのだけど、毎回毎回DAOに設定するのもめんどいと思うのでこういうアノテーションインターフェースを書いておいてDAOインターフェースでアノテーション定義しておけば良い。それは以下に書くので
ちなみに今回はSpring Frameworkを使ってるからこうなってるけど、javax.injectなのを使うのならtype属性で指定するクラスを変える必要があるかと
SampleDao.java
package sample.dao;
import java.util.List;
import org.seasar.doma.Dao;
import org.seasar.doma.Insert;
import org.seasar.doma.Select;
import sample.AppConfig;
import sample.entity.Sample;
@DomaRepository
@Dao
public interface SampleDao {
@Select
List<Sample> findAll();
@Select
Sample find(int id);
@Insert
int save(Sample s);
}
先ほど書いた@DomaRepositoryを追加するだけ。@Daoのconfig属性は実装クラスのコンストラクタで@Autowiredされるので必要無いんじゃないかと
SampleService.java
package sample.service;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import sample.dao.SampleDao;
import sample.entity.Sample;
@Service
public class SampleService {
@Autowired
private SampleDao dao;
public List<Sample> findAll() {
return dao.findAll();
}
public Sample find(int id) {
return dao.find(id);
}
@Transactional(rollbackFor=Exception.class)
public int save(Sample sample) throws Exception {
// rollbackforで指定した例外が送出されるとロールバックされる
return dao.save(sample);
}
}
んまぁ@AutowiredでDAOを注入して使うオブジェクトなだけ(@Repositoryでも良かったんじゃないのかって思ったけど、そこ詳しくないので今回はパス)。@Transactionalをつけたメソッドをコールする場合にはトランザクション処理が行われるけど、applicationContext.xmlで指定したようにトランザクション情報に関わるAOPをしていない場合にはアノテーションの属性で指定すれば良い
SampleDaoTest.java
package sample.dao;
import java.util.List;
import org.junit.Before;
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.service.SampleService;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class SampleDaoTest {
@Autowired
SampleService service;
@Test
public void test_findAll() {
List<Sample> samples = service.findAll();
assertThat(samples.size(), greaterThan(0));
Sample sample = samples.get(0);
assertThat(sample, notNullValue());
assertThat(sample.getId(), is(1));
assertThat(sample.getName(), is("hoge"));
}
@Test
public void test_find() {
Sample sample = service.find(1);
assertThat(sample, notNullValue());
assertThat(sample.getName(), is("hoge"));
}
@Test
public void test_save() throws Exception {
Sample sample = new Sample();
sample.setName("hoge fuga foobar");
service.save(sample);
}
}
ってな感じでテスト書けば良いんだけど、SampleServiceのsaveメソッドでDAO側のsaveをコールしたあとに例外等が送出された場合、@Transactionalによるトランザクション情報に応じてトランザクションがロールバックされる
まぁざっくりとざらーっと書いたけど、domaとSpring Frameworkを連携するような感じでやる場合はこんな所かと
んまぁSpring Bootを使う場合な要件においては上記参考先がSpring Bootを使う場合のテクになってるのでそれ参考にすれば良いかと
追記
上記で<tx:annotation-driven />と<aop:advice>を利用した場合に@Repositoryを使うと問題が起きる。その件は近い内に記事書く