Help us understand the problem. What is going on with this article?

MyBatis2(iBatis)をSpring Boot 1.4(Spring 4)でつかう方法

More than 3 years have passed since last update.

今回は今更ながら・・・・Spring Boot 1.4(Spring 4.3)上でMyBatis 2(iBatisといった方が馴染みがあるかな?)を使う方法を紹介します。なぜ今頃MyBatis 2の話やねん?と思う方がほとんどだと思います:sweat_smile:が、このエントリーを書こうと思った動機は・・・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」なるものが提供されているんです:grin:
こいつの実体は、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」を指定します。

spring-initializr.png

完成品は以下のGitHubリポジトリで公開してます。

Note:
2016/12/17 13:30 追記

Spring Boot 2.0(Spring 5.0)のSNAPSHOTバージョンでの動作確認も行いました!!結果はもちろん:thumbsup:

mybatis2とibatis-springを依存アーティファクトに追加

pom.xml
<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.sqldata.sqlというファイルを配置しておくと、Spring Boot起動時にこれらのファイルを読み込んでSQLを実行してくれます。

src/main/resources/schema.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クラスを作ります。

src/main/java/com/example/domain/Todo.java
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クラスを作ります。

src/main/java/com/example/dao/TodoDao.java
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の動作を指定します。

src/main/resources/mybatis/sqlMapConfig.xml
<?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を記述します。

src/main/resources/mybatis/todo-sqlmap.xml
<?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定義

SqlMapClientSqlMapClientTemplateの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の状態が出力されました :clap: :clap: :clap:

ちなみに、開発プロジェクトは以下のような状態になります。

ディレクトリ構成
.
├── 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の移行ガイド)が参考になると思います。

kazuki43zoo
Javaエンジニアで、SpringやMyBatisらへんにそれなりに詳しいです。お仕事のつながりで「Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発」を共著させてもらいました!
https://kazuki43zoo.github.io
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした