WHERE句が可変な動的SQL
動的なSQLの組み立てができる点は、iBATISの大きな利点です。外部ファイルにSQLを定義する仕組みでは、動的SQLに対応できないと困ったことになります。
例えば次のような、検索処理を考えて見ます。
- 社員名が指定されたときは、
WHERE ENAME LIKE ~
の検索条件を追加 - 部署番号が指定されたときは、
WHERE DEPTNO = ~
の検索条件を追加 - 両方の条件が指定されたときは、AND条件とする
- 何も指定されないときは、WHERE句は使用しない
検索画面などでありがちな処理です。検索条件が指定されることでWHERE句が変化します。
SQL文字列の単純な連結となりますが、"WHERE"
という文字列で連結したり、"AND"
という文字列で連結したりと、なかなか厄介なプログラミングになります。
検索条件クラスSearchEmpParam
検索条件を指定するためのオブジェクトです。社員名と部署番号を検索条件に指定するために作成しました。
このオブジェクトに検索条件を格納し、iBATISに渡してSQLを実行することになります。
public class SearchEmpParam { private String ename; private int deptno; public String getEname() { return ename; } public void setEname(String ename) { this.ename = ename; } public int getDeptno() { return deptno; } public void setDeptno(int deptno) { this.deptno = deptno; } }
SqlMap
作成済みの「SqlMap-Emp.xml」にSQL定義を追加します。
<!-- 動的SQLの例 条件指定されときのみWHERE句が付加される --> <select id="searchEmp" parameterClass="examples.dto.SearchEmpParam" resultMap="rm1"> SELECT * FROM EMP <dynamic prepend="WHERE"> <isNotNull prepend="AND" property="ename"> ENAME like #ename# <!-- ename条件がnullでないとき --> </isNotNull> <isNotEqual compareValue="0" prepend="AND" property="deptno"> DEPTNO = #deptno# <!-- deptno条件が0でないとき --> </isNotEqual> </dynamic> </select>
ちょっと複雑になってきましたが、よく見るとシンプルです。
このSQL定義は、固定部分と動的に決定する部分に分かれています。<dynamic>
で定義される部分は動的に決定する部分です。parameterClass
で指定されたオブジェクトによって、動的にSQLが構築されます。
ename
プロパティがNotNullの場合(つまり検索条件enameが指定されたとき)に、ENAME like #ename#
というSQL文字列が連結されます。
deptno
プロパティが0以外の場合(つまり検索条件deptnoが指定されとき)に、DEPTNO = #deptno#
というSQL文字列が連結されます。
iBATISが1つ目か2つ目以降かを判断して、"WHERE"か"AND"の文字列を使い、連結してくれます。
実行プログラム
3通りのSQLが作成される実行例です。動的なSQLを実行するわけですが、プログラムの記述は非常にシンプルです。
System.out.println("動的SQLの指定 条件の有無"); System.out.println("条件指定なし"); SearchEmpParam paramAll = new SearchEmpParam(); List<Emp> listAll = (List<Emp>)sqlMap.queryForList("searchEmp", paramAll); for (Emp e : listAll){ System.out.println(e); } System.out.println("ENAME条件のみ指定 like '%WA%'"); SearchEmpParam paramEname = new SearchEmpParam(); paramEname.setEname("%WA%"); //ENAME条件を指定 List<Emp> listEname = (List<Emp>)sqlMap.queryForList("searchEmp", paramEname); for (Emp e : listEname){ System.out.println(e); } System.out.println("両方指定 dept=20, like '%WA%'"); SearchEmpParam paramBoth = new SearchEmpParam(); paramBoth.setDeptno(20); //Deptno条件を指定 paramBoth.setEname("%WA%"); //Ename条件を指定 List<Emp> listBoth = (List<Emp>)sqlMap.queryForList("searchEmp", paramBoth); for (Emp e : listBoth){ System.out.println(e); }
SearchEmpParam
に格納されている値によって、WHERE句が付加されたりされなかったりします。3回のqueryForList
が実行されていますが、動的に作成されるSQLはそれぞれ次のようになります。
SELECT * FROM EMP
SELECT * FROM EMP WHERE ENAME like '%WA'
SELECT * FROM EMP WHERE ENAME like '%WA' AND DEPTNO = 20
繰り返し構造をもつ動的SQL
指定された値によって、SQLの組み立てが繰り返し構造になることがよくあります。
サンプルは、WHERE句のINで指定する条件が動的に変化する例です。INで指定するパラメータが0個からn個に変化します。任意の数の社員番号を指定可能で、指定した社員番号がINで列挙指定されます。
検索条件クラスSearchEmpParam
検索条件クラスは、複数の社員番号が指定できるようにListで社員番号を複数保持できるようにします。
public class SearchEmpParam { private List<Integer> empnoList = new ArrayList<Integer>(); public List<Integer> getEmpnoList() { return empnoList; } }
SqlMap
作成済みの「SqlMap-Emp.xml」にSQL定義を追加します。
<!-- 動的SQLの例 INで指定する値が可変 --> <select id="searchEmpIn" parameterClass="examples.dto.SearchEmpParam" resultMap="rm1"> SELECT * FROM EMP <iterate property="empnoList" prepend="WHERE EMPNO IN" open="(" close=")" conjunction="," > #empnoList[]# </iterate> </select>
複数の社員番号をListから列挙するために、iterate
タグを指定します。この部分はempnoList
プロパティの要素数によって繰り返し出力されます。
prepend
属性は、要素が1個以上存在する際に付加されます。
open
属性、およびclose
属性は、繰り返しブロックの先頭と末尾に付加されます(開き括弧、閉じ括弧に使います)。
conjuction
属性は、各繰り返しの連結文字列として使用されます。SQLのIN演算子は","
(カンマ)で区切って記述するので上記のようになります。
プロパティの指定では、empnoList[]
のように、プロパティ名の最後に[ ]が付くことに注意してください。
実行プログラム
検索条件クラスSearchEmpParam
のempnoList
プロパティに、複数の社員番号を追加指定します。
System.out.println("動的SQLの指定"); SearchEmpParam paramIn = new SearchEmpParam(); paramIn.getEmpnoList().add(new Integer(7839)); paramIn.getEmpnoList().add(new Integer(7566)); paramIn.getEmpnoList().add(new Integer(7788)); paramIn.getEmpnoList().add(new Integer(7934)); List<Emp> list = (List<Emp>)sqlMap.queryForList("searchEmpIn", paramIn); for (Emp e : list){ System.out.println(e); }
SQLのIN演算子の部分に展開されて実行されます。
SQL文字列の埋め込みをする動的SQL
最も柔軟な動的SQLは、SQL文の一部を文字列で指定する方法です。
ORDER BY句のソート条件を指定する場合などは文を組み立てるよりも、文字列でORDER BY句そのものを直接指定できた方が楽だったりします。
iBATISでは、SQL文に文字を埋め込むことが可能です。これは埋め込みパラメータとは違います。あくまでSQL文として埋め込まれるものです。
SqlMap
作成済みの「SqlMap-Emp.xml」にSQL定義を追加します。
<!-- 動的SQLの例 文字列でSQL文そのものを指定 --> <select id="searchEmpOrder" resultMap="rm1"> SELECT * FROM EMP ORDER BY $value$ </select>
$value$
が、後から埋め込まれるSQL文字列です。#value#
のような埋め込みパラメータの記述と違う点に注意してください。
この例では唯一の文字列をパラメータとするため、$value$
になりますが、parameterClassを指定する場合は、$fooProperty$
、$barProperty$
のように、複数の埋め込み文字列を指定することもできます(もちろんMapでの指定も可能です)。
実行プログラム
System.out.println("動的SQLの指定 SQL文を指定 ORDER BY EMPNO DESC"); List<Emp> list = (List<Emp>)sqlMap.queryForList("searchEmpOrder", "EMPNO DESC"); for (Emp e : list){ System.out.println(e); }
queryForList
を実行する際に、SQLに埋め込む文字列を指定します。この例では次のように展開されます。
SELECT * FROM EMP ORDER BY $value$
↓
SELECT * FROM EMP ORDER BY EMPNO DESC
この埋め込み方法は、複雑な動的SQLを作成するために使用すべきです。
データの埋め込みには、#property#
形式で、値の埋め込みパラメータを使用するのが適切です。
まとめ
resultMap
要素を使用することで検索結果とBeanのマッピングができるdynamic
要素を使用することで動的SQLが定義できるiterate
要素を使用することで繰り返し構造の動的SQLが定義できる- 実行時にSQL文字列を埋め込むことができる
動的SQLが定義できることで、ソースコードからSQL文が完全に分離できます。ソースコードはSQL定義を参照するだけになり非常にシンプルになります。高機能なO/Rマッピングフレームワークは、SQLを隠蔽してしまうものもありますが、iBATISは、あえてSQLに特化した機能を強化しています。
強力なSQLの機能を最大限に利用できるフレームワークとして、iBATISは最良の選択肢かと思います。