Version v0.26 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 DSL

テンプレートからSQLを組み立てるためのDSL

We are currently working on the translation from Japanese to English. We would appreciate your cooperation.

概要

Template DSLはSQLテンプレートを使ってSQLを組み立てます。

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

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

SELECT

検索を実施するにはfrom関数に SQLテンプレートbind関数にSQLテンプレート内で利用したいデータを渡します。 SQLテンプレート内の各種ディレクティブではデータのpublicなメンバを参照できます。 検索結果を任意の型に変換するためにselect関数にラムダ式を渡します。

data class Condition(val street: String)

val sql = "select * from ADDRESS where street = /*street*/'test'"
val data = Condition("STREET 10")
val query: Query<List<Address>> = TemplateDsl.from(sql).bind(data).select { row: Row ->
    Address(
        row.asInt("address_id")!!,
        row.asString("street")!!,
        row.asInt("version")!!
    )
}

上述の例ではbind関数にConditionクラスのインスタンスを渡していますが、代わりにobject式を渡すこともできます。

val sql = "select * from ADDRESS where street = /*street*/'test'"
val query: Query<List<Address>> = TemplateDsl.from(sql).bind(
    object {
        val street = "STREET 10"
    }
).select { row: Row ->
    Address(
        row.asInt("address_id")!!,
        row.asString("street")!!,
        row.asInt("version")!!
    )
}

select関数に渡すラムダ式に登場するRowjava.sql.ResultSetio.r2dbc.spi.Rowの薄いラッパーです。 カラムのラベル名やインデックスで値を取得する関数を持ちます。 なお、インデックスは0から始まります。

options

クエリの挙動をカスタマイズするにはoptionsを呼び出します。 ラムダ式のパラメータはデフォルトのオプションを表します。 変更したいプロパティを指定してcopyメソッドを呼び出してください。

val sql = "select * from ADDRESS where street = /*street*/'test'"
val query: Query<List<Address>> = TemplateDsl.from(sql).options {
  it.copty(
    fetchSize = 100,
    queryTimeoutSeconds = 5
  )
}.bind(
  object {
    val street = "STREET 10"
  }
).select { row: Row ->
  Address(
    row.asInt("address_id")!!,
    row.asString("street")!!,
    row.asInt("version")!!
  )
}

指定可能なオプションには以下のものがあります。

escapeSequence
LIKE句に指定されるエスケープシーケンスです。デフォルトはnullDialectの値を使うことを示します。
fetchSize
フェッチサイズです。デフォルトはnullでドライバの値を使うことを示します。
maxRows
最大行数です。デフォルトはnullでドライバの値を使うことを示します。
queryTimeoutSeconds
クエリタイムアウトの秒数です。デフォルトはnullでドライバの値を使うことを示します。
suppressLogging
SQLのログ出力を抑制するかどうかです。デフォルトはfalseです。

executionOptions の同名プロパティよりもこちらに明示的に設定した値が優先的に利用されます。

EXECUTE

更新系のDMLを実行するにはexecute関数に SQLテンプレートbind関数にSQLテンプレート内で利用したいデータを渡します。 SQLテンプレート内の各種ディレクティブではデータのpublicなメンバを参照できます。

クエリ実行時にキーが重複した場合、org.komapper.core.UniqueConstraintExceptionがスローされます。

data class Condition(val id: Int, val street: String)

val sql = "update ADDRESS set street = /*street*/'' where address_id = /*id*/0"
val data = Condition(15, "NY street")
val query = Query<Int> = TemplateDsl.execute(sql).bind(data)

上述の例ではbind関数にConditionクラスのインスタンスを渡していますが、代わりにobject式を渡すこともできます。

val sql = "update ADDRESS set street = /*street*/'' where address_id = /*id*/0"
val query = Query<Int> = TemplateDsl.execute(sql).bind( object { id = 15, street = "NY street" } )

options

クエリの挙動をカスタマイズするにはoptionsを呼び出します。 ラムダ式のパラメータはデフォルトのオプションを表します。 変更したいプロパティを指定してcopyメソッドを呼び出してください。

data class Condition(val id: Int, val street: String)

val sql = "update ADDRESS set street = /*street*/'' where address_id = /*id*/0"
val data = Condition(15, "NY street")
val query = Query<Int> = TemplateDsl.execute(sql).bind(data).options {
    it.copty(
      queryTimeoutSeconds = 5
    )
}

指定可能なオプションには以下のものがあります。

escapeSequence
LIKE句に指定されるエスケープシーケンスです。デフォルトはnullDialectの値を使うことを示します。
queryTimeoutSeconds
クエリタイムアウトの秒数です。デフォルトはnullでドライバの値を使うことを示します。
suppressLogging
SQLのログ出力を抑制するかどうかです。デフォルトはfalseです。

executionOptions の同名プロパティよりもこちらに明示的に設定した値が優先的に利用されます。

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*/

ディレクティブ内で参照される式の中では以下の機能がサポートされています。

  • 演算子の実行
  • プロパティアクセス
  • 関数呼び出し
  • クラス参照
  • 拡張プロパティや拡張関数の利用

演算子

次の演算子がサポートされています。意味は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\_"のような文字列に変換します。 なお、asPrefix()asInfix()asSuffix()は内部でエスケープ処理を実行するので別途escape()を呼び出す必要はありません。

Last modified January 10, 2022: Prepare English contents (f426de7)