Version v2.2 of the documentation is no longer actively maintained. The site that you are currently viewing is an archived snapshot. For up-to-date documentation, see the latest version.
TEMPLATEクエリ
概要
TEMPLATEクエリはSQLテンプレートを利用して構築するクエリです。
TEMPLATEクエリはコアのモジュールには含まれないオプション機能です。 利用するにはGradleの依存関係に次のような宣言が必要です。
val komapperVersion: String by project
dependencies {
implementation("org.komapper:komapper-template:$komapperVersion")
}
Note
すべての スターター は上記の設定を含んでいます。 したがって、Starterを使う場合には上記の設定は不要です。Note
komapper-template
モジュールは内部でリフレクションを使います。
fromTemplate
検索を実施するにはfromTemplate
関数に SQLテンプレート、bind
関数にデータを渡します。
検索結果を任意の型に変換するために、select
関数にラムダ式を渡します。
val sql = "select * from ADDRESS where street = /*street*/'test'"
val query: Query<List<Address>> = QueryDsl.fromTemplate(sql)
.bind("street", "STREET 10")
.select { row: Row ->
Address(
row.getNotNull("address_id"),
row.getNotNull("street"),
row.getNotNull("version")
)
}
select
関数に渡すラムダ式に登場するRow
はjava.sql.ResultSet
やio.r2dbc.spi.Row
の薄いラッパーです。
カラムのラベル名やインデックスで値を取得する関数を持ちます。
なお、インデックスは0から始まります。
selectAsEntity
結果を任意のエンティティとして受け取りたい場合はselectAsEntity
を呼び出します。
第一引数にはエンティティのメタモデルを指定します。
SQLテンプレートのSELECT句にはエンティティの全プロパティに対応するカラムが含まれていなければいけません。
次の例では結果をAddress
エンティティとして受け取っています。
val sql = "select address_id, street, version from ADDRESS where street = /*street*/'test'"
val query: Query<List<Address>> = QueryDsl.fromTemplate(sql)
.bind("street", "STREET 10")
.selectAsEntity(a)
デフォルトではSELECTリストのカラムの順序でエンティティにマッピングしますが、
selectAsEntity
の第二引数にProjectionType.NAME
を渡すことでカラムの名前でマッピングできます。
val sql = "select street, version, address_id from ADDRESS where street = /*street*/'test'"
val query: Query<List<Address>> = QueryDsl.fromTemplate(sql)
.bind("street", "STREET 10")
.selectAsEntity(a, ProjectionType.NAME)
結果として受け取りたいエンティティクラスに@KomapperProjection
を付与している場合、
専用の拡張関数を使って以下のように簡潔に記述できます。
val sql = "select address_id, street, version from ADDRESS where street = /*street*/'test'"
val query: Query<List<Address>> = QueryDsl.fromTemplate(sql)
.bind("street", "STREET 10")
.selectAsAddress()
val sql = "select street, version, address_id from ADDRESS where street = /*street*/'test'"
val query: Query<List<Address>> = QueryDsl.fromTemplate(sql)
.bind("street", "STREET 10")
.selectAsAddress(ProjectionType.NAME)
options
クエリの挙動をカスタマイズするにはoptions
を呼び出します。
ラムダ式のパラメータはデフォルトのオプションを表します。
変更したいプロパティを指定してcopy
メソッドを呼び出してください。
val sql = "select * from ADDRESS where street = /*street*/'test'"
val query: Query<List<Address>> = QueryDsl.fromTemplate(sql)
.options {
it.copy(
fetchSize = 100,
queryTimeoutSeconds = 5
)
}
.bind("street", "STREET 10")
.select { row: Row ->
Address(
row.getNotNull("address_id"),
row.getNotNull("street"),
row.getNotNull("version")
)
}
指定可能なオプションには以下のものがあります。
- escapeSequence
- LIKE句に指定されるエスケープシーケンスです。デフォルトは
null
でDialect
の値を使うことを示します。 - fetchSize
- フェッチサイズです。デフォルトは
null
でドライバの値を使うことを示します。 - maxRows
- 最大行数です。デフォルトは
null
でドライバの値を使うことを示します。 - queryTimeoutSeconds
- クエリタイムアウトの秒数です。デフォルトは
null
でドライバの値を使うことを示します。 - suppressLogging
- SQLのログ出力を抑制するかどうかです。デフォルトは
false
です。
executionOptions の同名プロパティよりもこちらに明示的に設定した値が優先的に利用されます。
executeTemplate
更新系のDMLを実行するにはexecuteTemplate
関数に SQLテンプレート、bind
関数にデータを渡します。
クエリ実行時にキーが重複した場合、org.komapper.core.UniqueConstraintException
がスローされます。
val sql = "update ADDRESS set street = /*street*/'' where address_id = /*id*/0"
val query: Query<Long> = QueryDsl.executeTemplate(sql)
.bind("id", 15)
.bind("street", "NY street")
returning
returning
関数を使うことで、更新系のDMLを実行しかつ結果を取得できます。
returning
関数実行後は、fromTemplateで言及したselect
関数やselectAsEntity
関数が利用できます。
val sql = """
insert into address
(address_id, street, version)
values
(/*id*/0, /*street*/'', /*version*/0)
returning address_id, street, version
""".trimIndent()
val query: Query<Address> = QueryDsl.executeTemplate(sql)
.returning()
.bind("id", 16)
.bind("street", "NY street")
.bind("version", 1)
.select { row: Row ->
Address(
row.getNotNull("address_id"),
row.getNotNull("street"),
row.getNotNull("version")
)
}
.single()
Note
上述のSQLテンプレートではPostgreSQLなどでサポートされているRETURNING句を使用していますが、 更新系のDMLから結果を返すSQLはDBMSごとに異なることに注意ください。options
クエリの挙動をカスタマイズするにはoptions
を呼び出します。
ラムダ式のパラメータはデフォルトのオプションを表します。
変更したいプロパティを指定してcopy
メソッドを呼び出してください。
val sql = "update ADDRESS set street = /*street*/'' where address_id = /*id*/0"
val query: Query<Long> = QueryDsl.executeTemplate(sql)
.bind("id", 15)
.bind("street", "NY street")
.options {
it.copy(
queryTimeoutSeconds = 5
)
}
指定可能なオプションには以下のものがあります。
- escapeSequence
- LIKE句に指定されるエスケープシーケンスです。デフォルトは
null
でDialect
の値を使うことを示します。 - queryTimeoutSeconds
- クエリタイムアウトの秒数です。デフォルトは
null
でドライバの値を使うことを示します。 - suppressLogging
- SQLのログ出力を抑制するかどうかです。デフォルトは
false
です。
executionOptions の同名プロパティよりもこちらに明示的に設定した値が優先的に利用されます。
コマンド
コマンドはSQLテンプレートとパラメータを一つのまとまりとして扱うための機能です。クラスにorg.komapper.annotation.KomapperCommand
アノテーションを付与してSQLテンプレートを定義し、クラスのプロパティでパラメータを定義します。SQLの結果をどのように扱うかは特定のクラスを継承することで表現します。
以下は、複数件を取得するコマンドの例です。
@KomapperCommand("""
select * from ADDRESS where street = /*street*/'test'
""")
class ListAddresses(val street: String): Many<Address>({ selectAsAddress() })
コマンドを定義しビルドを実行すると、QueryDsl
にexecute
という名前の拡張関数が生成されます。したがって、上記のコマンドは次のようにして実行できます。
val query: Query<List<Address>> = QueryDsl.execute(ListAddresses("STREET 10"))
コマンドは、fromTemplateやexecuteTemplateを使う方法に比べて、コンパイル時にSQLテンプレートを検証できるという利点があります。具体的には次のことが可能です。
- SQLテンプレートの構文上の誤りを検出できます。例えば、
/*%end*/
が不足している場合コンパイルエラーとなります。 - 利用されていないパラメータを検出できます。利用されていないパラメータが見つかった場合は警告メッセージを出力します。なお、パラメータに
org.komapper.annotation.KomapperUnused
アノテーションを付与することで警告を抑制できます。 - パラメータの型やメンバを検証できます。例えば、String型の
name
がパラメータの場合、SQLテンプレート上で/* name.unknown */
のように存在しないメンバにアクセスしようとするとコンパイルエラーとなります。
Note
コマンドのクラスは、トップレベルのクラス、ネストされたクラス、インナークラスとして定義できます。ローカルクラスとしては定義できません。コマンドには5つの種類あります。
- One
- Many
- Exec
- ExecReturnOne
- ExecReturnMany
One
1件を取得するコマンドはorg.komapper.core.One
を継承します。
@KomapperCommand("""
select * from ADDRESS where address_id = /*id*/0
""")
class GetAddressById(val street: String): One<Address?>({ selectAsAddress().singleOrNull() })
One
の型パラメータには取得したい値の型を指定します。One
のコンストラクタには、検索結果を処理するラムダ式を渡します。ラムダ式の中でできることは、fromTemplateで言及したselect
関数やselectAsEntity
関数と同じです。
上述の例では、Address
クラスに@KomapperProjetion
が付与されていることを前提にしています。そのため、selectAsAddress
関数を使って結果をAddress
クラスに変換しています。
Many
複数件を取得するコマンドはorg.komapper.core.Many
を継承します。
@KomapperCommand("""
select * from ADDRESS where street = /*street*/'test'
""")
class ListAddresses(val street: String): Many<Address>({ selectAsAddress() })
Many
の型パラメータには取得したい1件を表現する型を指定します。Many
のコンストラクタには、検索結果を処理するラムダ式を渡します。ラムダ式の中でできることは、fromTemplateで言及したselect
関数やselectAsEntity
関数と同じです。
上述の例では、Address
クラスに@KomapperProjetion
が付与されていることを前提にしています。そのため、selectAsAddress
関数を使って結果をAddress
クラスに変換しています。
Exec
更新系のDMLを実行するコマンドはorg.komapper.core.Exec
を継承します。
@KomapperCommand("""
update ADDRESS set street = /*street*/'' where address_id = /*id*/0
""")
class UpdateAddress(val id: Int, val street: String): Exec()
ExecReturnOne
更新系のDMLを実行し1件を返すコマンドはorg.komapper.core.ExecReturnOne
を継承します。
@KomapperCommand("""
insert into ADDRESS
(address_id, street, version)
values
(/* id */0, /* street */'', /* version */1)
returning address_id, street, version
""")
class InsertAddressThenReturn(val id: Int, val street: String): ExecReturnOne<Address>({ selectAsAddress().single() })
ExecReturnOne
の型パラメータやコンストラクタについては、Oneと同様です。
ExecReturnMany
更新系のDMLを実行し複数件を返すコマンドはorg.komapper.core.ExecReturnMany
を継承します。
@KomapperCommand("""
update ADDRESS set street = /*street*/'' returning address_id, street, version
""")
class UpdateAddressThenReturn(val id: Int, val street: String): ExecReturnMany<Address>({ selectAsAddress() })
ExecReturnMany
の型パラメータやコンストラクタについては、Manyと同様です。
パーシャル
パーシャルはSQLテンプレートの一部を再利用するための機能です。
パーシャルはトップレベルのconst
にorg.komapper.annotation.KomapperPartial
アノテーションを使って定義します。
@KomapperPartial(
"""
/*%if pagination != null */
limit /* pagination.limit */0 offset /*pagination.offset*/0
/*%end*/
""",
)
private const val paginationPartial = ""
パーシャルを利用するにはパーシャルが定義されたファイルと同じファイル内でコマンドを定義し、SQLテンプレートから/*> パーシャル名 */
という記法でパーシャルを参照します。
@KomapperCommand(
"""
select * from address order by address_id
/*> paginationPartial */
""",
)
class UsePartial(val pagination: Pagination?) : Many<Address>({ selectAsAddress() })
class Pagination(val limit: Int, val offset: Int)
Note
パーシャルから別のパーシャルを参照することはできません。SQLテンプレート
Komapperが提供するSQLテンプレートはいわゆる2-Way-SQL対応のテンプレートです。 バインド変数や条件分岐に関する記述をSQLコメントで表現するため、 テンプレートをアプリケーションで利用できるだけでなく、pgAdmin など一般的なSQLツールでも実行できます。
例えば条件分岐とバインド変数を含んだSQLテンプレートは次のようになります。
select name, age from person where
/*%if name != null*/
name = /*name*/'test'
/*%end*/
order by name
上記のテンプレートはアプリケーション上でname != null
が真と評価されるとき次のSQLに変換されます。
select name, age from person where name = ? order by name
name != null
が偽と評価されるとき次のSQLに変換されます。
select name, age from person order by name
Note
上述の例でname != null
が偽と評価されるとき最終的にSQLにwhere
キーワードが含まれていないことに気づいたでしょうか?
KomapperのSQLテンプレートは、WHERE句、HAVING句、GROUP BY句、ORDER BY句の内側にSQLの要素が1つも含まれない場合その句を表すキーワードを出力しません。
したがって、不正なSQLが生成されることを防ぐために1 = 1
を必ずWHERE句に含めるなどの対応は不要です。
select name, age from person where 1 = 1 // このような対応は不要
/*%if name != null*/
and name = /*name*/'test'
/*%end*/
order by name
バインド変数ディレクティブ
バインド変数は/*expression*/
のように/*
と*/
で囲んで表します。
expression
には任意の値を返す式が入ります。
次の'test'
のようにディレクティブの直後にはテスト用の値が必須です。
where name = /*name*/'test'
最終的にはテスト用の値は取り除かれ上述のテンプレートは次のようなSQLに変換されます。
/*name*/
は?
に置換され、?
にはname
が返す値がバインドされます。
where name = ?
IN句にバインドするにはexpression
はIterable
型の値でなければいけません。
where name in /*names*/('a', 'b')
IN句にタプル形式で値をバインドするにはexpression
をIterable<Pair>
型やIterable<Triple>
型の値にします。
where (name, age) in /*pairs*/(('a', 'b'), ('c', 'd'))
リテラル変数ディレクティブ
リテラル変数は/*^expression*/
のように/*^
と*/
で囲んで表します。
expression
には任意の値を返す式が入ります。
次の'test'
のようにディレクティブの直後にはテスト用の値が必須です。
where name = /*^name*/'test'
最終的にはテスト用の値は取り除かれ上述のテンプレートは次のようなSQLに変換されます。
/*^name*/
はname
が返す値(この例では"abc"
)のリテラル表現('abc'
)で置換されます。
where name = 'abc'
埋め込み変数ディレクティブ
埋め込み変数は/*#expression*/
のように/*#
と*/
で囲んで表します。
expression
には任意の値を返す式が入ります。
select name, age from person where age > 1 /*# orderBy */
上述のorderBy
の式が"order by name"
という文字列を返す場合、最終的なSQLは次のように変換されます。
select name, age from person where age > 1 order by name
ifディレクティブ
ifの条件分岐は/*%if expression*/
で始めて/*%end*/
で終わります。
expression
には真偽値を返す式が入ります。
/*%if name != null*/
name = /*name*/'test'
/*%end*/
/*%if expression*/
と/*%end*/
の間に/*%else*/
を入れることもできます。
/*%if name != null*/
name = /*name*/'test'
/*%else*/
name is null
/*%end*/
forディレクティブ
forを使ったループは/*%for identifier in expression */
で始めて/*%end*/
で終わります。
expression
にはIterable
を返す式が入りidentifier
はIterable
のそれぞれの要素を表す識別子となります。
forのループの中ではidentifier
に_has_next
のプレフィックをつけた識別子が利用可能になります。
これは次の繰り返し要素が存在するかどうかを表す真偽値を返します。
/*%for name in names */
employee_name like /* name */'hoge'
/*%if name_has_next */
/*# "or" */
/*%end */
/*%end*/
endディレクティブ
条件分岐やループ処理を終了するには、endディレクティブを使います。
endディレクティブは/*%end*/
というSQLコメントで表現されます。
パーサーレベルのコメントディレクティブ
パーサーレベルのコメントディレクティブを使用すると、SQLテンプレートが解析された後にコメントを削除できます。
パーサーレベルのコメントを表現するには、/*%! コメント */
という構文を使います。
次のようなSQLテンプレートがあるとします。
select
name
from
employee
where /*%! このコメントは削除されます */
employee_id = /* employeeId */99
上記のSQLテンプレートは、次のSQLへと解析されます。
select
name
from
employee
where
employee_id = ?
式
ディレクティブ内で参照される式の中では以下の機能がサポートされています。
- 演算子の実行
- プロパティアクセス
- 関数呼び出し
- クラス参照
- 拡張プロパティや拡張関数の利用
演算子
次の演算子がサポートされています。意味はKotlinの演算子と同じです。
==
!=
>=
<=
>
<
!
&&
||
次のように利用できます。
/*%if name != null && name.length > 0 */
name = /*name*/'test'
/*%else*/
name is null
/*%end*/
プロパティアクセス
.
や?.
を使ってプロパティにアクセスできます。?.
はKotlinのsafe call operatorと同じ挙動をします。
/*%if person?.name != null */
name = /*person?.name*/'test'
/*%else*/
name is null
/*%end*/
関数呼び出し
関数を呼び出せます。
/*%if isValid(name) */
name = /*name*/'test'
/*%else*/
name is null
/*%end*/
クラス参照
@クラスの完全修飾名@
という記法でクラスを参照できます。
例えばexample.Direction
というenum classにWEST
という要素がある場合、次のように参照できます。
/*%if direction == @example.Direction@.WEST */
direction = 'west'
/*%end*/
拡張プロパティと拡張関数
Kotlinが提供する以下の拡張プロパティと拡張関数をデフォルトで利用できます。
val CharSequence.lastIndex: Int
fun CharSequence.isBlank(): Boolean
fun CharSequence.isNotBlank(): Boolean
fun CharSequence.isNullOrBlank(): Boolean
fun CharSequence.isEmpty(): Boolean
fun CharSequence.isNotEmpty(): Boolean
fun CharSequence.isNullOrEmpty(): Boolean
fun CharSequence.any(): Boolean
fun CharSequence.none(): Boolean
/*%if name.isNotBlank() */
name = /*name*/'test'
/*%else*/
name is null
/*%end*/
また、Komapperが定義する以下の拡張関数も利用できます。
fun String?.asPrefix(): String?
fun String?.asInfix(): String?
fun String?.asSuffix(): String?
fun String?.escape(): String?
例えば、asPrefix()を呼び出すと"hello"
という文字列が"hello%"
となり前方一致検索で利用できるようになります。
where name like /*name.asPrefix()*/
同様にasInfix()
を呼び出すと中間一致検索用の文字列に変換し、asSuffix()
を呼び出すと後方一致検索用の文字列に変換します。
escape()
は特別な文字をエスケープします。例えば、"he%llo_"
という文字列を"he\%llo\_"
のような文字列に変換します。
Note
asPrefix()
、asInfix()
、asSuffix()
は内部でエスケープ処理を実行するので別途escape()
を呼び出す必要はありません。