SpringBoot+Doma2+Gradleを試してみた。


題材は「[はじめてのSpring Boot](http://www.amazon.co.jp/dp/4777518655)」にある顧客管理システム。
最近よく聞くDomaを試してみたかったのもあり、
JPAじゃなくDoma2、MavenじゃなくGradleで挑戦しました。


ちなみにまだRestAPIができただけです^^；

このエントリでは「[はじめてのSpring Boot](http://www.amazon.co.jp/dp/4777518655)」に載ってるところは説明省略します。
すごいわかりやすかったので買って読む価値はあると思いますよ。

## 環境

今回ためした環境は以下のとおりです。

Java8
SpringBoot1.1.9
Doma2.0.1
H2
Gradle2.2.1


## 構成

ソースはこちら。
https://github.com/nyasba/domaboot.git

一部省略してますが、こんなかんじです。

```
$ tree
.
├── build.gradle
└── src
    └── main
        ├── java
        │   └── com
        │       └── example
        │           ├── App.java
        │           ├── AppConfig.java
        │           ├── api
        │           │   ├── CustomerRequest.java
        │           │   └── CustomerRestController.java
        │           ├── datasource
        │           │   ├── CustomerRepository.java
        │           │   └── DomaRepository.java
        │           ├── domain
        │           │   └── CustomerEntity.java
        │           └── service
        │               └── CustomerService.java
        └── resources
            ├── META-INF
            │   └── com
            │       └── example
            │           └── datasource
            │               └── CustomerRepository
            │                   ├── findAllOrderByName.sql
            │                   └── findById.sql
            ├── data.sql
            ├── logback.xml
            ├── schema.sql
            └── templates
                └── dummy.html
```

## Domaを使う

### DB接続設定

今回はH2database(メモリ)を使ってます。
接続先の定義はDataSourceで書くだけで簡単ですね。

[12/26修正]
Domaの返却するDataSourceはそのままだとSpringの管理外になるため、実行時例外発生時にロールバックされなくなるとのこと。TransactionAwareDataSourceProxyで包むように修正。

<blockquote class="twitter-tweet" data-conversation="none" lang="ja"><p><a href="https://twitter.com/nyasba">@nyasba</a> @ Transactionalをつけたメソッド内でinsertした後Runtime例外投げてもコミットされていると思います。Springの管理するトランザクションがスレッドローカルで結び付いているのでそこに参加させる必要があるはずです</p>&mdash; Toshiaki Maki (@making) <a href="https://twitter.com/making/status/548272660023496705">2014, 12月 26</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>


```AppConfig.java
package com.example;

import net.sf.log4jdbc.Log4jdbcProxyDataSource;
import org.seasar.doma.jdbc.Config;
import org.seasar.doma.jdbc.NoCacheSqlFileRepository;
import org.seasar.doma.jdbc.SimpleDataSource;
import org.seasar.doma.jdbc.SqlFileRepository;
import org.seasar.doma.jdbc.dialect.Dialect;
import org.seasar.doma.jdbc.dialect.H2Dialect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;


@Configuration
@EnableTransactionManagement
public class AppConfig {

    DataSource realDataSource(){
        SimpleDataSource dataSource = new SimpleDataSource();
        dataSource.setUrl("jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
        dataSource.setUser("sa");
        return dataSource;
    }

	// 12/26修正
    @Bean
    DataSource dataSource(){
        return new TransactionAwareDataSourceProxy(
                    new Log4jdbcProxyDataSource(
                            this.realDataSource()
                    )
        );
    }

    @Bean
    Dialect dialect(){
        return new H2Dialect();
    }

    @Bean
    SqlFileRepository sqlFileRepository(){
        return new NoCacheSqlFileRepository();
    }

    @Bean
    Config config(){
        return new Config() {
            @Override
            public DataSource getDataSource() {
                return dataSource();
            }

            @Override
            public Dialect getDialect() {
                return dialect();
            }

            @Override
            public SqlFileRepository getSqlFileRepository(){
                return sqlFileRepository();
            }
        };
    }
}
```

### Dao

DBにアクセスするためのInterfaceを作成します。
これらのクラスには@Daoをつけます。

ここで作ったメソッド１つ１つにSQLが対応します。

```CustomerRepository.java 
package com.example.datasource;

import com.example.domain.CustomerEntity;
import org.seasar.doma.*;
import org.seasar.doma.jdbc.Result;

import java.util.List;

@DomaRepository
@Dao
public interface CustomerRepository {

    @Select
    public List<CustomerEntity> findAllOrderByName();

    @Select
    public CustomerEntity findById(Integer id);

    @Insert
    public Result<CustomerEntity> insert(CustomerEntity customerEntity);

    @Update
    public Result<CustomerEntity> update(CustomerEntity customerEntity);

    @Delete
    public Result<CustomerEntity> delete(CustomerEntity customerEntity);
}
```

@DomaRepositoryはSpringのAutowiredに対応するためのアノテーションです。
CustomerServiceでAutowiredしているのでそのために必要になります。
（[SpringBootとDomaを連携する](http://shinsuke789.hatenablog.jp/entry/2014/07/31/123800)を参考に作りました）

```DomaRepository.java 
package com.example.datasource;

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 = {
    @Annotation(target = AnnotationTarget.CLASS, type = Component.class),
    @Annotation(target = AnnotationTarget.CONSTRUCTOR, type= Autowired.class)
})
public @interface DomaRepository {
}
```

### 検索処理

SQLは、
``` src/main/resources/META-INF/<package名>/<class名>/<method名>.sql ```
という形式で作成します。

例えば以下のメソッドの場合は

```
    @Select
    public CustomerEntity findById(Integer id);
```

このようなファイルを作ります。
```src/main/resources/META-INF/com/example/datasource/CustomerRepository/findById.sql```


中身はこのようになってます。
DomaのSQLは2WaySQLといって、そのまま実行できることが特徴です。


```findById.sql
select * from customers where id = /* id */1;
```


[参考] http://doma.seasar.org/reference/sqlfile.html
> SQLファイルは、SQL文を格納したテキストファイルで、Daoのメソッドにマッピングされます。 SQLのブロックコメント(/* */)や行コメント(--)を使用することで、バインド変数や動的なSQLのための条件分岐を表現できます。 SQLのツールでそのままそのSQLを実行できるように、バインド変数にはテスト用のデータを指定します。テスト用のデータは、実行時には使用されません。 たとえば、SQLファイルには次のようなSQL文が格納されます。

> select * from employee where employee_id = /* employeeId */99

> ここでは、ブロックコメントで囲まれた employeeIdがDaoインタフェースのメソッドのパラメータに対応し、 直後の 99はテスト用の条件になります。



### 結果を格納するオブジェクト（Entity）

結果はEntityに格納されます。
今回はオブジェクトをimmutableにしてます。

Selectで抽出したカラムとオブジェクトの変数との関係は@Entityのnamingで指定しています。
NamingType.SNAKE_UPPER_CASEを使うと、lastNameという変数にはLAST_NAMEというカラムの値が入ります。
@Tableのテーブル名はSQLファイルに書かれてるので検索に関しては使ってないです。

```CustomerEntity.java 
package com.example.domain;

import lombok.Getter;
import org.seasar.doma.*;
import org.seasar.doma.jdbc.entity.NamingType;

@Entity(naming = NamingType.SNAKE_UPPER_CASE, immutable = true)
@Table(name = "customers")
public class CustomerEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @SequenceGenerator(sequence = "seq_customers")
    @Getter
    private final Integer id;

    @Getter
    private final String lastName;

    @Getter
    private final String firstName;

    public CustomerEntity( String lastName, String firstName ){
        // DBの自動採番を使うためにnullを設定
        this.id = null;
        this.lastName = lastName;
        this.firstName = firstName;
    }

    public CustomerEntity( Integer id ,String lastName, String firstName ){
        this.id = id;
        this.lastName = lastName;
        this.firstName = firstName;
    }
}
```


### 登録／更新処理

登録／更新処理は専用のアノテーションを付けることで実現していますので、別にSQLを作る必要はありません。登録／更新したいEntityを引数で渡して実行するだけです。

```
    @Insert
    public Result<CustomerEntity> insert(CustomerEntity customerEntity);

    @Update
    public Result<CustomerEntity> update(CustomerEntity customerEntity);
```

ただ、IDの自動採番については少々はまりました。
Entityの中で@GeneratedValueを使って、H2のシーケンステーブルから採番するように記載していましたが、Entityをimmutableにしていたため、何を設定するかで困りました。

```
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @SequenceGenerator(sequence = "seq_customers")
    @Getter
    private final Integer id;
```

適当な値（1とか）ではダメで、nullを設定しておかないとInsert時に自動採番できないようです。
そのためコンストラクタを2つ用意しています。

Lombokの自動生成とも相性が悪く、IntelliJでは@AllArgsConstructor使えませんでした（Eclipseでは使えるらしい）

### ビルド

DomaのSQLファイルをコンパイルより前に出力先Dirにコピーするために以下のように依存関係を変える必要があるようです。

```build.gradle
processResources.destinationDir = compileJava.destinationDir
compileJava.dependsOn processResources
```

## まとめ

SpringBootでDoma,Gradleを試してみました。
Domaは思った以上に簡単に使えたので気に入りました d(ﾟ∀ﾟd)
簡単なアプリならこれで十分ですね。


「[SpringBootで作ったRestAPI/Webアプリのテストを書いてみた](http://qiita.com/nyasba/items/df826214ddaee0194a56)」につづく。