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クエリ

SQLテンプレートを利用するクエリ

概要

TEMPLATEクエリはSQLテンプレートを利用して構築するクエリです。

TEMPLATEクエリはコアのモジュールには含まれないオプション機能です。 利用するにはGradleの依存関係に次のような宣言が必要です。

val komapperVersion: String by project
dependencies {
    implementation("org.komapper:komapper-template:$komapperVersion")
}

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関数に渡すラムダ式に登場するRowjava.sql.ResultSetio.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句に指定されるエスケープシーケンスです。デフォルトはnullDialectの値を使うことを示します。
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()

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句に指定されるエスケープシーケンスです。デフォルトはnullDialectの値を使うことを示します。
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() })

コマンドを定義しビルドを実行すると、QueryDslexecuteという名前の拡張関数が生成されます。したがって、上記のコマンドは次のようにして実行できます。

val query: Query<List<Address>> = QueryDsl.execute(ListAddresses("STREET 10"))

コマンドは、fromTemplateexecuteTemplateを使う方法に比べて、コンパイル時にSQLテンプレートを検証できるという利点があります。具体的には次のことが可能です。

  • SQLテンプレートの構文上の誤りを検出できます。例えば、/*%end*/が不足している場合コンパイルエラーとなります。
  • 利用されていないパラメータを検出できます。利用されていないパラメータが見つかった場合は警告メッセージを出力します。なお、パラメータにorg.komapper.annotation.KomapperUnusedアノテーションを付与することで警告を抑制できます。
  • パラメータの型やメンバを検証できます。例えば、String型のnameがパラメータの場合、SQLテンプレート上で/* name.unknown */のように存在しないメンバにアクセスしようとするとコンパイルエラーとなります。

コマンドには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テンプレートの一部を再利用するための機能です。 パーシャルはトップレベルのconstorg.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)

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

バインド変数ディレクティブ

バインド変数は/*expression*/のように/**/で囲んで表します。 expressionには任意の値を返す式が入ります。

次の'test'のようにディレクティブの直後にはテスト用の値が必須です。

where name = /*name*/'test'

最終的にはテスト用の値は取り除かれ上述のテンプレートは次のようなSQLに変換されます。 /*name*/?に置換され、?にはnameが返す値がバインドされます。

where name = ?

IN句にバインドするにはexpressionIterable型の値でなければいけません。

where name in /*names*/('a', 'b')

IN句にタプル形式で値をバインドするにはexpressionIterable<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を返す式が入りidentifierIterableのそれぞれの要素を表す識別子となります。 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\_"のような文字列に変換します。

最終更新 August 17, 2024: Improve example code (436f748)