MyBatis による SQL マッピング

MyBatis とは

MyBatis(旧名・iBatis) とは SQL 文等と Java オブジェクト間のマッピングを行う永続化フレームワーク(O/R マッピングライブラリ)です。マッピングを行うことにより、データベースへの接続方法の変更等があった場合でも、プログラムのソースコードを変更することなく、設定ファイル(XML 形式)を修正するだけで良くなります。

例えば、テーブルの構造はそのままに、テーブル名やカラム名の変更が行われたとしましょう。このとき、従前のやり方ではソースコードを修正する必要がありました。しかし MyBatis なら、マッピングの設定が書かれている XML ファイルを修正するだけで問題なく動作し続けます。

こうしたデータベース側のちょっとした変更に対してシステム側の修正をなるべく少なくするために MyBatis が導入されることが良くあります。

似たようなものとしては Hibernate が有名で、世界的にはこちらの方が圧倒的に人気がありますが、日本では MyBatis もまだまだトレンドであるようです。

MyBatis の入手

MyBatis は以下のサイトから入手できます。
mybatis - SQL Mapping Framework for Java - Google Project Hosting

導入事例

当ブログでもたびたび使っている Customers プロジェクトを例に、MyBatis の導入例を見てみましょう。
まず /WEB-INF/lib フォルダに mybatis-3.2.1.jar をコピーします。次に src フォルダ直下に 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>
  <typeAliases>
    <typeAlias
      type="jp.mydns.akanekodou.model.Customer"
      alias="Customer" />
  </typeAliases>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC" />
      <dataSource type="POOLED">
        <property name="driver" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/menudb" />
        <property name="username" value="scott" />
        <property name="password" value="tiger" />
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="jp/mydns/akanekodou/dao/mapper/customer-mapper.xml" />
  </mappers>
</configuration>

データベースへの接続方法やマッピングの設定ファイルの指定などが主な内容です。

次に jp.mydns.akanekodou.dao.mapper パッケージを作成し、そこに customer-mapper.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="jp.mydns.akanekodou.dao.mapper.CustomerMapper">
  <resultMap id="customerResultMap" type="Customer">
    <id property="id" column="顧客ID" />
    <result property="name" column="顧客名" />
    <result property="phone" column="連絡先" />
  </resultMap>
  <select id="selectAll" resultMap="customerResultMap">
    SELECT * FROM 顧客マスタ
  </select>
</mapper>

ここで SQL 文のマッピングが行われています。説明はさておいて、同じパッケージ内に CustomerMapper.java というインターフェース*1を作成します。

package jp.mydns.akanekodou.dao.mapper;

import java.util.List;

import jp.mydns.akanekodou.model.Customer;

public interface CustomerMapper {
    List<Customer> selectAll();
}

抽象メソッドはマッピングの設定ファイルの select 要素の id 属性と合わせます。これで selectAll() というメソッドと SQL 文のマッピングができました。このインターフェースのフルパスは jp.mydns.akanekodou.dao.mapper.CustomerMapper で、ちょうど先程のマッピング設定における mapper 要素の namespace 属性の値と同じになります(ここは肝ですので間違えぬように)。

ここでちょっと Java を知っている人なら疑問が浮かぶはずです。
「インターフェースの実装はしなくていいの ?」
ごもっともです。しかし MyBatis はこのインターフェースとマッピングの設定ファイルに基づいて、動的に実装を行ってくれるので、自分で実装を書く必要がない*2のです。

次に DaoUtil.java を書き変えます。かなり大幅な変更になります。

package jp.mydns.akanekodou.dao;

import java.io.IOException;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class DaoUtil {
    private SqlSessionFactory sessionFactory;
    private static DaoUtil inst = new DaoUtil();

    private DaoUtil() {
        sessionFactory = createSqlSessionFactory();
    }

    public static SqlSessionFactory getSqlSessionFactory() {
        return inst.sessionFactory;
    }

    private SqlSessionFactory createSqlSessionFactory() {
        SqlSessionFactory ssf = null;

        try {
            ssf = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {
            e.printStackTrace();
        }

        return ssf;
    }
}

シングルトンとして実装している点は同じですが、今までの DataSource ではなくて SqlSessionFactory でデータベースとやりとりをすることになります。SqlSessionFactory は一つあれば事足りますので、このようにシングルトンとして実装することが推奨されています。SqlSessionFactory ができた時点でデータベースとの接続は完了*3していて、後はここから SqlSession を open して SQL 文を発行するための(先程定義した)メソッドを実行することになります。

当然 CustomerDAO.java も書き変えます。

package jp.mydns.akanekodou.dao;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;

import java.util.List;

import jp.mydns.akanekodou.dao.mapper.CustomerMapper;
import jp.mydns.akanekodou.model.Customer;

public class CustomerDAO {
    private SqlSessionFactory sessionFactory;

    public CustomerDAO() {
        sessionFactory = DaoUtil.getSqlSessionFactory();
    }

    public List<Customer> all() {
        SqlSession session = sessionFactory.openSession();
        CustomerMapper mapper = session.getMapper(CustomerMapper.class);
        List<Customer> list = mapper.selectAll();
        session.close();

        return list;
    }
}

「何やごちゃごちゃとファイル作ったり書き変えたり面倒やなぁ」とお思いでしょうが、これで DAO 関連のファイルに関してはデータベース側に何らかの変更がかかったとしてもほとんど修正することなく動くようになります。修正するのは customer-mapper.xml と、せいぜい Customer.java くらいです。

ちょっとした変更のたびにあのファイルもこのファイルも直さないと…というシステムは良いシステムとは言えません。便利なツールは積極的に導入して、変化に対して柔軟に対応できるシステム作りを目指しましょう。

*1:当ブログではインターフェースを作るのは初めてですね^^;

*2:まぁ自分で実装を書かなきゃいけないのならツールを導入するメリットはないですよね。

*3:今までのように DataSource から Connection を開いて…といったことはする必要がありません。