1. はじめに
かなり久しぶりのQiita投稿になります。とあるシステム開発プロジェクトのFW刷新においてDBアクセスにiBatisを利用していたものをMyBatisに変更する作業を行いました。設定ファイル(sqlmapファイル→mapperファイル)の変換処理はibatis2mybatisを利用して何とか終わりましたが、動的SQLの評価箇所を集中的に試験・確認したい状況になりました。そこで動的SQLの情報やパラメータ情報を取得したい必要性が発生して今回の記事となります。ソースコードについては量があるので別の記事としました。
2. 概要
MyBatisのmapperファイルやiBatisのSqlMapファイルを読み込み、XMLのタグ情報、属性情報、テキストノードの情報を取得、タグの入れ子構造を考慮して解析結果をJSON形式で標準出力に出力するツールです。標準出力なのでパイプやリダイレクトで他のコマンド(grep
,jq
等)やファイル出力に連携できます。
ポイントはテキストノード内のパラメータ(#{xxx}
,${xxx}
)やタグでパラメータを指定している属性を取得して纏めて分かるようにしているところです。なお、外部ライブラリの導入がいろいろメンドウな状況なのでJava17の標準機能のみで実装します。
3. 実行例
3.1. コマンド
java app.SqlMapAnalyze ./demo/TodoDemo.sqlmap.xml
3.2. 入力のmapperファイル
<?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">
<!-- (1) -->
<mapper namespace="todo.domain.repository.todo.TodoRepository">
<!-- (2) -->
<resultMap id="todoResultMap" type="Todo">
<id property="todoId" column="todo_id" />
<result property="todoTitle" column="todo_title" />
<result property="finished" column="finished" />
<result property="createdAt" column="created_at" />
</resultMap>
<!-- (3) -->
<select id="findOne" parameterType="String" resultMap="todoResultMap">
<![CDATA[
SELECT
todo_id,
todo_title,
finished,
created_at
FROM
todo
WHERE
todo_id = #{todoId}
]]>
</select>
<!-- (4) -->
<select id="findAll" resultMap="todoResultMap">
<![CDATA[
SELECT
todo_id,
todo_title,
finished,
created_at
FROM
todo
]]>
</select>
<!-- (5) -->
<insert id="create" parameterType="Todo">
<![CDATA[
INSERT INTO todo
(
todo_id,
todo_title,
finished,
created_at
)
VALUES
(
#{todoId},
#{todoTitle},
#{finished},
#{createdAt}
)
]]>
</insert>
<!-- (6) -->
<update id="update" parameterType="Todo">
<![CDATA[
UPDATE todo
SET
todo_title = #{todoTitle},
finished = #{finished},
created_at = #{createdAt}
WHERE
todo_id = #{todoId}
]]>
</update>
<!-- (7) -->
<delete id="delete" parameterType="Todo">
<![CDATA[
DELETE FROM
todo
WHERE
todo_id = #{todoId}
]]>
</delete>
<!-- (8) -->
<select id="countByFinished" parameterType="Boolean"
resultType="Long">
<![CDATA[
SELECT
COUNT(*)
FROM
todo
WHERE
finished = #{finished}
]]>
</select>
</mapper>
3.3. 解析結果のJSON
{
"param": {
"dirPath": "./demo/TodoDemo.sqlmap.xml",
"fileSuffix": ".xml",
"type": "-mybatis",
"analyzeTime": "2024-02-26T22:48:54.681389"
},
"result": [
{
"filePath": "./demo/TodoDemo.sqlmap.xml",
"sqlMap": {
"nestLevel": "1",
"tag": "mapper",
"text": "",
"params": null,
"attributes": {
"namespace": "todo.domain.repository.todo.TodoRepository"
},
"tags": [
{
"nestLevel": "2",
"tag": "select",
"text": " SELECT todo_id, todo_title, finished, created_at FROM todo WHERE todo_id = #{todoId} ",
"params": [
"todoId"
],
"attributes": {
"parameterType": "String",
"resultMap": "todoResultMap",
"id": "findOne"
},
"tags": []
},
{
"nestLevel": "2",
"tag": "select",
"text": " SELECT todo_id, todo_title, finished, created_at FROM todo ",
"params": [],
"attributes": {
"resultMap": "todoResultMap",
"id": "findAll"
},
"tags": []
},
{
"nestLevel": "2",
"tag": "insert",
"text": " INSERT INTO todo ( todo_id, todo_title, finished, created_at ) VALUES ( #{todoId}, #{todoTitle}, #{finished}, #{createdAt} ) ",
"params": [
"todoId",
"todoTitle",
"finished",
"createdAt"
],
"attributes": {
"parameterType": "Todo",
"id": "create"
},
"tags": []
},
{
"nestLevel": "2",
"tag": "update",
"text": " UPDATE todo SET todo_title = #{todoTitle}, finished = #{finished}, created_at = #{createdAt} WHERE todo_id = #{todoId} ",
"params": [
"todoTitle",
"finished",
"createdAt",
"todoId"
],
"attributes": {
"parameterType": "Todo",
"id": "update"
},
"tags": []
},
{
"nestLevel": "2",
"tag": "delete",
"text": " DELETE FROM todo WHERE todo_id = #{todoId} ",
"params": [
"todoId"
],
"attributes": {
"parameterType": "Todo",
"id": "delete"
},
"tags": []
},
{
"nestLevel": "2",
"tag": "select",
"text": " SELECT COUNT(*) FROM todo WHERE finished = #{finished} ",
"params": [
"finished"
],
"attributes": {
"parameterType": "Boolean",
"id": "countByFinished",
"resultType": "Long"
},
"tags": []
}
]
}
}
]
}
4. さいごに
試しに解析してみたTodoDemo.sqlmap.xmlはタグの入れ子も浅く、また利用している動的SQLも少ないですが、プロジェクトのファイルはもっと複雑(入れ子も深く、動的SQLの数も多い)でした。最深の入れ子の動的SQLを有効にするには、その入れ子までの動的SQLを全て有効(TRUE)にしないと到達しません。そのような場合、XMLのタグの入れ子構造とパラメータにフォーカスした今回の方法は有効になるのではないでしょうか。次回はツールのソースコードについて説明したいと思います。