SQLファイル

SQLファイルは、SQL文を格納したテキストファイルで、Daoのメソッドにマッピングされます。 SQLのブロックコメント(/* */)や行コメント(--)を使用することで、バインド変数や動的なSQLのための条件分岐を表現できます。 SQLのツールでそのままそのSQLを実行できるように、バインド変数にはテスト用のデータを指定します。テスト用のデータは、実行時には使用されません。 たとえば、SQLファイルには次のようなSQL文が格納されます。

ここでは、ブロックコメントで囲まれた employeeIdがDaoインタフェースのメソッドのパラメータに対応し、 直後の 99はテスト用の条件になります。 対応するDaoインタフェースのメソッドは次のとおりです。

SQLファイルとDaoのメソッドのマッピングは次のアノテーションで示します。

SQLファイルはクラスパスが通った META-INF ディレクトリ以下に配置しなければいけません。

ファイル名は、次の形式でなければいけません。

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.DialectgetNameメソッドの値が使用されます。 あらかじめ用意された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ファイルのエンコーディングはUTF-8でなければいけません。

Domaでは、SQLコメント中に式を記述することで、値のバインディングや条件分岐を行います。 Domaに解釈されるSQLコメントを式コメントと呼びます。

バインド変数を示す式コメントをバインド変数コメントと呼びます。 バインド変数は、java.sql.PreparedStatementを介してSQLに設定されます。

バインド変数は/*~*/というブロックコメントで囲んで示します。 バインド変数の名前は、Daoメソッドのパラメータ名に対応します。 対応するパラメータの型は、基本型もしくはドメインクラスでなければいけません。 バインド変数コメントの直後にはテスト用データを指定する必要があります。 テスト用データは、実行時には使用されません。

基本型もしくはドメインクラス型のパラメータ

Daoインタフェースのメソッドのパラメータが基本型もしくはドメインクラスの場合、 このパラメータは、1つのバインド変数を表現できます。 バインド変数コメントはバインド変数を埋め込みたい場所に記述し、バインド変数コメントの直後にはテスト用データを指定しなければいけません。 Daoインタフェースのメソッドと、対応するSQLの例は次のとおりです。

Iterable型のパラメータ

Daoインタフェースのメソッドのパラメータがjava.lang.Iterableのサブタイプの場合、 このパラメータは、IN句内の複数のバインド変数を表現できます。 ただし、java.lang.Iterableのサブタイプの実型引数は基本型もしくはドメインクラスでなければいけません。 バインド変数コメントはINキーワードの直後に置き、バインド変数コメントの直後には括弧つきでテスト用データを指定しなければいけません。 Daoインタフェースのメソッドと、対応するSQLの例は次のとおりです。

selectByIdList(List employeeIdList);]]>

任意の型のパラメータ

Daoインタフェースのメソッドのパラメータが基本型もしくはドメインクラスでない場合、 パラメータは、複数のバインド変数コメントに対応します。 バインド変数コメントの中では、ドット(.)を使用し任意の型のフィールドやメソッドにアクセスできます。 Daoインタフェースのメソッドと、対応するSQLの例は次のとおりです。 EmployeeDtoクラスには、employeeNameフィールドやsalaryフィールドが存在するものとします。

selectByNameAndSalary(EmployeeDto dto);]]>

フィールドにアクセスする代わりに、publicなメソッドを呼び出すことも可能です。

埋め込み変数を示す式コメントを埋め込み変数コメントと呼びます。 埋め込み変数の値は、SQLを組み立てる際にSQLの一部として直接埋め込まれます。 SQLインジェクションを防ぐため、埋め込み変数の値にシングルクォテーション、セミコロン、行コメント、ブロックコメントは含めることを禁止しています。

埋め込み変数は/*#~*/というブロックコメントで示します。埋め込み変数の名前はDaoメソッドのパラメータ名にマッピングされます。 埋め込み変数はORDER BY句など、SQLの一部をプログラムで組み立てたい場合に使用できます。 Daoのメソッドと、対応するSQLの例は次のとおりです。

selectAll(BigDecimal salary, String orderyBy);]]> /* salary */100 /*# orderBy */]]>

Daoの呼び出し例は次の通りです。

list = dao.selectAll(salary, orderBy);]]>

発行されるSQLは次のようになります。

? order by salary asc, employee_name]]>

ifとend

条件分岐を示す式コメントを条件コメントと呼びます。 構文は、次のとおりです。 条件式は、結果がbooleanもしくはjava.lang.Boolean型と評価される式でなければいけません。 例を示します。

上記のSQL文は、 employeeIdnullでない場合 次のような準備された文に変換されます。

このSQL文は、 employeeIdnullの場合に次のような準備された文に変換されます。

ifの条件が成り立たない場合にifの外にあるWHERE句が出力されないのは、WHEREHAVINGの自動除去機能が働いているためです。

条件コメントにおけるWHEREやHAVINGの自動除去

条件コメントを使用した場合、条件の前にあるWHEREやHAVINGについて、自動で出力の要/不要を判定します。 たとえば、次のようなSQLでemployeeIdnullの場合、

/*%if ~*/の前の whereは自動で除去され、次のSQLが生成されます。

条件コメントにおけるANDやORの自動除去

条件コメントを使用した場合、条件の後ろにつづくANDやORについて、自動で出力の要/不要を判定します。 たとえば、次のようなSQLでemployeeIdnullの場合、

