--
--

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Tag:

06
2011

[Java]MyBatisを使ってみた2(3.0.2)

CATEGORYJava
前回の続き。年末に会社辞めてしまったので(--;、忘れないうちにその後実際に使ってて気づいたこととか吐き出しておくよ~。

nullが入るパラメータの書き方

主にinsertやらupdateやらで遭遇。nullが入るパラメータで単純に前回のように NAME = #{name} とか書くと、nullのときに型が判んねえぞゴルァ!!とエラーになる。
ので、そういう場合はこんな風に記述する。
<update id="update" parameterType="foo.model.Member">
UPDATE MEMBERS
SET
NAME = #{name,jdbcType=VARCHAR},
STATUS = #{status,jdbcType=NUMERIC}
WHERE MEMBER_ID = #{memberId}
</update>
jdbcTypeの指定は、特に書くことによって問題が生じるわけではないので、とりあえず怪しいところは全部書いておけばよいと思われる。
(型を間違えたり、変わったときに面倒という点はあるが。)

jdbcTypeの一覧は公式ドキュメントUser Guideの Supported JDBC Types の辺りを参照。

SQL文の再利用

SQLの一部、例えば結合条件なんかを複数のSQLで使いまわすときはこんな感じ。
<sql id="selectJoin">
SELECT
M.*,
A.AUTH_ID AS AUTH_AUTH_ID,
A.STATUS AS AUTH_AUTH_STATUS
FROM MEMBERS M
LEFT JOIN AUTHS A ON (A.MEMBER_ID = O.MEMBER_ID)
</sql>
<select id="selectStatusHoge" resultMap="memberResultMap">
<include refid="selectJoin" />
WHERE M.STATUS = 1
</select>
sqlタグで宣言しておき、includeタグで参照する。

属性の格納方法(1件の場合)

前回のMemberクラスに、上のSQLに対応した関連として Auth というクラスが引っ付いている場合、resultMapを下記のようにすれば検索結果をまとめて格納可能。
<resultMap id="memberResultMap" type="foo.model.Member">
<id property="memberId" column="MEMBER_ID" />
<result property="name" column="NAME" />
<result property="status" column="STATUS" />
<result property="auth.authId" column="AUTH_AUTH_ID" />
<result property="auth.status" column="AUTH_STATUS" />
</resultMap>
上は手抜きな書き方。もうちょっとちゃんとやる場合は、Authクラス用のresultMapを定義して、それをassociationタグで指定することになると思う。
その場合は、resultMapを使いまわせる(SQLの列名もちゃんと使い回しを想定して常時ASで別名つけたりしないといけないが)。

属性の格納方法(n件の場合)

関連が List<Auth> みたいな場合、collectionタグを使用して記述する。
<resultMap id="memberResultMap" type="foo.model.Member">
<id property="memberId" column="MEMBER_ID" />
<result property="name" column="NAME" />
<result property="status" column="STATUS" />
<collection property="auths" ofType="foo.model.Auth">
<id property="authId" column="AUTH_AUTH_ID" />
<result property="status" column="AUTH_STATUS" />
</collection>
</resultMap>
idタグで一意にするための属性を指定する、というか指定しないと確かエラーになる(associationタグで同じような書き方をする場合も)。
idタグは複数指定可。ただしresultより前に書かないと駄目。

collectionタグを使うと、

MEMBER_IDNAMEAUTH_ID
1000Aさん1
1000Aさん2
1001Bさん1
1002CさんNULL

みたいな検索結果をちゃんとidタグの属性の値で一意になるようにして入れてくれるのでとても便利。
上の例なら、Memberが全2件で、Aさんの List<Auth> には2件、Bさんには1件が入る。
(SQLでのソートは必須かも。)

・・・これで万事オッケーと言いたいところだが、上のCさんのように、外部結合などでNULLになるような場合は注意が必要。
その性質上か、NULLでも List<Auth> は1件となり空のAuthオブジェクトがセットされてしまった。
そういったケースではJava側でオブジェクトの中身までチェックするような書き方をせざるを得ない。

その他、公式ドキュメントによればcollectionタグに別のselectタグを指定することもできるみたい。
試していないが、この方法なら仕組み上、上のような問題は起こらないと思われる。
でもそれ常識的に考えて1+N問題起こるよね・・・件数が予測できない場合は迂闊に使えなさそう。

パラメータ/検索結果のMapでの受け渡し

前回Mapも可のはず、と書いたが予想通りどちらも普通に可能だった。
検索結果ならresultMapタグで type="Map"、selectタグ等のパラメータなら parameterType="Map" とすればよい。

・・・と思って見返したら、前回既にMapでパラメータ渡すコード書いてるね?あれ?とにかくMapもちゃんと使えたよと。

enum⇔コード値の変換

Java側がenumでDBがコード値とかの場合は、自前でTypeHandlerインタフェースを実装したクラスを用意する必要がある。
雛形として org.apache.ibatis.type.BaseTypeHandler というクラスが用意されているみたいなので、abstractなメソッドを実装する。
説明を載せようかと思ったが、公式で使っているソースを見れば何となくイメージがつかめると思うので、そちらを参照で。

用意したTypeHandlerはresultMapや検索条件などで下記のように指定する。
<resultMap id="memberResultMap" type="foo.model.Member">
<id property="memberId" column="MEMBER_ID" />
<result property="name" column="NAME" />
<result property="status" column="STATUS" typeHandler="foo.dao.mapper.MemberStatusTypeHandler" />
</resultMap>
<select id="selectByStatus" parameterType="foo.model.Member" resultMap="memberResultMap">
SELECT * FROM MEMBERS
WHERE STATUS = #{status,typeHandler=foo.dao.mapper.MemberStatusTypeHandler}
</select>

余分なWHERE句の除去

前回手抜きした部分(^^;
<select id="selectByCondition" parameterType="Map" resultMap="memberResultMap">
SELET * FROM MEMBERS
WHERE 1 = 1
<if test="name != null">
AND NAME LIKE '%' || #{name} || '%'
</if>
</select>
絞込み条件があれば指定なければ全部みたいなSQLの場合、上でも別にいいのだけど、何かみっともない。そういうときはtrimタグを使用する。
<select id="selectByCondition" parameterType="Map" resultMap="memberResultMap">
SELET * FROM MEMBERS
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="name != null">
AND NAME LIKE '%' || #{name} || '%'
</if>
</trim>
</select>
これですっきり!(生成されるSQLは)

SQL文のログ出力

MyBatisが実行しているSQLをログに出力する方法。
これはiBatisと同じでよいみたいで、この辺を参考にさせていただいた。

ページング

前回書いていた通り、selectListにRowBoundsのパラメータを指定することで、検索結果のうち特定の範囲を取り出すことができた。
パフォーマンスについては結局真面目には調査できず(--;
とりあえず大した件数でなければ問題はなさそうだけど・・・?

なお、昨今のオールインワンのフレームワークみたいに便利なページング機能があるわけではないので、上記以外の部分は別途用意しないといけない模様。
自分のほうではオーソドックス(?)に SELECT COUNT(*) する検索と普通の検索を用意して、普通の検索のほうをRowBoundsで絞り込んだ。両方いっぺんに取れたらいいのに・・・。

その他(だそく)

MyBatisみたいに好き放題SQL書ける仕組みを用意すると、必ずビジネスロジックでも何でもSQLに書こうとする奴が現れるので早めに釘を指すこと。マジ要注意。気づいたら巨大SQL登場とか微妙に条件が違うコピペSQL割拠とか洒落にならないorz

パフォーマンスとかでSQLでやらないと仕方ないことはいっぱいあるけど、Javaアプリならビジネスロジックは基本Javaで・・・ですよね?


以上、また長くなったがだいたいこんなところか。使ってないor気づいてない機能はまだまだいっぱいあると思われるが、とりあえず自分のところではこれで何とか形になったっぽい。
後はキャッシュが相変わらず未調査。これもちゃんと調べて使っていかないといけないもののはずではあるのだが(汗
スポンサーサイト

Tag: Java MyBatis

0 Comments

Leave a comment

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。