今回は今更ながら・・・・Spring Boot 1.4(Spring 4.3)上でMyBatis 2(iBatisといった方が馴染みがあるかな?)を使う方法を紹介します。なぜ今頃MyBatis 2の話やねん?と思う方がほとんどだと思いますが、このエントリーを書こうと思った動機は・・・Spring 3系がついに今年の12月(あと半月・・・)でEOLになるためです。
MyBatis 2とSpringの関係
Springは3系まではMyBatis 2との連携コンポーネントを提供していましたが、Spring 4系から華麗にDropされています。そのため、MyBatis 2を使っているアプリケーション(レガシーなアーキテクチャで作られているアプリ)はSpring 4にバージョンアップすることができませんでした。これまでは脆弱性など重要なバグが見つかればSpring 3系にもフィードバックされていましたが、来年からはフィードバックされません。セキュリティ脆弱性などのことを考えるとSpring 4系(or 来年4月頃にリリースされる予定のSpring 5系)へのバージョンアップを検討すべきです!!
Note:
ちなみに・・・MyBatisの最新バージョンは3.4.1ですが、MyBatis 2とは互換性が全くありません。また、MyBatis 3とSpringの連携コンポーネントは、SpringではなくMyBatisの開発コミュニティーから提供されています。
で、どうすればいいの?
たぶんほとんど知られていないと思いますが・・・MyBatisの開発コミュニティーから「ibatis-spring」なるものが提供されているんです
こいつの実体は、Spring 3系で提供されているMyBatis 2との連携コンポーネントの完全クローンなので、jarをぶち込むだけでSpring 4上でMyBatis 2を使うことができるのです。
Spring Boot 1.4上でMyBatis 2を使ってみよう!
論より証拠・・・ということで、Spring Boot 1.4(Spring 4.3)上でMyBatis 2が動くか確認してみることにします。
まずは、開発プロジェクトを「SPRING INITIALIZR」を使って作成します。依存アーティファクトには、「JDBC」と「H2」を指定します。

