谷本 心 in せろ部屋

はてなダイアリーから引っ越してきました

BootのO/RマッパーにMyBatisを使いたい。

DBアクセス層に何を使うかって、本当によく話題になりますよね。
「これで間違いないでしょ」っていう鉄板の選択肢がないから、だと思うわけですが
SQLを書きたい日本人」な僕としては、消去法的にMyBatisを使っています。


消去法って言うからには、消えた選択肢があるわけで。


Hibernate : アイドントライク ハイバネートサン
JPA : アイドントライク ハイバネートサン
Doma : アイドントライク APT
S2JDBC : キャノット ユーズ ウィズ スプリング
DBflute : 少し文化が違うんです。
Mirage : 開発止まっちゃったし。


ホントはMirageあたりが大好物で、過去に実案件に投入した時には
まったく問題が起きなくて素晴らしかったのですが
開発が止まっていることと、実績的なアレでなかなか使いにくいんです。


そんなわけで消去法的に残ったMyBatisを使うため
Spring Boot + MyBatisの設定をしました。


ところで、テーブルのカラム名はスネークケース、JavaのEntityはキャメルケース、
という組み合わせで使うことは、とてもよくあることだと思いますが、
MyBatisでそれを行うためには、設定ファイルを書く必要があります。
(あるいはConfigurationクラスを使う)


また、Java8で使えるようになったLocalDateクラスを利用する場合も
TypeHandlerと設定ファイルを書く必要があります。


その辺りを盛り込んだのが、以下になります。


src/main/java/適当なパッケージ/MyBatisConfig.java

import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

@Configuration
@MapperScan("package.names.to.persistence") // Mapper層のパッケージ名を指定
public class DataConfig {
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        sessionFactory.setTypeAliasesPackage(Entity.class.getPackage().getName());
        sessionFactory.setConfigLocation(new ClassPathResource("/mybatis-config.xml"));
        return sessionFactory;
    }
}

このように、mybatis-config.xmlを読み込むように設定を入れました。


設定ファイルは、こんな感じになります。
src/main/resources/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"/>
    </settings>
    <typeHandlers>
        <typeHandler handler="xxx.LocalDateTypeHandler"/>
    </typeHandlers>
</configuration>

スネークケースからキャメルケースへの変換と
LocalDateを使うための型ハンドラの定義を書いています。


型ハンドラの実装は、こんな感じです。
src/main/java/xxx/LocalDateTypeHandler.java

package xxx;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;

import java.sql.CallableStatement;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;

@MappedTypes(LocalDate.class)
public class LocalDateTypeHandler extends BaseTypeHandler<LocalDate> {
    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, LocalDate localDate,
                                    JdbcType jdbcType) throws SQLException {
        preparedStatement.setDate(i, Date.valueOf(localDate));
    }

    @Override
    public LocalDate getNullableResult(ResultSet resultSet, String s) throws SQLException {
        Date date = resultSet.getDate(s);
        if (date == null) {
            return null;
        }
        return date.toLocalDate();
    }

    @Override
    public LocalDate getNullableResult(ResultSet resultSet, int i) throws SQLException {
        return resultSet.getDate(i).toLocalDate();
    }

    @Override
    public LocalDate getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        return callableStatement.getDate(i).toLocalDate();
    }
}


こんな感じで設定をすれば、あとは世の中にあるサンプル通りに
Mapperクラスを作り、domainクラスを作り、SQLを書いたXMLファイルを作れば動きます。


ただIDEからJUnitを動かした後に、mvn spring-boot:runで起動したり、
逆に、mvn spring-boot:runして起動&停止した後に、JUnitを実行させたりすると
なぜか設定ファイルが見つからず無限ループになってしまうなど、微妙にバギーな挙動をします。


あと、MyBatisのConfigurationクラスと、SpringのConfigurationアノテーション
名前が被ってしまっているからMyBatisのConfigurationクラスが使いにくいとか、
どうも、Spring Boot + MyBatisの組み合わせは、こなれてない印象があります。


もう少しバージョンが上がれば、良い感じになっていくのかな?