MyBatis / iBatis の動的 SQL を API で作成

MyBatis / iBatisAPI を使って DB へ接続せずに Mapper XML の動的 SQL を作成する方法です。

ソースは http://github.com/fits/try_samples/tree/master/blog/20141221/

MyBatis の場合

動的 SQL の結果を取得する手順は下記のようになります。

  • (1) Configuration をインスタンス
  • (2) (1) と Mapper XML で XMLMapperBuilder をインスタンス
  • (3) Mapper XML をパース
  • (4) (1) から指定の SQL に対応した MappedStatement を取得
  • (5) (4) で取得した MappedStatement へパラメータを渡して動的 SQL を構築、結果の SQL を取得

今回は Groovy で実装してみました。

動的 SQL のパラメータはコマンドライン引数で JSON 文字列として指定するようにしています。

mybatis_sql_gen.groovy
@Grab('org.mybatis:mybatis:3.2.8')
import org.apache.ibatis.session.*
import org.apache.ibatis.builder.xml.*

import groovy.json.JsonSlurper

if (args.length < 3) {
    println '<mybatis mapper xml> <sql id> <json params>'
    return
}
// (1)
def config = new Configuration()
// (2)
def parser = new XMLMapperBuilder(new File(args[0]).newInputStream(), config, "", config.sqlFragments)
// (3)
parser.parse()
// (4)
def st = config.getMappedStatement(args[1])
// パラメータの作成(JSON 文字列から)
def params = new JsonSlurper().parseText args[2]
// (5)
def sql = st.getBoundSql(params).sql

println sql

実行

下記 Mapper XML を使って実行してみます。

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="sample">
  <select id="findData">
    SELECT * FROM data
    WHERE
      title like #{title}
    <if test="author != null and author.name != null">
      AND author_name like #{author.name}
    </if>
    <if test="types">
      AND type in 
      <foreach item="type" collection="types" open="(" separator="," close=")">
        #{type}
      </foreach>
    </if>
  </select>
</mapper>
実行例1

まずはパラメータ無しの場合。{}

> groovy mybatis_sql_gen.groovy mapper.xml findData "{}"

SELECT * FROM data
    WHERE
      title like ?
実行例2

次に、author.name と types パラメータを指定した場合。{"author":{"name": 1}, "types": [1, 2, 3]}

なお、今回の動的 SQL ではパラメータの有無しか見ていませんので、値には適当な数値 (1[1, 2, 3]) を使っています。

> groovy mybatis_sql_gen.groovy gen.groovy mapper.xml findData "{\"author\":{\"name\": 1}, \"types\": [1, 2, 3]}"

SELECT * FROM data
    WHERE
      title like ?

      AND author_name like ?


      AND type in
       (
        ?
       ,
        ?
       ,
        ?
       )

iBatis の場合

iBatis の場合も、使用する API は異なりますが同じような手順で処理できます。

ibatis_sql_gen.groovy
@Grab('org.apache.ibatis:ibatis-sqlmap:2.3.4.726')
import com.ibatis.sqlmap.engine.builder.xml.*
import com.ibatis.sqlmap.engine.scope.*

import groovy.json.JsonSlurper

if (args.length < 3) {
    println '<ibatis mapper xml> <sql id> <json params>'
    return
}

def state = new XmlParserState()

def parser = new SqlMapParser(state)
parser.parse(new File(args[0]).newInputStream())

// SqlMapExecutorDelegate を取得
def dlg = state.config.delegate

def st = dlg.getMappedStatement(args[1])
def sql = st.sql

def scope = new StatementScope(new SessionScope())
scope.statement = st

// パラメータの作成(JSON 文字列から)
def params = new JsonSlurper().parseText args[2]

println sql.getSql(scope, params)

実行

下記 Mapper XML を使って同様に実行してみます。

mapper.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="sample">
  <select id="findData">
    SELECT * FROM data
    WHERE
      title like #title#
    <isNotNull property="author">
      <isNotNull property="author.name">
        AND author_name like #author.name#
      </isNotNull>
    </isNotNull>
    <isNotNull property="types">
      AND type in
      <iterate property="types" open="(" conjunction="," close=")">
        #types[]#
      </iterate>
    </isNotNull>
  </select>
</sqlMap>
実行例1
> groovy ibatis_sql_gen.groovy mapper.xml findData "{}"

     SELECT * FROM data     WHERE       title like ?
実行例2
> groovy ibatis_sql_gen.groovy mapper.xml findData "{\"author\":{\"name\": 1}, \"types\": [1, 2, 3]}"

     SELECT * FROM data     WHERE       title like ?                     AND author_name like ?                        AND type in       (         ?       ,     ?       ,         ?       )