Spring Boot + MyBatisでSQLを書いた話
皆さん、こんにちは。技術開発グループのn-ozawanです。
明日から35周年記念の社員旅行としてオーストラリアに行ってきます。
本題です。
突然ですが、皆さんはORM (Object Relational Mapping) に何を使ってますか?一昔前はHibernateがもてはやされていた記憶があります。今はどうなのでしょう?今回はMyBatisを使ったDBアクセスをしたので、MyBatisについてのお話です。
目次
MyBatis
概要
MyBatisはORMの1つで、XMLベースにSQL文を記述します。ORMは、オブジェクトとRDBを紐づける仕組みですが、MyBatisはオブジェクトとSQLを紐づける仕組みなので、厳密にはORMではありません。ただ、ORMを語る際に(個人的な感覚では)よく話題に上がっている印象があります。
MyBatisのメリットはSQLの自由度にあります。一般的なORMはSQLを記述することが出来ませんが、MyBatisはSQLを記述してDBを操作します。MyBatisは記述したSQLとオブジェクトを自動的に紐づけてくれますので、型変換などの煩わしさから解放されます。しかし、動的なSQLを作るとなるとXMLで記述する必要があるため可読性が下がるほか、そもそもSQL文を記述する煩わしさもあります。中規模以上のシステムを構築する際には工夫が必要になるかと思います。
導入
Spring Boot + Gradle でMyBatisを導入するのはそんなに難しくありません。MyBatisをインストールするためにbuild.gradle
に以下を記述します。
dependencies {
implementation "org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3"
}
いろんな書き方
MyBatisはXMLファイルにSQL文の書くやり方から、アノテーションを使ってSQLを書くやり方まで、多様に用意されています。
XMLファイルにSQLを書く方法
まずはXMLファイルの格納場所をapplication.properties
に指定します。
mybatis.mapper-locations=classpath*:/mapper/*.xml
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.co.iosnet.example.repository.ExampleRepository">
<select id="selectExample" resultType="jp.co.iosnet.example.model.ExampleModel">
SELECT
*
FROM
example_table;
WHERE
id = #{id}
</select>
</mapper>
MyBatisのXMLではmapper
要素で囲みます。namespace
属性にはJavaのクラスを指定します。検索するSQLを記述する場合はselect
要素に記述します。select
要素のid
属性には、このSQLを実行するメソッド名を記述します。resultType
属性はSQLの結果を格納するオブジェクトです。
Javaのコードは以下のようになります。
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ExampleRepository {
Optional<ExampleModel> selectExample(String id);
}
注意点はExampleRepository
はinterfaceとして定義することです。interfaceですので、selectExample
メソッドの中身を実装する必要はありません。クラス名やメソッド名、返却する値の型はXMLファイルに記述した内容と一致させる必要があります。
ExampleRepository
をインジェクションしてメソッドを呼び出すだけで、SQLを実行してその結果を取得することが出来ます。
@Autowired
private ExampleRepository exampleRepository;
// SQL実行
Optional<ExampleModel> result = exampleRepository.selectExample("1");
アノテーションでSQLを書く方法
ちょっとしたSQLであればアノテーションで記述するのをお勧めします。
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ExampleRepository {
@Select("""
SELECT
*
FROM
example_table;
WHERE
id = #{id}
""")
Optional<ExampleModel> selectExample(String id);
}
XMLファイルを別途用意する必要はありません。@Select
アノテーションを付与するだけでSQL文が記述出来ます。
もちろんアノテーションでも動的にSQL文を記述することが出来ます。
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ExampleRepository {
@Select("""
<script>
SELECT
*
FROM
example_table;
WHERE
id IN
<foreach item=\"id\" collection=\"idList\" open=\"(\" separator=\",\" close=\")\">
#{id}
</foreach>
</script>
""")
List<ExampleModel> selectExample(String[] idList);
}
ユニークキーのIDを複数個指定して検索するSQLにしました。一段と見辛くなりましたね。foreach
要素はその名の通り、繰り返しとなる要素です。上記のコードでは、idList
に[1, 2, 3]
を指定すると、(1, 2, 3)
に変換されます。
ビルダークラスを使ってSQLを書く方法
XMLはいやだ!という方のために、MyBatisは別の方法を提供しています。
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ExampleRepository {
@SelectProvider(type = ExampleSqlProvider.class, method = "selectExample")
List<ExampleModel> selectExample(String[] idList);
public class ExampleSqlProvider {
public String selectExample(String[] idList) {
return new SQL() {{
SELECT("*");
FROM("example_table");
WHERE("id IN (" + String.join(",", idList) + ")");
}}
}
}
}
SQLを生成するクラスとしてExampleSqlProvider
クラスを定義しています。new SQL() {{ ... }}
によりSQLを文字列として生成します。Javaのコードで書いているので、XMLよりかは親和性があるかもしれません。詳しくは公式サイトにサンプルコードが沢山あるのでそちらを参照してください。
ただし、ORMが登場する前に、StringBufferなどでSQL文を実装していたやり方とそんなに大差がない気がします。私の理解が及ばないところで、利便性があったりするのでしょうか。
おわりに
RDBとオブジェクトでデータの持ち方やデータの型が異なります。この差異を実装するのは非常に手間であり、コスト増加の要因ともなっていました。そこで登場したのがORMです。ORMの登場によりRDBとオブジェクトを自動的に紐づけてくれるので実装が楽になります。
ただ、JPAを元に構築されたHibernateなどを見ても、ORMの学習コストが非常に高いです。SQLを直接記述出来ないため、ORMは処理が遅い、という印象を持ってしまいます。きちんと使いこなせばそんな印象は間違いということもあるかもしれませんが、先ほど述べた通り学習コストが高いので使いこなすまで時間がかかりそうです。プロジェクトの要員を揃えるのにも一苦労しそうです。
MyBatisはSQLを記述出来るため細かな指定が出来ます。ただし、ちょっとしたデータ取得のためにSQLを記述しなくてはならない点、人によってはSQLの品質に問題が生じる点など、中規模以上のシステムでは導入は難しい気がします。小規模であれば導入しても良いかと思いました。
ではまた。