MyBatis

概要

MyBatisはXMLにSQLを記述することでJavaとRDBとの相互変換を実現するライブラリ。MyBatis3からはアノテーションを使用することでマップを記述できるようになっている。

設定ファイル

MyBatisはXMLによって多くの設定を記述する。実際にコードからMyBatisを呼び出すときにはこの設定ファイルをロードする必要がある。

MyBatis | 設定

例えば以下のように記述する。

<?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 の子要素になる -->
<configuration>
    <!-- 設定の内容を上書きするプロパティファイルを指定する。プロパティファイルの値は、このファイルから参照できる。 -->
    <properties resource="mybatis.properties"/>
    <!-- Mapped SQL Statement で使用する型のエイリアス設定 -->
    <typeAliases>
        <typeAlias type="com.bookstore.scrum.domain.IceBoxItem" alias="IceBoxItem"/>
        <typeAlias type="com.bookstore.scrum.domain.IceBoxItemId" alias="IceBoxItemId"/>
    </typeAliases>
    <!-- DBの型とJavaの型を相互変換するハンドラ。独自に定義する場合はこのように明記する。 -->
    <typeHandlers>
        <package name="com.bookstore.scrum.repository.mybatis.typehandlers"/>
    </typeHandlers>
    <!-- 接続先DBの環境。複数持つことができ、開発環境、テスト環境、本番環境などで簡単に接続先を切り替えることができる。 -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!-- 上記のプロパティファイルから読み込んだ値を使用してDBとの接続設定を記述 -->
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <!-- Mapped SQL Statement の定義 -->
    <mappers>
        <mapper resource="mapper/IceBoxItemMapper.xml"/>
    </mappers>
</configuration>

この時読み込んでいるプロパティファイルはこんな感じになった。

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/scrum
username=scrum
password=scrum

この場合、コード上では以下のように設定ファイルをロードする。

val configPath = "mybatis-config.xml"
val sqlSessionFactory = SqlSessionFactoryBuilder().build(Resources.getResourceAsStream(configPath))

MappedSQLStatement

Mapped SQL Statement が MyBatis の主要な機能になる。Mapped SQL Statement はXMLファイルによって記述されたJavaとDBとの相互変換定義のことで、コードでこれらのことを実現するより、簡単に相互変換を実現できるらしい。

MyBatis | Mapper XML ファイル

select

DBからデータを取り出すマップを定義する。

<?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">
<!-- IDを指定してIceBoxItemというJavaのインスタンスを一つ取得するStatement -->

<!-- Statement の名前空間 -->
<mapper namespace="com.bookstore.scrum.repository.mybatis.mapper.IceBoxItemMapper">
    <!-- parameterTypeでSQLの引数を、resultTypeで結果の型を定義する -->
    <select id="selectIceBoxItem" parameterType="String" resultType="IceBoxItem">
        <!-- 実際のSQL -->
        SELECT *
        FROM iceboxitem
        <!-- #{ ... } は引数によって展開される -->
        WHERE id = #{id}
    </select>
</mapper>

select要素によって、DBからデータを取り出す定義ができる。実際のSQLはselect要素の中に記述する。IntelliJ IDEA ではSQLを自動的に認識してシンタックスハイライトや補完などをしてくれた。素晴らしい。

定義したXMLファイルは設定ファイルから読み込むことでコード上から呼び出すことができる。

    ...
    <!-- Mapped SQL Statement の定義 -->
    <mappers>
        <mapper resource="mapper/IceBoxItemMapper.xml"/>
    </mappers>
    ...

実際に上記の内容を呼び出してみた。DBの中身はいい感じにセットアップしてあるとして、他にも用意するものが必要になる。

戻り値の型

// IceBoxItem.kt
//
// Entityクラスは特に関係ないクラス。
//
class IceBoxItem(iceBoxItemId: IceBoxItemId, val title: String, val description: String) :
    Entity<IceBoxItem>(iceBoxItemId.rawId) {

    fun toProductBacklogItem(priority: Priority, point: Point): ProductBacklogItem =
        ProductBacklogItem(ProductBacklogItemId("dummy"), title, description, priority, point)

}

// IceBoxItemId.kt
class IceBoxItemId(val rawId: String)

IceBoxItemのコンストラクタにIceBoxItemIdがあるため独自にハンドラを定義する必要がある。この場合はIdがVARCHARでDBに保存されているので、IceBoxItemId <--> VARCHARのTypeHandlerを定義する。

// DB の VARCHAR と IceBoxItemId 型を相互変換する
@Suppress("unused")
@MappedJdbcTypes(JdbcType.VARCHAR)
class IceBoxItemIdHandler : BaseTypeHandler<IceBoxItemId>() {

    override fun setNonNullParameter(ps: PreparedStatement?, i: Int, parameter: IceBoxItemId?, jdbcType: JdbcType?) {
        ps!!.setString(i, parameter!!.rawId)
    }

    override fun getNullableResult(rs: ResultSet?, columnName: String?): IceBoxItemId {
        return IceBoxItemId(rs!!.getString(columnName))
    }

    override fun getNullableResult(rs: ResultSet?, columnIndex: Int): IceBoxItemId {
        return IceBoxItemId(rs!!.getString(columnIndex))
    }

    override fun getNullableResult(cs: CallableStatement?, columnIndex: Int): IceBoxItemId {
        return IceBoxItemId(cs!!.getString(columnIndex))
    }

}

このTypeHandlerは設定ファイルによって明記する必要がある。

    ...
    <typeHandlers>
        <!-- 以下のパッケージ内にあるハンドラを自動登録する -->
        <package name="com.bookstore.scrum.repository.mybatis.typehandlers"/>
    </typeHandlers>
    ...

実際に呼び出してみよう。呼び出すときにはStatementの名前空間+IDのフルネームで Mapped SQL Statement を指定する。

sqlSessionFactory.openSession().let { session ->
    val iceBoxItem = session.selectOne<IceBoxItem>("com.bookstore.scrum.repository.mybatis.mapper.IceBoxItemMapper.selectIceBoxItem", "10")
    iceBoxItem.apply {
        println(id)
        println(title)
        println(description)
    }
    session.close()
}