doma2を使ってみた (2) - DI Containerを使う場合 -

2015-03-14T00:00:00+09:00 Java doma

公式ドキュメント: 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を使うと問題が起きる。その件は近い内に記事書く

doma2を使ってみた (3) - @Repositoryを使う - doma2を使ってみた