今回は2015年11月にバージョン1.0.0がリリースされ、2016年4月19日に1.1がリリースされたmybatis-spring-boot-starterの使い方を紹介します。
MyBatisをSpring Boot上で使う際は、mybatis-springから提供されているSqlSessionFactoryBean
やSqlSessionTemplate
のBean定義を開発者が行う必要がありましたが、mybatis-spring-boot-starterの登場によりこれらのBean定義は自動コンフィギュレーションによって解決されます
まずは、実際にmybatis-spring-boot-starterを利用して簡単なCLI(Command Line Interface)アプリケーションを作ってみます 。
Note:
2019/7/16: 追記
JDK 13で導入予定(現時点ではPreview版)のText Blocksを使ったデモアプリケーションへのリンクを追加しました。2019/7/15: 追記
2019年7月15日にバージョン2.1.0がリリースされたので、内容を2.1ベースにしました。2019/5/7: 追記
mybatis-spring-boot-starter 2.0の変更点へのリンクを追加しました。加えて、2.0.1上でmybatis.type-aliases-package
を利用する時に発生する可能性がある不具合に関する説明(説明へのリンク)を追加しました。2019/5/3: 追記
2019年1月22日にバージョン2.0.0(2019年4月にバージョン2.0.1)がリリースされたので、内容を2.0ベースにしました。2017/4/10: 追記
2017年4月10日にバージョン1.3.0がリリースされたので、内容を1.3ベースにしました。2017/1/4: 追記
2017年1月2日にバージョン1.2.0がリリースされたので、内容を1.2ベースにしました。 なお、バージョン1.2を使う場合は、Spring Boot 1.4以降(正確にはSpring Framework 4.3以降)上で動かす必要があります。(2017/1/4)
なお、メジャー/マイナーバージョンアップ時の変更点については、以下の投稿にまとめてあります。
- mybatis-spring-boot-starter 2.1の変更点
- mybatis-spring-boot-starter 2.0の変更点
- mybatis-spring-boot-starter 1.3の変更点
- mybatis-spring-boot-starter 1.2の変更点
- mybatis-spring-boot-starter 1.1の変更点
動作検証バージョン
- MyBatis Spring Boot Starter 2.1.0
- MyBatis 3.5.2
- MyBatis Spring 2.0.2
- Spring Boot 2.1.6.RELEASE
- Spring Framework 5.1.8.RELEASE
検証コード
Java版
Text Blocks版(JDK 13)
JDK 13 Early-Access Buildsを使用して、MapperメソッドにText Blocksを使ったデモアプリケーションをGitHubに公開しました。
Groovy版
2016/7/13に、MapperインタフェースをGroovyで作る方法(「GroovyでMyBatisのMapperをつくる(とSQLの可読性がGood!!)」)を投稿しました。
Kotlin版
2016/8/3に、Kotlin上でMyBatis(mybatis-spring-boot-starter)を使う方法(「Kotlinでmybatis-spring-boot-starterを使う」)を投稿しました。
純正の簡易サンプル
MyBatis Spring Bootから簡易的なサンプルがいくつか提供されているので、そちらも合わせて参照すると良いかと思います。
開発プロジェクトの作成
Spring Bootは、Spring-Boot向けの開発プロジェクトを生成するためのWeb Service「SPRING INITIALIZR」を提供しています。今回は、このWeb Serviceを利用して開発プロジェクトを作成します。
SPRING INITIALIZRはMavenとGradleプロジェクトをサポートしていますが、今回の記事ではMavneプロジェクトを作成します。ウィザードに以下の値を入力して「Generate Project」ボタンを押下すると、Mavenプロジェクトがダウンロードされます。
入力項目 | 値 | 備考 |
---|---|---|
Project | Maven Project | ※デフォルト |
Language | Java | ※デフォルト |
Spring Boot | 2.1.6 | ※デフォルト |
Group | com.example | ※デフォルト |
Artifact | mybatis-demo | ※デフォルト(demo)から変更 |
Dependencies | MyBatis, H2 |
ダウンロードしたZipファイルを解凍すると、以下のようなディレクトリ構成のプロジェクトが生成できます。
.
├── HELP.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
├── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ └── mybatisdemo
│ │ └── MybatisDemoApplication.java
│ └── resources
│ └── application.properties
└── test
└── java
└── com
└── example
└── mybatisdemo
└── MybatisDemoApplicationTests.java
Note:
curlとtarコマンドが使える環境であれば、以下のコマンドを実行するだけで開発プロジェクトを作ることもできます。
$ curl -s https://start.spring.io/starter.tgz\
-d name=mybatis-demo\
-d artifactId=mybatis-demo\
-d dependencies=mybatis,h2\
-d baseDir=mybatis-demo\
| tar -xzvf -
作成した開発プロジェクトをSpring Bootアプリケーションとして起動してみます。
$ ./mvnw spring-boot:run
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------------< com.example:mybatis-demo >----------------------
[INFO] Building mybatis-demo 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] >>> spring-boot-maven-plugin:2.1.6.RELEASE:run (default-cli) > test-compile @ mybatis-demo >>>
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ mybatis-demo ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ mybatis-demo ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:testResources (default-testResources) @ mybatis-demo ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /private/tmp/mybatis-demo/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ mybatis-demo ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] <<< spring-boot-maven-plugin:2.1.6.RELEASE:run (default-cli) < test-compile @ mybatis-demo <<<
[INFO]
[INFO]
[INFO] --- spring-boot-maven-plugin:2.1.6.RELEASE:run (default-cli) @ mybatis-demo ---
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.6.RELEASE)
2019-07-15 17:25:33.712 INFO 55692 --- [ main] c.e.mybatisdemo.MybatisDemoApplication : Starting MybatisDemoApplication on shimizukazutakanoMacBook-puro.local with PID 55692 (/private/tmp/mybatis-demo/target/classes started by shimizukazuki in /private/tmp/mybatis-demo)
2019-07-15 17:25:33.714 INFO 55692 --- [ main] c.e.mybatisdemo.MybatisDemoApplication : No active profile set, falling back to default profiles: default
2019-07-15 17:25:34.049 WARN 55692 --- [ main] o.m.s.mapper.ClassPathMapperScanner : No MyBatis mapper was found in '[com.example.mybatisdemo]' package. Please check your configuration.
2019-07-15 17:25:34.579 INFO 55692 --- [ main] c.e.mybatisdemo.MybatisDemoApplication : Started MybatisDemoApplication in 1.121 seconds (JVM running for 4.19)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.064 s
[INFO] Finished at: 2019-07-15T17:25:34+09:00
[INFO] ------------------------------------------------------------------------
とりあえずエラーはでないことは確認できたので、先に進みます。
mybatis-spring-boot-starterの確認
ウィザードで「MyBatis」を選択すると、Spring Bootのバージョンに対応するmybatis-spring-boot-starterが依存アーティファクトに追加されます。今回はSpring Boot 2.1.6を選択しているため、mybatis-spring-boot-starter 2.1.0が追加されます。(ちなみに・・・Spring Boot 1.5系を選ぶとmybatis-spring-boot-starter 1.3.4が追加されます)
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
mybatis-spring-boot-starter 2.1.0は、推移的に以下のアーティファクトも追加してくれます
$ ./mvnw dependency:tree
...
[INFO] +- org.mybatis.spring.boot:mybatis-spring-boot-starter:jar:2.1.0:compile
[INFO] | +- org.springframework.boot:spring-boot-starter:jar:2.1.6.RELEASE:compile
[INFO] | | +- org.springframework.boot:spring-boot:jar:2.1.6.RELEASE:compile
[INFO] | | | \- org.springframework:spring-context:jar:5.1.8.RELEASE:compile
[INFO] | | | +- org.springframework:spring-aop:jar:5.1.8.RELEASE:compile
[INFO] | | | \- org.springframework:spring-expression:jar:5.1.8.RELEASE:compile
[INFO] | | +- org.springframework.boot:spring-boot-autoconfigure:jar:2.1.6.RELEASE:compile
[INFO] | | +- org.springframework.boot:spring-boot-starter-logging:jar:2.1.6.RELEASE:compile
[INFO] | | | +- ch.qos.logback:logback-classic:jar:1.2.3:compile
[INFO] | | | | \- ch.qos.logback:logback-core:jar:1.2.3:compile
[INFO] | | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.11.2:compile
[INFO] | | | | \- org.apache.logging.log4j:log4j-api:jar:2.11.2:compile
[INFO] | | | \- org.slf4j:jul-to-slf4j:jar:1.7.26:compile
[INFO] | | +- javax.annotation:javax.annotation-api:jar:1.3.2:compile
[INFO] | | \- org.yaml:snakeyaml:jar:1.23:runtime
[INFO] | +- org.springframework.boot:spring-boot-starter-jdbc:jar:2.1.6.RELEASE:compile
[INFO] | | +- com.zaxxer:HikariCP:jar:3.2.0:compile
[INFO] | | \- org.springframework:spring-jdbc:jar:5.1.8.RELEASE:compile
[INFO] | | +- org.springframework:spring-beans:jar:5.1.8.RELEASE:compile
[INFO] | | \- org.springframework:spring-tx:jar:5.1.8.RELEASE:compile
[INFO] | +- org.mybatis.spring.boot:mybatis-spring-boot-autoconfigure:jar:2.1.0:compile
[INFO] | +- org.mybatis:mybatis:jar:3.5.2:compile
[INFO] | \- org.mybatis:mybatis-spring:jar:2.0.2:compile
...
ドメインオブジェクトの作成
今回は、ドメインオブジェクトとしてTodo
クラスを作ります。
package com.example.mybatisdemo.domain;
public class Todo {
private int id;
private String title;
private String details;
private boolean finished;
// ... setter and getter
}
Note:
setterとgetterの生成はIDEの機能を使ってもよいですが、Lombokを使う方が開発効率があがります!!
Mapperインターフェースの作成
Todo
へのCRUD操作を提供するMapperインターフェースを作ります。
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.domain.Todo;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface TodoMapper {
@Insert("INSERT INTO todo (title, details, finished) VALUES (#{title}, #{details}, #{finished})")
@Options(useGeneratedKeys = true, keyProperty = "id")
void insert(Todo todo);
@Select("SELECT id, title, details, finished FROM todo WHERE id = #{id}")
Todo select(int id);
}
Note:
SQLをXMLファイルに書きたい場合は、以下のようなMapperインターフェースとXMLファイルを作成します。src/main/java/com/example/mybatisdemo/mapper/TodoMapper.java@Mapper public interface TodoMapper { void insert(Todo todo); Todo select(int id); }
src/main/resources/com/example/mybatisdemo/mapper/TodoMapper.xml<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.mybatisdemo.mapper.TodoMapper"> <insert id="insert" useGeneratedKeys="true" keyProperty="id"> INSERT INTO todo (title, details, finished) VALUES (#{title}, #{details}, #{finished}) </insert> <select id="select" resultType="com.example.mybatisdemo.domain.Todo"> SELECT id, title, details, finished FROM todo WHERE id = #{id} </select> </mapper>
todoテーブルの作成
H2の組み込みデータベースにtodoテーブルを作成します。
Spring Bootの自動コンフィギュレーションで作成されるDataSource
を使う場合は、クラスパス直下にschema.sql
とdata.sql
というファイルを配置しておくと、Spring Boot起動時にこれらのファイルを読み込んでSQLを実行してくれます。
CREATE TABLE todo (
id IDENTITY
,title TEXT NOT NULL
,details TEXT
,finished BOOLEAN NOT NULL
);
MybatisDemoApplicationの修正とSpring Bootアプリケーションの起動
MybatisDemoApplication
を修正し、Mapperインタフェースを経由してデータベースにアクセスします。
- 修正前
package com.example.mybatisdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MybatisDemoApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisDemoApplication.class, args);
}
}
- 修正後
package com.example.mybatisdemo;
import com.example.mybatisdemo.domain.Todo;
import com.example.mybatisdemo.mapper.TodoMapper;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.Transactional;
@SpringBootApplication
public class MybatisDemoApplication implements CommandLineRunner { // CommandLineRunnerを実装する
public static void main(String[] args) {
SpringApplication.run(MybatisDemoApplication.class, args);
}
private final TodoMapper todoMapper;
public MybatisDemoApplication(TodoMapper todoMapper) {
this.todoMapper = todoMapper; // Mapperをインジェクションする
}
// Spring Boot起動時にCommandLineRunner#runメソッドが呼び出される
@Transactional
@Override
public void run(String... args) throws Exception {
Todo newTodo = new Todo();
newTodo.setTitle("飲み会");
newTodo.setDetails("銀座 19:00");
todoMapper.insert(newTodo); // 新しいTodoをインサートする
Todo loadedTodo = todoMapper.select(newTodo.getId()); // インサートしたTodoを取得して標準出力する
System.out.println("ID : " + loadedTodo.getId());
System.out.println("TITLE : " + loadedTodo.getTitle());
System.out.println("DETAILS : " + loadedTodo.getDetails());
System.out.println("FINISHED : " + loadedTodo.isFinished());
}
}
MybatisDemoApplication
を修正したら、Spring Bootアプリケーションとして起動します。
$ ./mvnw spring-boot:run
...
[INFO] --- spring-boot-maven-plugin:2.1.6.RELEASE:run (default-cli) @ mybatis-demo ---
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.6.RELEASE)
2019-07-15 17:35:04.734 INFO 55816 --- [ main] c.e.mybatisdemo.MybatisDemoApplication : Starting MybatisDemoApplication on shimizukazutakanoMacBook-puro.local with PID 55816 (/private/tmp/mybatis-demo/target/classes started by shimizukazuki in /private/tmp/mybatis-demo)
2019-07-15 17:35:04.736 INFO 55816 --- [ main] c.e.mybatisdemo.MybatisDemoApplication : No active profile set, falling back to default profiles: default
2019-07-15 17:35:05.561 INFO 55816 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2019-07-15 17:35:05.820 INFO 55816 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2019-07-15 17:35:06.176 INFO 55816 --- [ main] c.e.mybatisdemo.MybatisDemoApplication : Started MybatisDemoApplication in 1.746 seconds (JVM running for 5.696)
ID : 1
TITLE : 飲み会
DETAILS : 銀座 19:00
FINISHED : false
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.341 s
[INFO] Finished at: 2019-07-15T17:35:06+09:00
[INFO] ------------------------------------------------------------------------
2019-07-15 17:35:06.266 INFO 55816 --- [ Thread-3] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2019-07-15 17:35:06.270 INFO 55816 --- [ Thread-3] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
...
標準出力にインサートしたTodoの状態が出力されました
ちなみに、開発プロジェクトは以下のような状態になります。
.
├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
├── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ └── mybatisdemo
│ │ ├── MybatisDemoApplication.java // 修正したファイル
│ │ ├── domain
│ │ │ └── Todo.java // 追加したファイル
│ │ └── mapper
│ │ └── TodoMapper.java // 追加したファイル
│ └── resources
│ ├── application.properties
│ ├── com
│ │ └── example
│ │ └── mybatisdemo
│ │ └── mapper
│ │ └── TodoMapper.xml // 追加したファイル(SQLをXMLに記述する場合のみ)
│ └── schema.sql // 追加したファイル
└── test
└── java
└── com
└── example
└── mybatisdemo
└── MybatisDemoApplicationTests.java
JUnit上でMybatisDemoApplication
を起動
ダウンロードした開発プロジェクトには、JUnit用のテストケースクラス(MybatisDemoApplicationTests
)が格納されています。
$ ./mvnw test
...
2019-07-15 17:36:55.348 INFO 55839 --- [ main] c.e.m.MybatisDemoApplicationTests : Started MybatisDemoApplicationTests in 1.4 seconds (JVM running for 2.08)
ID : 1
TITLE : 飲み会
DETAILS : 銀座 19:00
FINISHED : false
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.125 s - in com.example.mybatisdemo.MybatisDemoApplicationTests
2019-07-15 17:36:55.688 INFO 55839 --- [ Thread-2] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2019-07-15 17:36:55.690 INFO 55839 --- [ Thread-2] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.304 s
[INFO] Finished at: 2019-07-15T17:36:56+09:00
[INFO] ------------------------------------------------------------------------
テストは成功したみたいですが、ダウンロードした状態だとテスト結果をassertしていません。
せっかくなので、MybatisDemoApplicationTests
を以下のように修正してテスト結果をassertしてみます。
package com.example.mybatisdemo;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.rule.OutputCapture;
import org.springframework.test.context.junit4.SpringRunner;
import static org.hamcrest.Matchers.containsString;
@RunWith(SpringRunner.class)
@SpringBootTest
public class MybatisDemoApplicationTests {
@ClassRule
public static OutputCapture out = new OutputCapture(); // System.outの内容をキャプチャする
@Test
public void contextLoads() {
// System.outした内容を検証する
out.expect(containsString("ID : 1"));
out.expect(containsString("TITLE : 飲み会"));
out.expect(containsString("DETAILS : 銀座 19:00"));
out.expect(containsString("FINISHED : false"));
}
}
再度テストを実行すると、標準出力した内容が正しいことが検証できました
Mapperインタフェースのスキャンの仕組み
なにやらMyBatisを使ってデータベースにアクセスすることはできましたが、どうやってMapperインタフェースがスキャンされたのでしょうか??
mybatis-springの機能だけだと、@MapperScan
を使ってスキャン対象のベースパッケージを指定する必要がありました。しかし、mybatis-spring-boot-starterを使うとSpring Bootアプリケーション(MybatisDemoApplication
)が格納されているパッケージ(com.example
)配下に格納されているインターフェースのうち、@org.apache.ibatis.annotations.Mapper
(mybatis 3.4で追加されたアノテーション)が付与されたインタフェースがMapperインターフェースとしてスキャンされます。
Note:
mybatis-spring-boot-starter 1.0系では、Spring Bootアプリケーション(MybatisDemoApplication
)が格納されているパッケージ(com.example
)配下に格納されているインタフェースが全てMapperインタフェースとしてスキャンされていました。
この仕組みは便利な反面で、意図していないインターフェースがMapperインタフェースとしてスキャンされてDIコンテナに登録されてしまうという問題がありました。@Mapper
はこの問題を解決する手段として追加されたマーカーアノテーションです。
Mapperインタフェースに@Mapper
を付与しない場合は、@MapperScan
を使ってスキャン対象のベースパッケージを明示的に指定しましょう。
@MapperScan("com.example.mybatisdemo.mapper") // スキャンするベースパッケージを明示的に指定する
@SpringBootApplication
public class MybatisDemoApplication {
// ...
}
タイプエイリアスの利用
SQLをXMLに記述する際に、parameterType
やresultType
属性にクラスのFQCN(Fully Qualified Class Name)ではなく、シンプルなクラス名(エイリアス名)を指定したい場合があると思います。
そのような場合は、applicaion.properties
に以下の定義を追加するだけです。
mybatis.type-aliases-package=com.example.mybatisdemo.domain
カンマ区切りで複数のベースパッケージを指定することができます。2.0.1よりワイルドカード(例:com.example.**.domain
)が指定できるようになっています(mybatis-spring 2.0.1+で利用可能だが2.0.2+の利用を推奨)。
Important:
2.0.1でmybatis.type-aliases-package
を使用すると、特定の条件をみたす時にエラーが発生する不具合が報告されています。不具合の詳細については、mybatis-spring-boot-starter 2.0の変更点を参照してください。2019/7/15 追記
2.1.0で解決済みです。
2.0.0よりタイプエイリアスを登録するクラスであることを示す親クラスを指定するためのプロパティが追加されています。これはmybatis-springでもともとサポートされていた仕組みを、Spring Bootのコンフィギュレーションプロパティで指定できるように改善したものです。
mybatis.type-aliases-super-type=com.example.mybatisdemo.TypeAliasTarget
タイプハンドラーの適用
MyBatisがデフォルトで用意していない型のタイプハンドラーを追加したい場合や、デフォルトで適用されるタイプハンドラーを上書きしたい場合があると思います。
そのような場合は、applicaion.properties
に以下の定義を追加するだけです。
mybatis.type-handlers-package=com.example.mybatisdemo.typehandler
カンマ区切りで複数のベースパッケージを指定することができます。2.0.1よりワイルドカード(例:com.example.**.typehandler
)が指定できるようになっています(mybatis-spring 2.0.1+で利用可能だが2.0.2+の利用を推奨)。
バージョン2.1.0より、DIコンテナに登録したTypeHandler
を検出してMyBatisに適用することができます。
@SpringBootApplication
public class MybatisDemoApplication {
@Bean
MyTypeHandler myTypeHandler(){
return new MyTypeHandler();
}
}
複数のTypeHandler
をMyBatisに適用したい場合は、それぞれBean定義するだけです。
SqlSessionの実行モードの変更
デフォルトの実行モードはMyBatis設定の設定値(デフォルトはSIMPLEモード)ですが、アプリケーションの種類や要件によっては実行モードを変更したい場合があると思います。
そのような場合は、applicaion.properties
に以下の定義を追加するだけです。もちろんMyBatis設定の設定値を変更してもOKです。
mybatis.executor-type=BATCH
Mapper XMLファイルを明示的に読み込む
Mapperインタフェースを経由しないで直接SqlSession
(SqlSessionTemplate
)を使ってSQLを実行する場合は、SQLが定義されているMapper XMLファイルを明示的に読み込む必要があります。
そのような場合は、applicaion.properties
に以下の定義を追加するだけです。
mybatis.mapper-locations=classpath*:/mybatis/sqls/**/*.xml
カンマ区切りで複数のロケーションを指定することもできますし、
mybatis.mapper-locations[0]=classpath*:/mybatis/sqls/aaa/**/*.xml
mybatis.mapper-locations[1]=classpath*:/mybatis/sqls/bbb/**/*.xml
という感じで指定することもできます。
また、propertiesファイルではなくyamlファイルを使用すると、
mybatis:
mapper-locations:
- classpath*:/mybatis/sqls/aaa/**/*.xml
- classpath*:/mybatis/sqls/bbb/**/*.xml
という記述にすることもできます。
MyBatisの設定
バージョン1.0系では、MyBatis自体の動作をカスタマイズしたい場合は、MyBatis設定ファイルの<settings>
タグ内に設定を追加する必要がありましたが、バージョン1.1系からapplicaion.properties
に直接設定できるようになりました。また、バージョン1.3(1.2.1にもバックポート済み)からConfigurationCustomizer
インタフェースが追加され、MyBatisの設定を完全にカスタマイズすることが可能になっています。
MyBatis設定ファイル使用時の設定例
mybatis.config-location=classpath:/mybatis/mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true" />
<setting name="defaultFetchSize" value="100" />
<setting name="defaultStatementTimeout" value="30" />
</settings>
</configuration>
もちろん<settings>
以外のタグも利用できます。
ちなみに・・・MyBatis設定ファイルが存在するかチェックを行う仕組みも提供されており、デフォルトでは無効になっています。この仕組みを有効にしたい場合は、以下の定義を追加してください。
mybatis.check-config-location=true
applicaion.properties
使用時の設定例
MyBatis設定ファイルを使わずにapplicaion.properties
だけで設定することもできます。
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.default-fetch-size=100
mybatis.configuration.default-statement-timeout=30
なお、mybatis.configuration.xxxx
とmybatis.config-location
を同時に指定することはできません。
指定するとエラーになります。
Note:
バージョン1.1.1までは、IDE上でmybatis.configuration
配下のプロパティが補完されませんでしたが、バージョン1.2で解決されています。(ただ・・・同じプロパティキーが重複してるな・・・・)
2017/1/8 追記
入力補完時にプロパティキーが重複しているのは・・・maven-bundle-plugin 2.5.4のバグ?が原因っぽく、maven-bundle-plugin 3.0.0+を使ってビルドしたら解決できました。これは、mybatis-spring-boot-starter#gh-127で対応予定でバージョン1.2.1で直ると思います。2017/4/10 追記
入力補完時にプロパティキーが重複する問題は、バージョン1.3(1.2.1にもバックポート済み)で解決済みです。
ConfigurationCustomizer
を使用したカスタマイズ例
バージョン1.3.0(1.2.1にもバックポート済み)より、MyBatisの設定情報を保持するBean(org.apache.ibatis.session.Configuration
のBean)をJavaコードでカスタマイズするためのコールバックインタフェース(org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer
)が追加されました。以下のように、このインタフェースを実装したクラスをBean定義しておくと、ConfigurationCustomizer
のconfigure
メソッドがコールバックされる仕組みになっており、Javaコードを使用してMyBayisの設定を完全にカスタマイズすることができます。
@Configuration
public class MyBatisConfiguration {
@Bean
ConfigurationCustomizer mybatisConfigurationCustomizer() {
return (configuration) -> {
// aplication.propertiesで指定・表現できないカスタマイズコードを実装
configuration.getTypeHandlerRegistry().register(RoundingMode.class, EnumOrdinalTypeHandler.class);
};
}
}
外部定義したプロパティ値の適用
MyBatisでは、外部定義したプロパティ値をMyBatis設定ファイルやMapperファイルの中に埋め込むことができ、外部定義はMyBatis設定ファイルの<properties>
要素を使って指定します。(プログラムで直接指定することもできますが、本投稿では扱いません)
さらに、MyBatis-Springを使う場合はMyBatis設定ファイルの<properties>
要素に加えて、SqlSessionFactoryBean
のconfigurationProperties
プロパティ(java.util.Properties
型)に外部定義を指定することができ、バージョン1.2より、application.properties
(or application.yml
)を使って指定できるようになりました。
例えば、MyBatis設定ファイルで以下のように定義していた部分は・・・
<properties>
<property name="key" value="value"/>
</properties>
以下のように、application.properties
(or application.yml
)の中で指定するように書き換えることができます。
mybatis.configuration-properties.key=value
Note:
実は・・・バージョン1.1.1でも、以下のように定義することで同じことが実現できました。この方法はバージョン1.2以降でも使うことができ、同じキー名がある場合はバージョン1.2で追加されたmybatis.configuration-properties
で指定した値が優先されます。src/main/resources/application.propertiesmybatis.configuration.variables.key=value
たとえば・・・
mybatis.configuration.variables
には環境依存しない値(or デフォルト値)を定義しておき、プロファイル毎(環境毎)のプロパティファイルでmybatis.configuration-properties
を使用して環境依存値に上書きするといった使い方もできます。
Plugin(Interceptor)の適用
MyBatisは、MyBatisのコアコンポーネントが行う処理に割り込む仕組み(Interceptor
)を提供しています。
この仕組みを利用することはあまりないと思いますが、MyBatisにInterceptor
を適用したい場合は、以下のようなBean定義を追加するだけです。
@SpringBootApplication
public class MybatisDemoApplication {
@Bean
Interceptor myInterceptor(){
return new MyInterceptor();
}
// ...
}
複数のIntereptor
をMyBatisに適用したい場合は、それぞれBean定義するだけです。
DatabaseIdProvider
の適用
MyBatisは、接続中のデータベースを識別するための仕組み(DatabaseIdProvider
)を提供しています。
この仕組みを利用することはあまりないと思いますが、MyBatisにDatabaseIdProvider
を適用したい場合は、以下のようなBean定義を追加するだけです。
@SpringBootApplication
public class MybatisDemoApplication {
@Bean
VendorDatabaseIdProvider vendorDatabaseIdProvider() {
VendorDatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();
Properties vendorProperties = new Properties();
vendorProperties.put("PostgreSQL", "postgresql");
vendorProperties.put("H2", "h2");
databaseIdProvider.setProperties(vendorProperties);
return databaseIdProvider;
}
// ...
}
LanguageDriver
の適用
MyBatisは、動的SQLを組み立てるため言語を変更(カスタマイズ)するための仕組み(LanguageDriver
)を提供しています。
デフォルトではMyBatisに組み込まれているXMLベースの言語(XMLLanguageDriver
)が適用されますが、デフォルトの動作を変更したい場合は、開発者が独自に作成した LanguageDriver
クラスは、以下のようなBean定義を追加するだけです(バージョン2.1より利用可能)。
@Bean
MyLanguageDriver myLanguageDriver() {
return MyLanguageDriver();
}
さらに、DIコンテナから検出した LanguageDriver
のBeanが1つの場合は、検出した LanguageDriver
がデフォルトの LanguageDriver
クラスとして扱われます(複数のBeanを検出した場合は、MyBatis本体のデフォルト設定である XMLLanguageDriver
がそのまま適用されます)。自動検出機能によってデフォルトの LanguageDriver
クラスを変更したくない場合や、複数の LanguageDriver
を共存して利用する際にデフォルトの LanguageDriver
クラスを変更したい場合は、 次に紹介する mybatis.default-scripting-language-driver
プロパティ(バージョン2.1で追加したプロパティ)を使用して、デフォルトの LanguageDriver
クラスを明示的に指定するようにしてください。
デフォルトで使用するLanguageDriver
の指定
デフォルトで使用するLanguageDriver
を明示的に指定する場合は、以下のように設定するだけです。
mybatis.default-scripting-language-driver={デフォルトのLanguageDriverクラスのFQCN}
このプロパティは、バージョン2.1より利用可能です。なお、バージョン2.0までは、デフォルトで使う LanguageDriver
クラスを指定する方法として、 mybatis.configuration.default-scripting-language
というプロパティ(MyBatis本体が提供するコンフィギュレーションクラスへ直接値を設定するためのプロパティ)をサポートしていましたが、後述の「LanguageDriver
の自動コンフィグレーション」と組み合わせた時に期待通りの動作にならない事があるため、このプロパティはバージョン2.1で廃止(使用禁止)になっています。
LanguageDriver
の自動コンフィグレーション
バージョン2.1より、MyBatis純正の以下の3つのサブモジュール(LanguageDriver
)については、自動コンフィギュレーションの仕組みがサポートされています。
上記3つのサブモジュールのjarファイルをクラスパス上に追加すると、自動でサブモジュール提供の LanguageDriver
クラスのBeanがDIコンテナに登録されてMyBatisへ適用されます。なお、開発者が明示的に上記サブモジュールから提供されているLanguageDriver
クラスをBean定義した場合は、自動コンフィギュレーションは行われません。
WARNING:
以下の条件に全て一致する場合は、下位互換性が失われるため、追加で対策が必要になります。
- 上記3つのサブモジュール(mybatis-velocity, mybatis-freemarker, mybatis-thymeleaf)のjarが1つのみクラスパス上にある
- デフォルトの
LanguageDriver
がサブモジュール提供のLanguageDriver
ではない条件に一致する場合は、サブモジュール提供の
LanguageDriver
クラスがデフォルトのLanguageDriver
に設定される仕組みになっているため、以下のように明示的にデフォルトのLanguageDriver
を指定する必要があります。mybatis.default-scripting-language-driver={デフォルトのLanguageDriverクラスのFQCN}
MyBatis Velocityの利用
以下のアーティファクトを追加すると、org.mybatis.scripting.velocity.VelocityLanguageDriver
(mybatis-velocity 2.0以前を指定するとorg.mybatis.scripting.velocity.Driver
)をMyBatisへ適用します。
<!-- Mybatis Velocityを利用する場合 -->
<dependency>
<groupId>org.mybatis.scripting</groupId>
<artifactId>mybatis-velocity</artifactId>
<version>2.1.0</version>
</dependency>
mybatis-velocity 2.1.0以降を利用する場合は、コンフィギュレーションプロパティの仕組みを利用してMyBatis VelocityおよびMyBatis Velocityの中で利用するVelocityのテンプレートエンジンの動作をカスタマイズすることができます。
#
# mybatis.scripting-language-driver.velocity.{key} = {value} 形式
#
# Velocity本体の動作をカスタマイズするプロパティ
# mybatis.scripting-language-driver.velocity.velocity-settings.{name} = {value}形式
# {name}に設定可能なプロパティはリファレンスドキュメントを参照してください。
mybatis.scripting-language-driver.velocity.velocity-settings.runtime.custom_directives = com.example.directives.MyDirective
# テンプレートエンジンに渡す追加属性(オブジェクト)を指定するプロパティ
# mybatis.scripting-language-driver.velocity.additional-context-attributes.{name} = {value}
mybatis.scripting-language-driver.velocity.additional-context-attributes.likeEscape = com.example.helpers.LikeEscape
MyBatis FreeMarkerの利用
以下のアーティファクトを追加すると、org.mybatis.scripting.freemarker.FreeMarkerLanguageDriver
をMyBatisへ適用します。
<dependency>
<groupId>org.mybatis.scripting</groupId>
<artifactId>mybatis-freemarker</artifactId>
<version>1.2.0</version>
</dependency>
mybatis-velocity 1.2.0以降を利用する場合は、コンフィギュレーションプロパティの仕組みを利用してMyBatis FreeMarkerおよびMyBatis FreeMarkerの中で利用するFreeMarkerのテンプレートエンジンの動作をカスタマイズすることができます。
#
# mybatis.scripting-language-driver.freemarker.{key} = {value} 形式
#
# FreeMarker本体の動作をカスタマイズするプロパティ
# mybatis.scripting-language-driver.freemarker.freemarker-settings.{name} = {value}形式
# {name}に設定可能なプロパティはリファレンスドキュメントを参照してください。
mybatis.scripting-language-driver.freemarker.freemarker-settings.interpolation_syntax = dollar
# MyBatis FreeMarkerの動作をカスタマイズするプロパティ
# mybatis.scripting-language-driver.freemarker.{name} = {value}
# {name}に設定可能なプロパティはリファレンスドキュメントを参照してください。
mybatis.scripting-language-driver.freemarker.template-file.base-dir = sql
MyBatis Thymeleafの利用
以下のアーティファクトを追加すると、org.mybatis.scripting.thymeleaf.ThymeleafLanguageDriver
をMyBatisへ適用します。
<dependency>
<groupId>org.mybatis.scripting</groupId>
<artifactId>mybatis-thymeleaf</artifactId>
<version>1.0.1</version>
</dependency>
コンフィギュレーションプロパティの仕組みを利用してMyBatis ThymeleafおよびMyBatis Thymeleafの中で利用するThymeleafのテンプレートエンジンの動作をカスタマイズすることができます。
#
# mybatis.scripting-language-driver.thymeleaf.{key} = {value} 形式
#
# MyBatis Thymeleafの動作をカスタマイズするプロパティ
# mybatis.scripting-language-driver.thymeleaf.{name} = {value}
# {name}に設定可能なプロパティはリファレンスドキュメントを参照してください。
mybatis.scripting-language-driver.thymeleaf.use2way = false
mybatis.scripting-language-driver.thymeleaf.template-file.cache-enabled = false
mybatis.scripting-language-driver.thymeleaf.dialect.like-additional-escape-target-chars = %, _
Mapperの遅延初期化制御
バージョン2.1より、DIコンテナに登録したMapperの初期化タイミング(DIコンテナ初期化時、インジェクションまたは利用時)を制御することができます。デフォルトの動作は、DIコンテナ初期化時に全てのMapperが初期化されます。
これは、Spring Boot 2.2(投稿時点では正式版は未リリース)でサポートされるBeanの遅延初期化の仕組みを有効化した際に、特定の条件下においてMapprのメソッド呼び出し時にエラーが発生してしまうため、Mapperに対する遅延初期化の適用有無を利用者側で制御できるようにするために追加した仕組みです。エラーを回避するだけなら、Mapperに対する遅延初期化を無条件で無効化すればよいのですが、開発時(テスト時)には遅延初期化の仕組みは有効だと思うので、MyBatis(mybatis-spring & mybatis-spring-boot-starter)独自にMapperを遅延初期化を制御できるようにしました。詳しくは、「mybatis-spring-boot 2.1.0で追加されるmybatis.lazy-initializationについて」をご覧ください。
NOTE:
バージョン2.1より提供する遅延初期化の仕組みはMyBatis(mybatis-spring & mybatis-spring-boot-starter)独自の仕組みなので、Spring Boot 2.1系でも利用することができます。
@MybatisTest
(mybatis-spring-boot-starter-test)の利用
バージョン1.3より、MyBatisの機能をテストする時に必要となるBean定義をサポートする@MybatisTest
アノテーションが追加されました。これは、Spring Bootが提供する@DataJpaTest
や@JdbcTest
のMyBatis版になります(当然ながら・・使い方もSpring Boot提供のアノテーションと同じです!!)。
この対応に伴い、MyBatisのテストをする際に必要となるライブラリ群を解決するためのmybatis-spring-boot-starter-test
が新設されているので、MyBatis提供の機能に対するテストを行う場合は、pom.xml
に以下の定義を追加してください。
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>2.1.0</version>
<scope>test</scope>
</dependency>
たとえば、以下のようなMapperインタフェースに対するテストを行う場合は、
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.domain.Todo;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface TodoMapper {
@Insert("INSERT INTO todo (title, details, finished) VALUES (#{title}, #{details}, #{finished})")
@Options(useGeneratedKeys = true, keyProperty = "id")
void insert(Todo todo);
@Select("SELECT id, title, details, finished FROM todo WHERE id = #{id}")
Todo select(int id);
}
@MybatisTest
を使用して、以下のようなテストケースクラスを作ればOKです。
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.domain.Todo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@MybatisTest // (1)
public class TodoMapperTests {
@Autowired
private TodoMapper todoMapper; // (2)
@Autowired
private NamedParameterJdbcOperations jdbcOperations; // (3)
@Test
public void insert() {
// setup
// none
// perform test and assertions
{
// (4)
Todo newTodo = new Todo();
newTodo.setTitle("飲み会");
newTodo.setDetails("銀座 19:00");
todoMapper.insert(newTodo);
// (5)
Todo actualTodo =
jdbcOperations.queryForObject("SELECT * FROM todo WHERE id = :id",
new MapSqlParameterSource("id", newTodo.getId()),
new BeanPropertyRowMapper<>(Todo.class));
assertThat(actualTodo.getId()).isEqualTo(newTodo.getId());
assertThat(actualTodo.getTitle()).isEqualTo("飲み会");
assertThat(actualTodo.getDetails()).isEqualTo("銀座 19:00");
assertThat(actualTodo.isFinished()).isEqualTo(false);
}
}
@Test
public void select() {
// (6)
// setup
Todo newTodo = new Todo();
newTodo.setTitle("飲み会");
newTodo.setDetails("銀座 19:00");
GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
jdbcOperations.update(
"INSERT INTO todo (title, details, finished) VALUES(:title, :details, :finished)",
new BeanPropertySqlParameterSource(newTodo), keyHolder);
// perform test and assertions
{
// (7)
Todo actualTodo = todoMapper.select(keyHolder.getKey().intValue());
assertThat(actualTodo.getId()).isEqualTo(keyHolder.getKey().intValue());
assertThat(actualTodo.getTitle()).isEqualTo("飲み会");
assertThat(actualTodo.getDetails()).isEqualTo("銀座 19:00");
assertThat(actualTodo.isFinished()).isEqualTo(false);
}
}
}
項番 | 説明 |
---|---|
(1) | クラスに@MybatisTest を付与する。このアノテーションを付与することで、MyBatisを動かすために必要になるAutoConfigureクラスだけが有効になります。デフォルトの動作ではコンポーネントスキャンが無効化されているので、テスト時に必要ないBeanが無題にDIコンテナに登録されることを防ぐことができます。 |
(2) | テスト対象のMapperをインジェクションする。@MybatisTest を付与すると、MyBatis提供のAutoConfigureによってMapperのBeanが生成されます。 |
(3) | 登録データの検証およびテストデータを登録するために、NamedParameterJdbcTemplate のBeanをインジェクションする。@MybatisTest を付与すると、JdbcTemplate とNamedParameterJdbcTemplate のBeanがDIコンテナに登録されます。 |
(4) | テスト対象のinsert メソッドを呼び出す。 |
(5) |
insert メソッドの呼び出し結果を検証する。ここでは、NamedParameterJdbcOperations のメソッドを介して登録したデータを取得し、登録したデータの妥当性を検証しています。 |
(6) |
select メソッドのテストを行うためにテストデータを登録する。ここでは、NamedParameterJdbcOperations のメソッドを介してテストデータを登録しています。なお、Spring Framework提供の@Sql アノテーションなどを使用してテストデータを登録する方法もあります。 |
(7) | テスト対象のselect メソッドを呼び出す。 |
(8) |
select メソッドの呼び出し結果を検証する。 |
この状態でテストを実行すると、上位パッケージに存在するSpring Bootアプリケーションクラス(@SpringBootApplication
を付与したクラス)内で定義しているBean定義も読み込まれる仕組みになっているため、MyBatisのテストに必要ないBeanがDIコンテナに登録される可能性があります。
もし、MyBatisのテストに必要ないBeanをDIコンテナに登録したくない場合は、テストケースクラスと同じパッケージに以下のような空のSpring Bootアプリケーションクラスを作成してください。こうすることで、上位パッケージにあるSpring Bootアプリケーションクラスを無効化することができます。
package com.example.mybatisdemo.mapper;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MapperTestApplication {
}
さらに詳しい情報は、公式リファレンスおよびSpring Bootが提供する@DataJpaTest
や@JdbcTest
のリファレンスをご覧ください。
まとめ
今回は、仕事でよく使うMyBatisをSpring Bootで動かす時に使うと便利なmybatis-spring-boot-starterを紹介しました。MyBatisのデフォルトの動作で問題なければ、特別な設定はいっさい不要です。また、バージョン1.3よりテストをサポートする新機能(@MybatisTest
)が追加されており、MapperやDAOのテストを行う際に必要となるBean定義を(いろんな意味で)効率的に行うことができます。
参考サイト
- https://github.com/mybatis/spring-boot-starter
- http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/
- http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-test-autoconfigure/
- http://www.mybatis.org/mybatis-3/ja/
- http://terasolunaorg.github.io/guideline/5.5.1.RELEASE/ja/ArchitectureInDetail/DataAccessDetail/DataAccessMyBatis3.html
- http://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-autoconfigured-jpa-test
- http://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-autoconfigured-jdbc-test