Ktormについて
最近の仕事ではSpring Batchを使ったバッチ処理をKotlinで書いています。
mysqlのDBにSQLを投げる際にORマッパーをどうするか調査した結果、Kotlin製のOR
マッパーであるKtormを使用しています。
KtormはSQL部分をDSLで見通しよく書けるので、結構満足して使っています。
例)
select
user_id,
sum(amount)
from
reports
where
created_at >= '2020/05/01'
group by
user_id
上記のようなSQLだと全てDSLで書くことができ、テーブルのカラム名もクラスに定義され(今回の例ではReportDef)
カラムの内容の変更があればエンティティ部分とその使用箇所を修正すればいいし、修正漏れがあればコンパイルエラーになってくれます。
database.from(ReportDef)
.select(
ReportDef.userId,
sum(ReportDef.amount)
).where {
ReportDef.createdAt greaterEq date
}.groupBy(ReportDef.userId)
interface Report : Entity<Report> {
companion object : Entity.Factory<Report>()
var id: Long
var userId: Long
var amount: Int
var purchaseType: String
}
object ReportDef : Table<Report>("reports") {
val id by long("id").primaryKey().bindTo { it.id }
val userId by long("user_id").bindTo { it.userId }
val amount by long("amount").bindTo { it.amount }
val purchaseType by varchar("purchase_type").bindTo { it.purchaseType }
}
ハマったケース
今回 購入方法がCASHとかCARDのように複数存在し、'CASH'のみの使用回数を知る必要がありました。
SQLだとselect部分にcount(purchase_type='CASH')
を追加する必要がありました。
count(purchase_type)
だとKtormではcount(ReportDef.purchaseMethod)
で書けるのですが、
count(purchase_type='CASH')
はさすがに素のKtormでは書けません。
基本的にORマッパーが提供している機能で必要なSQLを書ければ良いのですが、
未提供の場合は最悪のケースで文字列の中に素のSQLを書くことになり、
なんとか避けたいなぁと思っていました。
調査結果
色々調べたところ、Ktorm自体もmysqlで使用するSQL関数を拡張しているロジック
があり、これを参考に拡張関数のcountifメソッドを実装することでKtormのDSLで書くことができました。
database.from(ReportDef)
.select(
ReportDef.userId,
sum(ReportDef.amount),
ReportDef.purchaseMethod.countif('CASH') // 追加部分
).where {
ReportDef.createdAt greaterEq date
}.groupBy(ReportDef.userId)
fun <T : Any> ColumnDeclaring<T>.countIf(right: String): FunctionExpression<T> {
val rightExpression = ArgumentExpression(right, VarcharSqlType)
val equal = BinaryExpression(BinaryExpressionType.EQUAL, asExpression(), rightExpression.asExpression(), BooleanSqlType)
return FunctionExpression(
functionName = "sum",
arguments = listOf(equal),
sqlType = sqlType)
}
やっていることをざっくり説明すると以下になります。
- SQLでのsum関数を使用するFunctionExpressionを返す拡張メソッドを用意する
- 引数から渡された文字列を表すArgumentExpressionを用意する
-
=
を表すBinaryExpressionを用意し、右辺側に先ほどのArgumentExpressionを渡す -
=
を表すBinaryExpressionをFunctionExpressionのarguments引数に渡す
まとめ
- KtormのDSLはSQL関数をどうしても使う必要があるケースでもメソッドを実装することで対応可能。
- Kotlinの拡張関数は便利