概要
SQLファイルは、SQL文を格納したテキストファイルで、Daoのメソッドにマッピングされます。 SQLのブロックコメント(/* */)や行コメント(--)を使用することで、バインド変数や動的なSQLのための条件分岐を表現できます。 SQLのツールでそのままそのSQLを実行できるように、バインド変数にはテスト用のデータを指定します。テスト用のデータは、実行時には使用されません。 たとえば、SQLファイルには次のようなSQL文が格納されます。
select * from employee where employee_id = /* employeeId */99
ここでは、ブロックコメントで囲まれた employeeId
がDaoインタフェースのメソッドのパラメータに対応し、
直後の 99
はテスト用の条件になります。
対応するDaoインタフェースのメソッドは次のとおりです。
Employee selectById(employeeId);
SQLファイル
ファイル名の形式
ファイル名は、次の形式でなければいけません。
META-INF/Daoのクラスの完全修飾名をディレクトリに変換したもの/Daoのメソッド名.sql
例えば、 Daoのクラスが aaa.bbb.EmployeeDao
で
マッピングしたいメソッドが selectById
の場合、パス名は次のようになります。
META-INF/aaa/bbb/EmployeeDao/selectById.sql
複数のRDBMSに対応する必要があり特定のRDBMSでは別のSQLファイルを使いたい場合、
.sql
の前にハイフン区切りでRDBMS名を入れることで、優先的に使用するファイルを指示できます。
たとえば、PostgreSQL専用のSQLファイルは次の名前にします。
META-INF/aaa/bbb/EmployeeDao/selectById-postgres.sql
この場合、PostgreSQLを使用している場合に限り、META-INF/aaa/bbb/EmployeeDao/selectById.sql
よりも
META-INF/aaa/bbb/EmployeeDao/selectById-postgres.sql
が優先的に使用されます。
RDBMS名は、 org.seasar.doma.jdbc.dialect.Dialect
の getName
メソッドの値が使用されます。
あらかじめ用意されたDialectについてそれぞれのRDBMS名を以下の表に示します。
データベース | 方言クラスの名前 | RDBMS名 |
---|---|---|
DB2 | org.seasar.doma.jdbc.dialect.Db2Dialect |
db2 |
H2 Database Engine 1.2.126 | org.seasar.doma.jdbc.dialect.H212126Dialect |
h2 |
H2 Database Engine | org.seasar.doma.jdbc.dialect.H2Dialect |
h2 |
HSQLDB | org.seasar.doma.jdbc.dialect.HsqldbDialect |
hsqldb |
Microsoft SQL Server 2008 | org.seasar.doma.jdbc.dialect.Mssql2008Dialect |
mssql2008 |
Microsoft SQL Server | org.seasar.doma.jdbc.dialect.MssqlDialect |
mssql |
MySQL | org.seasar.doma.jdbc.dialect.MySqlDialect |
mysql |
Oracle Database | org.seasar.doma.jdbc.dialect.OracleDialect |
oracle |
PostgreSQL | org.seasar.doma.jdbc.dialect.PostgresDialect |
postgres |
SQLite | org.seasar.doma.jdbc.dialect.SqliteDialect |
sqlite |
SQLコメント
Domaでは、SQLコメント中に式を記述することで、値のバインディングや条件分岐を行います。 Domaに解釈されるSQLコメントを式コメントと呼びます。
バインド変数コメント
バインド変数を示す式コメントをバインド変数コメントと呼びます。
バインド変数は、java.sql.PreparedStatement
を介してSQLに設定されます。
バインド変数は/*~*/というブロックコメントで囲んで示します。 バインド変数の名前は、Daoメソッドのパラメータ名に対応します。 対応するパラメータの型は、基本型もしくはドメインクラスでなければいけません。 バインド変数コメントの直後にはテスト用データを指定する必要があります。 テスト用データは、実行時には使用されません。
基本型もしくはドメインクラス型のパラメータ
Daoインタフェースのメソッドのパラメータが基本型もしくはドメインクラスの場合、 このパラメータは、1つのバインド変数を表現できます。 バインド変数コメントはバインド変数を埋め込みたい場所に記述し、バインド変数コメントの直後にはテスト用データを指定しなければいけません。 Daoインタフェースのメソッドと、対応するSQLの例は次のとおりです。
Employee selectById(Integer employeeId);
select * from employee where employee_id = /* employeeId */99
Iterable
型のパラメータ
Daoインタフェースのメソッドのパラメータがjava.lang.Iterable
のサブタイプの場合、
このパラメータは、IN句内の複数のバインド変数を表現できます。
ただし、java.lang.Iterable
のサブタイプの実型引数は基本型もしくはドメインクラスでなければいけません。
バインド変数コメントはINキーワードの直後に置き、バインド変数コメントの直後には括弧つきでテスト用データを指定しなければいけません。
Daoインタフェースのメソッドと、対応するSQLの例は次のとおりです。
List<Employee> selectByIdList(List<Integer> employeeIdList);
select * from employee where employee_id in /* employeeIdList */(1,2,3)
任意の型のパラメータ
Daoインタフェースのメソッドのパラメータが基本型もしくはドメインクラスでない場合、 パラメータは、複数のバインド変数コメントに対応します。 バインド変数コメントの中では、ドット(.)を使用し任意の型のフィールドやメソッドにアクセスできます。 Daoインタフェースのメソッドと、対応するSQLの例は次のとおりです。 EmployeeDtoクラスには、employeeNameフィールドやsalaryフィールドが存在するものとします。
List<Employee> selectByNameAndSalary(EmployeeDto dto);
select * from employee where employee_name = /* dto.employeeName */'abc' and salary = /* dto.salary */1234
フィールドにアクセスする代わりに、publicなメソッドを呼び出すことも可能です。
select * from employee where salary = /* dto.getTaxedSalary() */1234
埋め込み変数コメント
埋め込み変数を示す式コメントを埋め込み変数コメントと呼びます。 埋め込み変数の値は、SQLを組み立てる際にSQLの一部として直接埋め込まれます。 SQLインジェクションを防ぐため、埋め込み変数の値にシングルクォテーション、セミコロン、行コメント、ブロックコメントは含めることを禁止しています。
埋め込み変数は/*#~*/というブロックコメントで示します。埋め込み変数の名前はDaoメソッドのパラメータ名にマッピングされます。 埋め込み変数はORDER BY句など、SQLの一部をプログラムで組み立てたい場合に使用できます。 Daoのメソッドと、対応するSQLの例は次のとおりです。
List<Employee> selectAll(BigDecimal salary, String orderyBy);
select * from employee where salary > /* salary */100 /*# orderBy */
Daoの呼び出し例は次の通りです。
EmployeeDao dao = new EmployeeDaoImpl(); BigDecimal salary = new BigDecimal(1000); String orderBy = "order by salary asc, employee_name"; List<Employee> list = dao.selectAll(salary, orderBy);
発行されるSQLは次のようになります。
select * from employee where salary > ? order by salary asc, employee_name
条件コメント
ifとend
条件分岐を示す式コメントを条件コメントと呼びます。 構文は、次のとおりです。
/*%if 条件式*/ ~ /*%end*/
select * from employee where /*%if employeeId != null */ employee_id = /* employeeId */99 /*%end*/
上記のSQL文は、 employeeId
が null
でない場合 次のような準備された文に変換されます。
select * from employee where employee_id = ?
このSQL文は、 employeeId
がnull
の場合に次のような準備された文に変換されます。
select * from employee
ifの条件が成り立たない場合にifの外にあるWHERE句が出力されないのは、WHERE
や HAVING
の自動除去機能が働いているためです。
条件コメントにおけるWHEREやHAVINGの自動除去
条件コメントを使用した場合、条件の前にあるWHEREやHAVINGについて、自動で出力の要/不要を判定します。
たとえば、次のようなSQLでemployeeId
が null
の場合、
select * from employee where /*%if employeeId != null */ employee_id = /* employeeId */99 /*%end*/
/*%if ~*/
の前の where
は自動で除去され、次のSQLが生成されます。
select * from employee
条件コメントにおけるANDやORの自動除去
条件コメントを使用した場合、条件の後ろにつづくANDやORについて、自動で出力の要/不要を判定します。
たとえば、次のようなSQLでemployeeId
が null
の場合、
select * from employee where /*%if employeeId != null */ employee_id = /* employeeId */99 /*%end*/ and employeeName like 's%'
/*%end*/
の後ろの and
は自動で除去され、次のSQLが生成されます。
select * from employee where employeeName like 's%'
elseifとelse
/*%if 条件式*/
と /*%end*/
の間では、
elseifやelseを表す次の構文も使用できます。
/*%elseif 条件式*/
/*%else*/
例を示します。
select * from employee where /*%if employeeId != null */ employee_id = /* employeeId */9999 /*%elseif department_id != null */ and department_id = /* departmentId */99 /*%else*/ and department_id is null /*%end*/
上のSQLは、employeeId != null が成立するとき実際は次のSQLに変換されます。
select * from employee where employee_id = ?
employeeId == null && department_id != null が成立するとき、実際は次のSQLに変換されます。 department_idの直前のANDは自動で除去されるため出力されません。
select * from employee where department_id = ?
employeeId == null && department_id == null が成立するとき、実際は次のSQLに変換されます。 department_idの直前のANDは自動で除去されるため出力されません。
select * from employee where department_id is null
過去との互換性のため、/*%if 条件式*/
と /*%end*/
の間では、
行コメントを使用した次の構文も使用できます。
特に理由がない限り、ブロックコメントの /*%elseif 条件式*/ や /*%else*/ を使用してください。
--elseif 条件式--
--else
elseifやelseを行コメントで表した場合の例を示します。
select * from employee where /*%if employeeId != null */ employee_id = /* employeeId */9999 --elseif department_id != null -- department_id = /* departmentId */99 --else department_id is null /*%end */
ネストした条件コメント
条件コメントはネストさせることができます。
select * from employee where /*%if employeeId != null */ employee_id = /* employeeId */99 /*%if employeeName != null */ and employee_name = /* employeeName */'hoge' /*%else*/ and employee_name is null /*%end*/ /*%end*/
条件コメントにおける制約
条件コメントのifとendはSQLの同じ節に含まれなければいけません。 節とは、SELECT節、FROM節、WHERE節、GROUP BY節、HAVING節、ORDER BY節などです。 次の例では、ifがFROM節にありendがWHERE節にあるため不正です。
select * from employee /*%if employeeId != null */ where employee_id = /* employeeId */99 /*%end*/
また、ifとendは同じレベルの文に含まれなければいけません。 次の例では、ifが括弧の外にありendが括弧の内側にあるので不正です。
select * from employee where employee_id in /*%if departmentId != null */(... /*%end*/ ...)
繰り返しコメント
forとend
繰り返しを示す式コメントを繰り返しコメントと呼びます。 構文は、次のとおりです。
/*%for 識別子 : 式*/ ~ /*%end*/
例を示します。
select * from employee where /*%for name : names */ employee_name like /* name */'hoge' /*%if name_has_next */ /*# "or" */ /*%end */ /*%end*/
上記のSQL文は、names
が3つの要素からなるリストを表す場合、次のような準備された文に変換されます。
select * from employee where employee_name like ? or employee_name like ? or employee_name like ?
item_has_nextとitem_index
/*%for 識別子 : 式*/
から /*%end*/
までの内側では、次の2つの特別な変数を使用できます。
- item_has_next
- item_index
itemは識別子です。つまり、forの識別子が「name」の場合、この変数はそれぞれ「name_has_next」と「name_index」となります。
item_has_nextは、次の繰り返し要素が存在するかどうかを示すbooleanの値です。
item_indexは、繰り返しのindexを表すintの値です。値は0始まりです。
繰り返しコメントにおけるWHEREやHAVINGの自動除去
繰り返しコメントを使用した場合、コメントの前にあるWHEREやHAVINGについて、自動で出力の要/不要を判定します。
たとえば、次のようなSQLでnames
のsizeが0の場合(繰り返しが行われない場合)、
select * from employee where /*%for name : names */ employee_name like /* name */'hoge' /*%if name_has_next */ /*# "or" */ /*%end */ /*%end*/
/*%for ~*/
の前の where
は自動で除去され、次のSQLが生成されます。
select * from employee
繰り返しコメントにおけるANDやORの自動除去
繰り返しコメントを使用した場合、コメントの後ろにつづくANDやORについて、自動で出力の要/不要を判定します。
たとえば、次のようなSQLでnames
のsizeが0の場合(繰り返しが行われない場合)、
select * from employee where /*%for name : names */ employee_name like /* name */'hoge' /*%if name_has_next */ /*# "or" */ /*%end */ /*%end*/ or salary > 1000
/*%end*/
の後ろの or
は自動で除去され、次のSQLが生成されます。
select * from employee where salary > 1000
繰り返しコメントにおける制約
繰り返しコメントのforとendはSQLの同じ節に含まれなければいけません。 節とは、SELECT節、FROM節、WHERE節、GROUP BY節、HAVING節、ORDER BY節などです。
また、forとendは同じレベルの文に含まれなければいけません。 つまり、括弧の外でfor、括弧の内側でendという記述は認められません。
通常のブロックコメント
/*
の直後に続く3文字目が、Javaの識別子の先頭で使用できない文字(ただし、空白と式で特別な意味をもつ「%
」、「#
」、「@
」、「"
」、「'
」は除く)の場合、それは通常のブロックコメントだとみなされます。
たとえば、次の例はすべて通常のブロックコメントとみなされます。
/**~*/ /*+~*/ /*=~*/ /*:~*/ /*;~*/ /*(~*/ /*)~*/ /*&~*/
一方、次の例はすべて式コメントだとみなされます。
/* ~*/ ...--3文字目が空白であるため式コメントです。 /*a~*/ ...--3文字目がJavaの識別子の先頭で使用可能な文字であるため式コメントです。 /*$~*/ ...--3文字目がJavaの識別子の先頭で使用可能な文字であるため式コメントです。 /*%~*/ ...--3文字目が条件コメントや繰り返しコメントの始まりを表す「%
」であるため式コメントです。 /*#~*/ ...--3文字目が埋め込み変数コメントを表す「#
」であるため式コメントです。 /*@~*/ ...--3文字目が組み込み関数もしくはクラス名を表す「@
」であるため式コメントです。 /*"~*/ ...--3文字目が文字列リテラルの引用符を表す「"
」であるため式コメントです。 /*'~*/ ...--3文字目が文字リテラルの引用符を表す「'
」であるため式コメントです。
特に理由がない場合、通常のブロックコメントには/**~*/
を使用するのがよいでしょう。
式言語
式コメントには式を記述できます。 文法は、Javaとほとんど同じです。 ただし、Javaで可能なことすべてができるわけではありません。
リテラル
以下のリテラルが用意されています。
リテラル | 型 |
---|---|
null |
void |
true |
boolean |
false |
boolean |
10 |
int |
10L |
long |
0.123F |
float |
0.123D |
double |
0.123B |
java.math.BigDecimal |
'a' |
char |
"a" |
String |
数値の型は、リテラルの最後に「L」や「F」などを付与して区別します。 「L」や「F」などは大文字でなければいけません。
select * from employee where /*%if employeeName != null && employeeName.length() > 10 */ employee_name = /* employeeName */'smith' /*%end*/
比較演算子
以下の比較演算子を使用できます。
演算子 | 説明 |
---|---|
== |
等値演算子 |
!= |
不等演算子 |
< |
小なり演算子 |
<= |
小なりイコール演算子 |
> |
大なり演算子 |
>= |
大なりイコール演算子 |
比較演算子を利用するには、 被演算子が java.lang.Comparable
を実装している必要があります。
<
、<=
、>
、>=
では、
被演算子がnull
であってはいけません。
select * from employee where /*%if employeeName.indexOf("s") > -1 */ employee_name = /* employeeName */'smith' /*%end*/
論理演算子
以下の論理演算子を使用できます。
演算子 | 説明 |
---|---|
! |
論理否定演算子 |
&& |
論理積演算子 |
|| |
論理和演算子 |
括弧を使って、演算子が適用される優先度を制御できます。
select * from employee where /*%if (departmentId == null || managerId == null) and employee_name != null */ employee_name = /* employeeName */'smith' /*%end*/
算術演算子
以下の算術演算子を使用できます。
演算子 | 説明 |
---|---|
+ |
加算演算子 |
- |
減算演算子 |
* |
乗算演算子 |
/ |
除算演算子 |
% |
剰余演算子 |
被演算子は数値型でなければいけません。
select * from employee where salary = /* salary + 1000 */0
その他の演算子
連結演算子(+
)を使って文字を連結できます。
被演算子は次のいずれかの型でなければいけません。
- java.lang.String
- java.lang.Character
- char
select * from employee where employee_name like /* employeeName + "_" */'smith'
インスタンスメソッドの呼び出し
ドット(.)で区切ってメソッド名を指定することでインスタンスメソッドを実行可能です。
実行可能なメソッドは、可視性がpublic
なものだけに限られます。
select * from employee where /*%if employeeName.startsWith("s") */ employee_name = /* employeeName */'smith' /*%end*/
引数がない場合は、メソッド名の後ろに()を指定します。
select * from employee where /*%if employeeName.length() > 10 */ employee_name = /* employeeName */'smith' /*%end*/
インスタンスフィールドへのアクセス
ドット(.)で区切ってフィールド名を指定することでインスタンスフィールドにアクセスできます。 可視性はprivateであってもアクセス可能です。
select * from employee where employee_name = /* employee.employeeName */'smith'
staticメソッドの呼び出し
@
で囲まれたクラスの完全修飾名にメソッドを続けることでstaticメソッドを実行可能です。
実行可能なメソッドは、可視性がpublic
なものだけに限られます。
select * from employee where /*%if @java.util.regex.Pattern@matches("^[a-z]*$", employeeName) */ employee_name = /* employeeName */'smith' /*%end*/
staticフィールドへのアクセス
@
で囲まれたクラスの完全修飾名にフィールドを続けることでstaticフィールドにアクセスできます。
可視性はprivateであってもアクセス可能です。
select * from employee where /*%if employeeName.length() < @java.lang.Byte@MAX_VALUE */ employee_name = /* employeeName */'smith' /*%end*/
組み込み関数の使用
組み込み関数は、主に、SQLにバインドする前にバインド変数の値を変更するためのユーティリティです。
たとえば、likeで前方一致検索を行う場合に、次のように記述できます。
select * from employee where employee_name like /* @prefix(employee.employeeName) */'smith' escape '$'
ここでは、@prefix(employee.employeeName)
というように、
employee.employeeName
を @prefix
関数に渡しています。
@prefix
関数は、パラメータで受け取る文字列を前方一致検索用の文字列に変換します。また、特別な意味を持つ文字をエスケープします。
この例では、employee.employeeName
の値が「ABC」である場合、
値は「ABC%」に変換されます。
もし、employee.employeeName
の値が「AB%C」というように「%」を含んでいる場合、
「%」はデフォルトのエスケープシーケンス($)でエスケープされ、値は「AB$%C%」に変換されます。
使用可能な関数は以下のとおりです。
戻り値の型 | 関数名とパラメータ | 概要 |
---|---|---|
String |
@escape(String text) |
LIKE演算のためのエスケープを行うことを示します。戻り値は入力値をエスケープした文字列です。エスケープにはデフォルトのエスケープ文字($)を用いて行われます。引数にnull を渡した場合、null を返します。 |
String |
@escape(String text, char escapeChar) |
LIKE演算のためのエスケープを行うことを示します。戻り値は入力値をエスケープした文字列です。エスケープは第2引数で指定したエスケープ文字を用いて行われます。最初の引数にnull を渡した場合、null を返します。 |
String |
@prefix(String prefix) |
前方一致検索を行うことを示します。戻り値は入力値をエスケープしワイルドカードを後ろに付与した文字列です。エスケープにはデフォルトのエスケープ文字($)を用いて行われます。引数にnull を渡した場合、null を返します。 |
String |
@prefix(String prefix, char escapeChar) |
前方一致検索を行うことを示します。戻り値は入力値をエスケープしワイルドカードを後ろに付与した文字列です。エスケープは第2引数で指定したエスケープ文字を用いて行われます。最初の引数にnull を渡した場合、null を返します。 |
String |
@suffix(String suffix) |
後方一致検索を行うことを示します。戻り値は入力値をエスケープしワイルドカードを前に付与した文字列です。エスケープはデフォルトのエスケープ文字($)を用いて行われます。引数にnull を渡した場合、null を返します。 |
String |
@suffix(String suffix, char escapeChar) |
後方一致検索を行うことを示します。戻り値は入力値をエスケープしワイルドカードを前に付与した文字列です。エスケープは第2引数で指定したエスケープ文字を用いて行われます。最初の引数にnull を渡した場合、null を返します。 |
String |
@infix(String infix) |
中間一致検索を行うことを示します。戻り値は入力値をエスケープしワイルドカードを前と後ろに付与した文字列です。エスケープはデフォルトのエスケープ文字($)を用いて行われます。引数にnull を渡した場合、null を返します。 |
String |
@infix(String infix, char escapeChar) |
中間一致検索を行うことを示します。戻り値は入力値をエスケープしワイルドカードを前と後ろに付与した文字列です。エスケープは第2引数で指定したエスケープ文字を用いて行われます。最初の引数にnull を渡した場合、null を返します。 |
String |
@contain(String inside) |
@infix(String infix) の別名です。非推奨です。 |
String |
@contain(String inside, char escapeChar) |
@infix(String infix, char escapeChar) の別名です。非推奨です。 |
java.util.Date |
@roundDownTimePart(java.util.Date date) |
時刻部分を切り捨てることを示します。戻り値は時刻部分が切り捨てられた新しい日付です。引数にnull を渡した場合、null を返します。 |
java.sql.Date |
@roundDownTimePart(java.sql.Date date) |
時刻部分を切り捨てることを示します。戻り値は時刻部分が切り捨てられた新しい日付です。引数にnull を渡した場合、null を返します。 |
java.sql.Timestamp |
@roundDownTimePart(java.sql.Timestamp timestamp) |
時刻部分を切り捨てることを示します。戻り値は時刻部分が切り捨てられた新しいタイムスタンプです。引数にnull を渡した場合、null を返します。 |
java.util.Date |
@roundUpTimePart(java.util.Date date) |
時刻部分を切り上げることを示します。戻り値は時刻部分が切り上げられた新しい日付です。引数にnull を渡した場合、null を返します。 |
java.sql.Date |
@roundUpTimePart(java.sql.Date date) |
時刻部分を切り上げることを示します。戻り値は時刻部分が切り上げられた新しい日付です。引数にnull を渡した場合、null を返します。 |
java.sql.Timestamp |
@roundUpTimePart(java.sql.Timestamp timestamp) |
時刻部分を切り上げることを示します。戻り値は時刻部分が切り上げられた新しいタイムスタンプです。引数にnull を渡した場合、null を返します。 |
boolean |
@isEmpty(CharSequence charSequence) |
文字シーケンスがnull 、もしくは文字シーケンスの長さが0 の場合 true を返します。 |
boolean |
@isNotEmpty(CharSequence charSequence) |
文字シーケンスがnull でない、かつ文字シーケンスの長さが0 でない場合 true を返します。 |
boolean |
@isBlank(CharSequence charSequence) |
文字シーケンスがnull 、もしくは文字シーケンスの長さが0 、もしくは文字シーケンスが空白だけから形成される場合 true を返します。 |
boolean |
@isNotBlank(CharSequence charSequence) |
文字シーケンスがnull でない、かつ文字シーケンスの長さが0 でない、かつ文字シーケンスが空白だけで形成されない場合 true を返します。 |
これらの関数は、org.seasar.doma.expr.ExpressionFunctions
のメソッドに対応しています。
カスタム関数の使用
関数を独自に定義し使用できます。
独自に定義した関数(カスタム関数)を使用するには次の設定が必要です。
-
関数は、
org.seasar.doma.expr.ExpressionFunctions
を実装したクラスのメソッドとして定義する。 メソッドは、publicなインスタンスメソッドでなければいけない。 - 作成したクラスは、注釈処理のオプションで登録する。 オプションのキーはexpr.functionsである。
-
作成したクラスのインスタンスを設定クラスのRDBMSの方言で使用する
(Domaが提供するRDBMSの方言の実装はコンストラクタで
org.seasar.doma.expr.ExpressionFunctions
を受け取ることが可能)。
カスタム関数を呼び出すには、組み込み関数と同じように関数名の先頭に@をつけます。 たとえば、myfuncという関数の呼び出しは、次のように記述できます。
select * from employee where employee_name = /* @myfunc(employee.employeeName) */'smith'