完成品は以下のGitHubリポジトリで公開してます。
Note:
2016/12/17 13:30 追記Spring Boot 2.0(Spring 5.0)のSNAPSHOTバージョンでの動作確認も行いました!!結果はもちろん
mybatis2とibatis-springを依存アーティファクトに追加
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis2</artifactId>
<version>2.3.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-2-spring</artifactId>
<version>1.0.3</version>
</dependency>
todoテーブルの作成
H2の組み込みデータベースにtodoテーブルを作成します。
Spring Bootの自動コンフィギュレーションで作成されるDataSource
を使う場合は、クラスパス直下にschema.sql
とdata.sql
というファイルを配置しておくと、Spring Boot起動時にこれらのファイルを読み込んでSQLを実行してくれます。
CREATE TABLE todo (
id INTEGER
,title TEXT NOT NULL
,details TEXT
,finished BOOLEAN NOT NULL
);
CREATE SEQUENCE seq_todo;
Note:
本当はidカラムはID列にして、JDBC 3.0から追加された
Statement#getGeneratedKeys()
を使いたいところだけど・・・MyBatis 2はどうやらサポートしてなさそう。(MyBais 3は連携できます)
なので・・・ここでは昔ながらにシーケンスを使うスタイルになっております(あしからず)
ドメインオブジェクトの作成
今回は、ドメインオブジェクトとしてTodo
クラスを作ります。
package com.example.domain;
public class Todo {
private int id;
private String title;
private String details;
private boolean finished;
// ... setter and getter
}
DAOクラスの作成
今回は、Todo
へのCRUD操作を提供するDAOクラスを作ります。
package com.example.dao;
import com.example.domain.Todo;
import org.springframework.orm.ibatis.SqlMapClientOperations;
import org.springframework.stereotype.Repository;
@Repository
@SuppressWarnings("deprecation") // 非推奨の警告を削除・・・(知っておりますw)
public class TodoDao {
private final SqlMapClientOperations operations;
public TodoDao(SqlMapClientOperations operations) {
this.operations = operations;
}
public void insert(Todo todo) {
operations.insert("com.example.dao.TodoDao.insert", todo);
}
public Todo select(int id) {
return (Todo) operations.queryForObject("com.example.dao.TodoDao.select", id);
}
}
Note:
ibatis-springはSpring 3系の完全クローンなので、
@Deprecated
がついたままです。非推奨は100も承知で使っているので、クラスレベルに@SuppressWarnings("deprecation")
をつけて警告を削除しています。
MyBatis 2の設定ファイルの作成
MyBatis 2の設定ファイルを作成し、MyBatis 2の動作を指定します。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig
PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
<settings useStatementNamespaces="true"/> <!-- ネームスペースを有効にする -->
</sqlMapConfig>
SQLマッピングファイルの作成
SQLマッピングファイルを作成し、TodoオブジェクトのCRUD操作を行うためのSQLを記述します。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap
PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap namespace="com.example.dao.TodoDao">
<insert id="insert" parameterClass="com.example.domain.Todo">
<selectKey keyProperty="id" resultClass="int" type="pre">
SELECT seq_todo.nextval
</selectKey>
INSERT INTO todo
(
id
,title
,details
,finished
)
values(
#id#
,#title#
,#details#
,#finished#
)
</insert>
<select id="select" parameterClass="int" resultClass="com.example.domain.Todo">
SELECT
id, title, details, finished
FROM
todo
WHERE
id = #id#
</select>
</sqlMap>
MyBatis 2用のBean定義
SqlMapClient
とSqlMapClientTemplate
のBean定義を行います。 ここではMybatis2DemoApplication
のインナークラスとしてConfigクラスを作成しています。
package com.example;
import com.ibatis.sqlmap.client.SqlMapClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.orm.ibatis.SqlMapClientFactoryBean;
import org.springframework.orm.ibatis.SqlMapClientOperations;
import org.springframework.orm.ibatis.SqlMapClientTemplate;
import javax.sql.DataSource;
import java.io.IOException;
@SpringBootApplication
public class Mybatis2DemoApplication {
public static void main(String[] args) {
SpringApplication.run(Mybatis2DemoApplication.class, args);
}
// MyBatis 2用のBean定義
@Configuration
@SuppressWarnings("deprecation")
static class MyBatisConfig {
private final ResourcePatternResolver resourcePatternResolver;
MyBatisConfig(ResourcePatternResolver resourcePatternResolver) {
this.resourcePatternResolver = resourcePatternResolver;
}
@Bean
SqlMapClientFactoryBean sqlMapClient(DataSource dataSource) throws IOException {
SqlMapClientFactoryBean factoryBean = new SqlMapClientFactoryBean();
factoryBean.setConfigLocations(resourcePatternResolver.getResources("classpath*:/mybatis/sqlMapConfig.xml"));
factoryBean.setMappingLocations(resourcePatternResolver.getResources("classpath*:/mybatis/**/*-sqlmap.xml"));
factoryBean.setDataSource(dataSource);
return factoryBean;
}
@Bean
SqlMapClientOperations sqlMapClientOperations(SqlMapClient sqlMapClient) {
return new SqlMapClientTemplate(sqlMapClient);
}
}
}
Mybatis2DemoApplicationの修正とSpring Bootアプリケーションの起動
Mybatis2DemoApplication
を修正し、DAOクラス(ibatis-spring)を経由してデータベースにアクセスします。
package com.example;
import com.example.dao.TodoDao;
import com.example.domain.Todo;
import com.ibatis.sqlmap.client.SqlMapClient;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.orm.ibatis.SqlMapClientFactoryBean;
import org.springframework.orm.ibatis.SqlMapClientOperations;
import org.springframework.orm.ibatis.SqlMapClientTemplate;
import org.springframework.transaction.annotation.Transactional;
import javax.sql.DataSource;
import java.io.IOException;
@SpringBootApplication
public class Mybatis2DemoApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(Mybatis2DemoApplication.class, args);
}
private final TodoDao todoDao;
Mybatis2DemoApplication(TodoDao todoDao) {
this.todoDao = todoDao;
}
@Transactional
@Override
public void run(String... args) throws Exception {
Todo newTodo = new Todo();
newTodo.setTitle("飲み会");
newTodo.setDetails("銀座 19:00");
todoDao.insert(newTodo); // 新しいTodoをインサートする
Todo loadedTodo = todoDao.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());
}
@Configuration
@SuppressWarnings("deprecation")
static class MyBatisConfig {
private final ResourcePatternResolver resourcePatternResolver;
MyBatisConfig(ResourcePatternResolver resourcePatternResolver) {
this.resourcePatternResolver = resourcePatternResolver;
}
@Bean
SqlMapClientFactoryBean sqlMapClient(DataSource dataSource) throws IOException {
SqlMapClientFactoryBean factoryBean = new SqlMapClientFactoryBean();
factoryBean.setConfigLocations(resourcePatternResolver.getResources("classpath*:/mybatis/sqlMapConfig.xml"));
factoryBean.setMappingLocations(resourcePatternResolver.getResources("classpath*:/mybatis/**/*-sqlmap.xml"));
factoryBean.setDataSource(dataSource);
return factoryBean;
}
@Bean
SqlMapClientOperations sqlMapClientOperations(SqlMapClient sqlMapClient) {
return new SqlMapClientTemplate(sqlMapClient);
}
}
}
MybatisDemo2Application
を修正したら、Spring Bootアプリケーションとして起動します。
$ ./mvnw spring-boot:run
...
2016-12-17 10:37:21.407 INFO 10597 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
ID : 1
TITLE : 飲み会
DETAILS : 銀座 19:00
FINISHED : false
2016-12-17 10:37:21.501 INFO 10597 --- [ main] com.example.Mybatis2DemoApplication : Started Mybatis2DemoApplication in 1.463 seconds (JVM running for 4.619)
...
標準出力にインサートしたTodoの状態が出力されました
ちなみに、開発プロジェクトは以下のような状態になります。
.
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
├── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ ├── Mybatis2DemoApplication.java // 修正したファイル
│ │ ├── dao
│ │ │ └── TodoDao.java // 追加したファイル
│ │ └── domain
│ │ └── Todo.java // 追加したファイル
│ └── resources
│ ├── application.properties
│ ├── mybatis
│ │ ├── sqlMapConfig.xml // 追加したファイル
│ │ └── todo-sqlmap.xml // 追加したファイル
│ └── schema.sql // 追加したファイル
└── test
└── java
└── com
└── example
└── Mybatis2DemoApplicationTests.java
まとめ
とりあえず、問題なく動くことが確認できました!!
個人的にはSpring 4上でMyBatisを使いたいならMyBatis 3を使えや〜と思っておりますが、大人の事情でそうもいってられないこともあると思います。とりあえず影響範囲をできるだけ最小限におさえてEOL対策したいと思うのも理解はできるので、「Spring 3系 + MyBatis 2を使っているアプリケーション」でSpring 3系のEOL対策をお考えの方は、Spring 4.3へのバージョンアップ + 「ibatis-spring」の利用を検討してみるとよいかと思います。
なお、Spring 3系からSpring 4系(or Spring 5系)への移行を考えている方は、以下のサイト(Spring本家の移行ガイド、TERASOLUNAの移行ガイド)が参考になると思います。