/*%end*/の後ろの and は自動で除去され、次のSQLが生成されます。

select * from employee where employeeName like 's%'

elseifとelse

/*%if 条件式*//*%end*/ の間では、 elseifやelseを表す次の構文も使用できます。

例を示します。

上のSQLは、employeeId != null が成立するとき実際は次のSQLに変換されます。

employeeId == null department_id != null が成立するとき、実際は次のSQLに変換されます。 department_idの直前のANDは自動で除去されるため出力されません。

employeeId == null department_id == null が成立するとき、実際は次のSQLに変換されます。 department_idの直前のANDは自動で除去されるため出力されません。

過去との互換性のため、/*%if 条件式*//*%end*/ の間では、 行コメントを使用した次の構文も使用できます。 特に理由がない限り、ブロックコメントの /*%elseif 条件式*/ や /*%else*/ を使用してください。

elseifやelseを行コメントで表した場合の例を示します。

ネストした条件コメント

条件コメントはネストさせることができます。

条件コメントにおける制約

条件コメントのifとendはSQLの同じ節に含まれなければいけません。 節とは、SELECT節、FROM節、WHERE節、GROUP BY節、HAVING節、ORDER BY節などです。 次の例では、ifがFROM節にありendがWHERE節にあるため不正です。

また、ifとendは同じレベルの文に含まれなければいけません。 次の例では、ifが括弧の外にありendが括弧の内側にあるので不正です。

forとend

繰り返しを示す式コメントを繰り返しコメントと呼びます。 構文は、次のとおりです。 識別子は、繰り返される要素を指す変数です。 式は、java.lang.Iterable型として評価される式でなければいけません。

例を示します。

上記のSQL文は、namesが3つの要素からなるリストを表す場合、次のような準備された文に変換されます。

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の場合(繰り返しが行われない場合)、

/*%for ~*/の前の whereは自動で除去され、次のSQLが生成されます。

繰り返しコメントにおけるANDやORの自動除去

繰り返しコメントを使用した場合、コメントの後ろにつづくANDやORについて、自動で出力の要/不要を判定します。 たとえば、次のようなSQLでnamesのsizeが0の場合(繰り返しが行われない場合)、

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文字目が文字リテラルの引用符を表す「'」であるため式コメントです。

特に理由がない場合、通常のブロックコメントには/**~*/を使用するのがよいでしょう。

--の直後に、elseifelseがつづかない場合、それは通常の行コメントだとみなされます。

たとえば、次の例は通常の行コメントだとみなされます。

-- aaa ---aaa

一方、次の例はすべて式コメントだとみなされます。

--elseif ~ -- --else

特に理由がない場合、通常の行コメントは使用しないか、---を使用するのがいいでしょう。

式コメントには式を記述できます。 文法は、Javaとほとんど同じです。 ただし、Javaで可能なことすべてができるわけではありません。

以下のリテラルが用意されています。

リテラル
nullvoid
trueboolean
falseboolean
10int
10Llong
0.123Ffloat
0.123Ddouble
0.123Bjava.math.BigDecimal
'a'char
"a"String

数値の型は、リテラルの最後に「L」や「F」などを付与して区別します。 「L」や「F」などは大文字でなければいけません。

10 */ employee_name = /* employeeName */'smith' /*%end*/]]>

以下の比較演算子を使用できます。

演算子説明
==等値演算子
!=不等演算子
<小なり演算子
<=小なりイコール演算子
>大なり演算子
>=大なりイコール演算子

比較演算子を利用するには、 被演算子が java.lang.Comparableを実装している必要があります。

<<=>>=では、 被演算子がnullであってはいけません。

-1 */ employee_name = /* employeeName */'smith' /*%end*/]]>

以下の論理演算子を使用できます。

演算子説明
!論理否定演算子
&&論理積演算子
||論理和演算子

括弧を使って、演算子が適用される優先度を制御できます。

以下の算術演算子を使用できます。

演算子説明
+加算演算子
-減算演算子
*乗算演算子
/除算演算子
%剰余演算子

被演算子は数値型でなければいけません。

連結演算子(+)を使って文字を連結できます。

被演算子は次のいずれかの型でなければいけません。

  • java.lang.String
  • java.lang.Character
  • char

ドット(.)で区切ってメソッド名を指定することでインスタンスメソッドを実行可能です。 実行可能なメソッドは、可視性がpublicなものだけに限られます。

引数がない場合は、メソッド名の後ろに()を指定します。

10 */ employee_name = /* employeeName */'smith' /*%end*/]]>

ドット(.)で区切ってフィールド名を指定することでインスタンスフィールドにアクセスできます。 可視性はprivateであってもアクセス可能です。

@で囲まれたクラスの完全修飾名にメソッドを続けることでstaticメソッドを実行可能です。 実行可能なメソッドは、可視性がpublicなものだけに限られます。

@で囲まれたクラスの完全修飾名にフィールドを続けることでstaticフィールドにアクセスできます。 可視性はprivateであってもアクセス可能です。

組み込み関数は、主に、SQLにバインドする前にバインド変数の値を変更するためのユーティリティです。

たとえば、likeで前方一致検索を行う場合に、次のように記述できます。

ここでは、@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という関数の呼び出しは、次のように記述できます。