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

  • 56
    Like
  • 0
    Comment
More than 1 year has passed since last update.

題材は「はじめてのSpring Boot」にある顧客管理システム。
最近よく聞くDomaを試してみたかったのもあり、
JPAじゃなくDoma2、MavenじゃなくGradleで挑戦しました。

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

このエントリでは「はじめてのSpring Boot」に載ってるところは説明省略します。
すごいわかりやすかったので買って読む価値はあると思いますよ。

環境

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

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で包むように修正。

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をつけます。

ここで作ったメソッド1つ1つに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を連携するを参考に作りました)

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アプリのテストを書いてみた」につづく。