概要
O/Rマッパーではなく、SQL文とオブジェクトのマッピングを行ってくれるというMyBatis(旧:iBatis)を試してみました。
DBはDB2 for i(AS400)となります。
結論から申しますと非常に便利!! しかしながら、DBがAS400だからなのかもしれませんが、何点か注意すべきことがあったので、学んだことをこちらにまとめておきたいと思います。
実行環境
- Java
- Mybatis
- Driver : AS/400 Toolbox for Java JDBC Driver
- Version : 8.11 (JT400 6.7)
- Database : DB2 UDB for AS/400
- Version : 07.01.0000 V7R1m0
はまったこと
(1) ライブラリーリストが機能しない
AS400の魅力のひとつに「ライブラリーリスト」という概念があります。
簡単にいうと、複数のスキーマを参照できるパスのようなもので、SQL文でスキーマ名を指定しなくても、そのライブラリーリストの中から順にテーブルを探してくれるという優れものなのです。
つまりは、本番環境用とテスト環境用のスキーマ(ライブラリー)を分けて運用している場合、ライブラリーリストの中身を変えるだけでSQL文は変更不要で共通化できるようになります。
ところが、今回の検証では、ライブラリーリストの設定が効きませんでした。
JDBCの設定は、下記の例の通り、DB接続オプション「libraries」で指定可能です。
Connection con = DriverManager.getConnection(
jdbc:as400:
MybatisでDB接続したジョブを調査したところ(WRKJOB - 13.ライブラリー・リストの表示)、
きちんと指定した2つのライブラリーが存在していましたが、SQLで指定したTIGERDB2に存在するテーブルを参照できず、エラーとなりました。
Cause: java.sql.SQLException: [SQL0204] TIGERDBのタイプ*FILEのTEST_TABLEが見つからない。
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:150)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:141)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:136)
:
これはMybatisの仕様なのでしょうか。
ただし、librariesオプションの最初に指定したライブラリーのテーブルは、参照できました!
スキーマは1つだけで、限定で使っていくしかなさそうです。
なお、Mybatisでは、定義ファイルでdataSourceを複数管理できるので、こちらの指定でテストと本番の切り替えはできそうです。
安心しました!
(2) resultMapのcolumn値でピリオド付きフィールドが使えない
下記のようなSQL文をresultMapに紐づけしようとした際に、「テーブル別名.フィールド名」の部分に値が格納されませんでした。(SQLは検証時のもので故意的にJOINしています)
<mapper namespace="tigertaizo.mappers.CompanyMapper">
<resultMap id="companyResultMap" type="tigertaizo.models.Company">
<id property="companyCode" column="CP1.COMPANY_CODE"/>
<result property="companyValue1" column="CP2.COMPANY_VALUE1"/>
<result property="companyValue2" column="CP2.COMPANY_VALUE2"/>
</resultMap>
<select id="selectTest" resultMap="companyResultMap">
SELECT
CP1.COMPANY_CODE,
CP2.COMPANY_VALUE1,
CP2.COMPANY_VALUE2
FROM COMPANY AS CP1
INNER JOIN COMPANY_VALUES AS CP2 ON CP1.COMPANY_CODE = CP2.COMPANY_CODE
WHERE CP1.COMPANY_CODE = #{companyCode}
</select>
</mapper>
これは大した問題ではなく、SQL文のフィールド名を別名にして、resultMapのcolumn値としました。解決です!
<mapper namespace="tigertaizo.mappers.CompanyMapper">
<resultMap id="companyResultMap" type="tigertaizo.models.Company">
<id property="companyCode" column="COMPANY_CODE"/>
<result property="companyValue1" column="COMPANY_VALUE1"/>
<result property="companyValue2" column="COMPANY_VALUE2"/>
</resultMap>
<select id="selectTest" resultMap="companyResultMap">
SELECT
CP1.COMPANY_CODE AS COMPANY_CODE,
CP2.COMPANY_VALUE1 AS COMPANY_VALUE1,
CP2.COMPANY_VALUE2 AS COMPANY_VALUE2
FROM COMPANY AS CP1
INNER JOIN COMPANY_VALUES AS CP2 ON CP1.COMPANY_CODE = CP2.COMPANY_CODE
WHERE CP1.COMPANY_CODE = #{companyCode}
</select>
</mapper>
(3) 最初のN行のみの選択したい場合にN値をパラメータで渡せない
新しいDB2 for iのVersionでは「LIMIT」句が使えるようになったという話ですが、私が参照するVersionではLIMIT句は非サポート。
最初のN行取得する場合のSQL文は「FETCH FIRST N ROWS ONLY」を使うのですが、このN行をパラメータで渡せず。。。
<mapper namespace="tigertaizo.mappers.CompanyMapper">
<resultMap id="companyResultMap" type="tigertaizo.models.Company">
<id property="companyCode" column="COMPANY_CODE"/>
<result property="companyValue1" column="COMPANY_VALUE1"/>
<result property="companyValue2" column="COMPANY_VALUE2"/>
</resultMap>
<select id="selectTest" resultMap="companyResultMap">
SELECT
CP1.COMPANY_CODE AS COMPANY_CODE,
CP2.COMPANY_VALUE1 AS COMPANY_VALUE1,
CP2.COMPANY_VALUE2 AS COMPANY_VALUE2
FROM COMPANY AS CP1
INNER JOIN COMPANY_VALUES AS CP2 ON CP1.COMPANY_CODE = CP2.COMPANY_CODE
FETCH FIRST #{count} ROWS ONLY
</select>
</mapper>
これはパラメータ渡しができないようで、SQL文字列の埋め込みをする動的SQLで対応するしかないようです。
「#{count}」の部分を 「${count}」へ変更したところ、うまく動きました!
(4) CHARフィールドのトリムがされない
JPAなどのO/Rマッパーでは、CHAR文字列のフィールドであっても、自動的にトリムされてJavaのフィールドにセットされるのですが、
Mybatis経由では、トリムされずにそのままセットされてしまいました。
JavaのDAOクラスでは、モデルにセットする際に.trim()をかましていたのですが、これは困りました。(レガシーなCHARが問題のような気もしますが・・・)
仕方なくSQL側でRTRIMすることで対処しました。大量のレコード取り出し時でのSQLパフォーマンスが気になるところです。
<mapper namespace="tigertaizo.mappers.CompanyMapper">
<resultMap id="companyResultMap" type="tigertaizo.models.Company">
<id property="companyCode" column="COMPANY_CODE"/>
<result property="companyValue1" column="COMPANY_VALUE1"/>
<result property="companyValue2" column="COMPANY_VALUE2"/>
</resultMap>
<select id="selectTest" resultMap="companyResultMap">
SELECT
RTRIM(CP1.COMPANY_CODE) AS COMPANY_CODE,
RTRIM(CP2.COMPANY_VALUE1) AS COMPANY_VALUE1,
RTRIM(CP2.COMPANY_VALUE2) AS COMPANY_VALUE2
FROM COMPANY AS CP1
INNER JOIN COMPANY_VALUES AS CP2 ON CP1.COMPANY_CODE = CP2.COMPANY_CODE
WHERE CP1.COMPANY_CODE = #{companyCode}
</select>
</mapper>
数日試したところでは、以上4点が気になりましたが、MyBatisの魅力は十分認識できました!
JPAとうまく使い分けしていきたいところです!!!