背景
ここ最近、データアクセスはS2JDBC(2WaySQL)で開発をしていたということもあり、Spring Data JPAのようにクラスファイルの中に文字列として"select column1,column2 from table・・・"みたいな書き方に違和感を覚えていました。そんな時にDoma2の存在を知る機会があったので、ちょっとやってみようかなと思い試してみました。
ちなみに、今の現場ではSpring Boot + MyBatisでやっていますが、
- パッと見た感じxmlでマッピング書くのがめんどくさそう
- xmlがjavaクラスへの依存を持ってしまっている(resultMapとか)、etc
- javaとSQLを分離したい
- そもそもあまり詳しくない
ってのもあり、やっぱ慣れてる2WaySQLだなーって思っています。
※他のO/Rマッパーをもう少し理解すれば考え方は変わってくるかもしれません。
なお、今回は上記で槇さんから紹介してもらったdoma-spring-boot-starterというAutoconfigureを利用します。
https://github.com/domaframework/doma-spring-boot
これにより、設定なしでSpring BootでDomaが利用できます。
Domaとは
一言で言うとO/Rマッパーです。Seasar2への依存はないので、Springや他のフレームワークからでも利用できます。もちろん2WaySQLも利用出来ます。
Domaのメジャーバージョンには1と2があり、
バージョン1は、Java6でコンパイルされ、 Java6、 Java7、 Java8上で動作します。
※http://doma.seasar.org/index.html
バージョン2は、Java8でコンパイルされ、 Java8上で動作します。
※http://doma.readthedocs.org/ja/stable/
環境
Windows 10
JDK 8
Spring Boot 1.3.0
STS 3.7.2
doma 2.5.1
H2DB 1.4.190
Maven 3.3.3
参考にしたもの
https://github.com/domaframework/spring-boot-sample
http://doma.seasar.org/
http://doma.readthedocs.org/ja/stable/
作るもの
書籍「はじめてのSpring Boot」を元ネタにした簡易なRestAPIを作ってみます。
なお、今回作ったサンプルソースは以下に置いてあります。
https://github.com/kenichi-nagaoka/Doma2-SpringBoot
構成
以下のような構成にします。
│ pom.xml
├─src
│ ├─main
│ │ ├─java
│ │ │ └─com
│ │ │ └─example
│ │ │ │ Application.java
│ │ │ │
│ │ │ ├─controller
│ │ │ │ CustomerController.java
│ │ │ │
│ │ │ ├─model
│ │ │ │ Customer.java
│ │ │ │
│ │ │ ├─repository
│ │ │ │ CustomerRepository.java
│ │ │ │
│ │ │ └─service
│ │ │ CustomerService.java
│ │ │
│ │ ├─resources
│ │ │ │ application.properties
│ │ │ │ data.sql
│ │ │ │ schema.sql
│ │ │ │
│ │ │ └─META-INF
│ │ │ └─com
│ │ │ └─example
│ │ │ └─repository
│ │ │ └─CustomerRepository
│ │ │ selectAll.sql
│ │ │
│ │ └─webapp
│ └─test
│ └─java
│ └─com
│ └─example
│ ApplicationTests.java
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>doma-cooperation</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<description>Demo project for Spring Boot - Doma</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.0.RELEASE</version>
</parent>
<repositories>
<repository>
<id>sonatype-snapshots</id>
<name>Sonatype Snapshots</name>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 冒頭でも述べた通り今回Domaとの連携は以下を利用しました、これを依存関係に追加するだけで推移的依存でDomaも使えます -->
<dependency>
<groupId>org.seasar.doma.boot</groupId>
<artifactId>doma-spring-boot-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
テーブル定義
Spring Bootはクラスパス直下(src/main/resources配下)に下記のファイルがあると起動時に自動で実行してくれます。
1.schema-(platform).sqlを実行
2.schema.sqlの実行
3.data-(platform).sqlの実行
4.data.sql
今回はH2DBを使ってインメモリでのデータ操作をします。なので、起動時にテーブル作成してデータ投入をしておきます。
CREATE table IF NOT EXISTS customers(id int primary key, name varchar(30));
INSERT INTO customers(id, name) VALUES(1, 'JAY-Z');
INSERT INTO customers(id, name) VALUES(2, 'NAS');
INSERT INTO customers(id, name) VALUES(3, 'DMX');
INSERT INTO customers(id, name) VALUES(4, '2PAC');
Application.java
エントリポイントとなるクラスです。特筆することは特になさそうですね。
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
CustomerController.java
コントローラクラスになります。/customersというURLとgetCustomersメソッドをマッピングしています。
package com.example.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.example.model.Customer;
import com.example.service.CustomerService;
@RestController
public class CustomerController {
@Autowired
CustomerService customerService;
@RequestMapping(value = "customers", method = RequestMethod.GET)
public List<Customer> getCustomers() {
return customerService.getCustomers();
}
}
CustomerService.java
ドメイン層のサービスにあたるものです。DAOであるCustomerRepositoryのselectAllメソッドを呼んでるだけです。
package com.example.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.model.Customer;
import com.example.repository.CustomerRepository;
@Service
@Transactional
public class CustomerService {
@Autowired
CustomerRepository customerRepository;
public List<Customer> getCustomers() {
return customerRepository.selectAll();
}
}
Customer.java
エンティティクラス(クエリの結果セットクラス)になります。
@Entityアノテーションを付与する必要がありますが、注意するべきところは「org.seasar.doma.Entity」を付与することです。@Entityは複数あるのでご注意を。また、オンメモリでデータベース(H2やHSQL等)を使う場合、@Entityを付与しておくと起動時にエンティティに対応したテーブルを自動で削除・作成してくれます。なので、その場合は上記のschema.sqlは不要になります。
@Tableアノテーションはテーブル名を明示的に指定します。このアノテーションがない場合はテーブル名にはエンティティクラスの単純名、カラム名にはプロパティ名が使用されます。
詳細は以下です。
http://doma.seasar.org/reference/entity.html
package com.example.model;
import org.seasar.doma.Entity;
import org.seasar.doma.Id;
import org.seasar.doma.Table;
@Entity
@Table(name = "customers")
public class Customer {
@Id
public Integer id;
public String name;
}
CustomerRepository.java
恐らくポイントはDAOであるここだと思います。
Domaでは基本的にDAOの実装クラスはaptで自動生成になります。そのための注釈として@Daoアノテーションを付与します。実装クラスはコンパイル時に自動で生成されます。
その下の@ConfigAutowireableは、冒頭でも述べたdoma-spring-boot-starterというAutoconfigureを利用しました。
これは、自動生成されたDAOの実装クラスをコンテナ管理対象にするために必要です。
詳細は以下です。
http://doma.seasar.org/reference/dao.html
これでもう出来たも同然です。
package com.example.repository;
import java.util.List;
import org.seasar.doma.Dao;
import org.seasar.doma.Select;
import org.seasar.doma.boot.ConfigAutowireable;
import com.example.model.Customer;
@Dao
@ConfigAutowireable
public interface CustomerRepository {
@Select
List<Customer> selectAll();
}
SQLファイルを作る
あとは、SQLファイルを作るだけです。SQLファイルはクラスパスが通ったMETA-INFディレクトリ以下に配置しなければいけません。
規約は以下です。
META-INF/Daoのクラスの完全修飾名をディレクトリに変換したもの/Daoのメソッド名.sql
つまり、今回の例で言うと、、
META-INF/com/example/repository/CustomerRepository/selectAll.sql
になります。
select id, name from customers order by id;
コンパイルする
今回はmavenを利用しているので、以下のコマンドでコンパイルしてみます。
mvn compile
結果は以下のようになりました。
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building doma-cooperation 1.0.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ doma-cooperation ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 3 resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ doma-cooperation ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 5 source files to C:\Development\sts-bundle\workspace\doma-cooperation\target\classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 22.281 s
[INFO] Finished at: 2015-12-13T21:28:22+09:00
[INFO] Final Memory: 21M/93M
maven-resources-pluginでSQLファイルをsrc/main/resources/META-INFからtarget/classesにコピーしています。これは、先にコピーしておかないとコンパイル時にSQLファイルが見つからない、、と怒られてしまうためです。
その次に、maven-compiler-pluginでaptでDAOの実装クラスを生成して、コンパイルを行います。
Mavenによるビルド詳細は以下です。
http://doma.seasar.org/reference/app_build.html#Maven%E3%81%AB%E3%82%88%E3%82%8B%E3%83%93%E3%83%AB%E3%83%89
なお、maven-resources-pluginやmaven-compiler-pluginは今回pom.xmlには定義していませんが、親の親のpom(変な表現)であるSpring Boot Dependencies.pomに定義してあるの利用できます。
実行してみる
早速実行してみます。Spring Bootを起動してブラウザ上で以下へアクセスします。
http://localhost:8080/customers
結果は以下のようになりました。期待通りですね。
[{"id":1,"name":"JAY-Z"},{"id":2,"name":"NAS"},{"id":3,"name":"DMX"},{"id":4,"name":"2PAC"}]
STSのコンソールは以下です(ログの設定は何もやっていないので文字化けしてますが、、、、)。
SQLファイルを読みに行っているのがわかります。
2015-12-13 21:57:52.519 INFO 8620 --- [nio-8080-exec-4] o.s.doma.jdbc.UtilLoggingJdbcLogger : [DOMA2220] ENTER : 繧ッ繝ゥ繧ケ=[com.example.repository.CustomerRepositoryImpl], 繝。繧ス繝?繝?=[selectAll]
2015-12-13 21:57:52.531 INFO 8620 --- [nio-8080-exec-4] o.s.doma.jdbc.UtilLoggingJdbcLogger : [DOMA2076] SQL繝ュ繧ー : SQL繝輔ぃ繧、繝ォ=[META-INF/com/example/repository/CustomerRepository/selectAll.sql],
select id, name from customers order by id
2015-12-13 21:57:52.534 INFO 8620 --- [nio-8080-exec-4] o.s.doma.jdbc.UtilLoggingJdbcLogger : [DOMA2221] EXIT : 繧ッ繝ゥ繧ケ=[com.example.repository.CustomerRepositoryImpl], 繝。繧ス繝?繝?=[selectAll]
これでSpring Boot + Doma2の連携が出来たことになります。
まとめ・ハマったこと
とりあえず、Spring Boot + Doma2の連携で2WaySQLが利用できるようになりましたが、実務での利用はもう少し検証が必要そうです。例えば、今回の例ではSELECTのみでしたが、INSERT、UPDATE、DELETEとかでロールバックや例外のハンドリングとかってどうなる?等といった、連携部分に関するところが検証ポイントになってくるかと思います。
また、そもそもDomaをよく知らなかったので、そこの理解に時間がかかりました。ただ、Doma1にしろDoma2にしろ日本語でのドキュメントが充実しているのでとっかかりやすいかと思います。
今回は以下にあるEclipseのAnnotation Processingの設定はしませんでした。
http://doma.seasar.org/setup.html#注釈処理の設定
これをやると、mvn compileのそれと被ってわけわからなくなってしまったので。。。
最後に、Spring Boot + Doma2での実績はあるようです。
http://www.slideshare.net/matsumana0101/20141115-jjug-ccc-2014-fall
次は、INSERT、UPDATE、DELETEあたりも書いてみようと思います。
以上です。