MyBatis / iBatis の動的 SQL を API で作成
MyBatis / iBatis の API を使って 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 ( ? , ? , ? )