ISID テックブログ

ISIDが運営する技術ブログ

Kotlinでデータベースアクセス「Komapper」

これは電通国際情報サービス アドベントカレンダーの14日目の記事です。

はじめに

X(クロス)イノベーション本部 アドバンストテクノロジー部の中村です。

最近、サーバサイドのプログラミング言語にKotlinを使うという話を耳にするようになってきました。Kotlinが便利な言語機能やAPIを持ち、Javaとの相互運用性が高いということもあって、システム開発プログラミング言語選定において魅力的な選択肢の1つになっていると感じます。

私はJavaもKotlinも大好きなのですが、体感的にKotlinの生産性はJavaに比べて数倍高いと感じています。もし私が技術選定をする立場ならKotlinは最初に検討したい言語です。

今回は、Kotlinの言語機能を最大限活用することを目指して開発を始めたデータベースアクセスライブラリKomapperの紹介をしたいと思います。

Komapperとは

Komapperは、Apache License 2.0の下で私が個人的に開発しているオープンソースソフトウェアです。私は、DomaというJavaで作られたデータベースアクセスライブラリの開発を10年以上続けていて、この領域のノウハウをある程度蓄積できたと感じています。Komapperにはそのノウハウを存分に注ぎ込みました。現時点のKomapper開発は個人的な取り組みですが、今後改善を加え、業務で開発・活用していく予定です。

さて、Komapperは、以下のような強みを持つサーバサイドKotlinのためのデータベースアクセスライブラリです。

  • JDBCR2DBCのサポート
  • コンパイル時のコード生成
  • 不変で合成可能なクエリ
  • Value Classのサポート
  • Spring Bootのサポート

Komapperでは、SQLを表現したクエリと呼ばれるオブジェクトを構築した後、そのクエリを実行することで実際にSQLを発行します。下記のサンプルコードをみてください。

val e = Meta.employee
val query = QueryDsl.from(e).where { e.salary greaterEq BigDecimal(5_000) }.orderBy(e.employeeId)
val employees = db.runQuery { query }

どうでしょう?SQLが分かる方にとっては何をしているか伝わりやすいコードではないでしょうか?

1行目は従業員テーブルを変数に確保しています。2行目では、「給料が5,000ドル以上の従業員」の一覧をIDでソートして取得するSQLを組み立てています。3行目でdb.runQuery関数を実行すると下記のSQLが発行されます。

select t0_.EMPLOYEE_ID, t0_.EMPLOYEE_NAME, t0_.SALARY, t0_.DEPARTMENT_ID from EMPLOYEE as t0_ where t0_.SALARY >= ? order by t0_.EMPLOYEE_ID asc

このようにSQLに似たクエリを宣言的に構築し実行できるのがKomapperの特徴となっています。

しかし、見た目がSQLっぽいコードを書けるだけであれば、特段目新しさは感じないかもしれません。そのようなライブラリはKotlinやJavaにも、それからその他のプログラミング言語にもたくさんあります。

クエリの分解

Komapperのクエリは分解できます。先ほどの例で示したクエリを3つの部品に分解して書いてみます。

val employeeQuery = QueryDsl.from(e)
val isHighPerformer = where { e.salary greaterEq BigDecimal(5_000) }
val orderById = orderBy(e.employeeId)

SQLで言うところのFROM句、WHERE句、ORDER BY句に分解できていますね。

分解したら名前づけも必要です。「給与が5,000ドル以上の従業員」は「ハイパフォーマー」と定義し、この検索条件はisHighPerformerという名前にしましょう。

分解してできた3つの部品からは容易に1つのクエリを組み立てられます。

val query = employeeQuery.where(isHighPerformer).orderBy(orderById)

db.runQuery関数を実行すると最初のクエリを1つで書いた例と全く同じSQLが発行されます。

分解したクエリの再利用

クエリを分解できることの利点はなんでしょうか?それは再利用できることです。

例えば、「SALESの部署に所属」する「ハイパフォーマー」を検索したい場合、先ほど分解して作成したemployeeQueryisHighPerformerorderByIdを使って次のように書けます。

val e = Meta.employee
val d = Meta.department
val query = employeeQuery.innerJoin(d) { 
    e.departmentId eq d.departmentId
}.where { 
    d.departmentName eq "SALES"
    and(isHighPerformer) 
}.orderBy(orderById)

1行目で部署テーブルを変数に確保し、2行目以降では従業員テーブルと部署テーブルを結合してクエリを組み立てています。

db.runQuery関数で実行すると下記のSQLが発行されます。検索条件やソート条件が再利用されているのが分かります。

select t0_.EMPLOYEE_ID, t0_.EMPLOYEE_NAME, t0_.SALARY, t0_.DEPARTMENT_ID from EMPLOYEE as t0_ inner join DEPARTMENT as t1_ on (t0_.DEPARTMENT_ID = t1_.DEPARTMENT_ID) where t1_.DEPARTMENT_NAME = ? and (t0_.SALARY >= ?) order by t0_.EMPLOYEE_ID asc

さらに、更新時にも検索条件を再利用できます。例えば、「ハイパフォーマー」の給料を一律1,000ドルupする更新クエリを作ってみましょう。

val e = Meta.employee
val query = QueryDsl.update(e).set {
    e.salary set e.salary + BigDecimal(1_000)
}.where(isHighPerformer)

db.runQuery関数で実行すると下記のSQLが発行されます。こちらも検索条件が再利用されているのが分かります。

update EMPLOYEE as t0_ set SALARY = (t0_.SALARY + ?) where t0_.SALARY >= ?

複数のクエリに同じ検索条件が登場するのは良くあることだと思います。よく使う条件をあらかじめ定義しておけば変更を加えた場合に修正漏れが発生しないという利点もあります。

利用しているKotlinの機能

Kotlinを活用することで、SQLに似たクエリを宣言的に記述したり、分解されたクエリを組み合わせたりできることを示しました。このようなことを実現するのに以下のようなKotlinの言語機能が役立っています。

では、もしKomapperがJavaで書かれたライブラリだったらどうでしょうか?Javaでは上述の機能が使えないため、APIに違いが出てきます。この記事の最初に登場したクエリと比較してみます。

Kotlinで書かれたKomapperをKotlinから使う例(この記事最初のクエリの再掲):

val query = QueryDsl.from(e).where { e.salary greaterEq BigDecimal(5_000) }.orderBy(e.employeeId)

Javaで書かれたKomapperをKotlinから使う例:

val query = QueryDsl.from(e).where { it.greaterEq(e.salary(), BigDecimal(5_000)) }.orderBy(e.employeeId)

前者のKotlin版Komapperを使った場合に比べて、後者のJava版Komapperを使った場合ではwhere関数のラムダ式の中においていくつか違いがあります。

  • パラメーターのitが登場した
  • greaterEq中置記法演算子から関数になった
  • e.salaryがプロパティから関数になった
  • 関数が増えた分()の括弧も増えた

どのように思われますか?一見、大した差ではないように感じると思います。

しかし、SQLとの比較で考えると、いずれもSQLシンタックスとは離れる方向の違いが生まれています。これはクエリを読み書きする際の思考を邪魔します。単純なクエリの場合はおそらく気にならなくても、分量が増えたり、クエリが複雑になると無視できない違いになると考えています。

おわりに

クエリの宣言と分解したクエリの再利用に焦点を絞って私が開発しているKomapperを紹介しました。

KotlinはJavaのライブラリをシームレスに呼び出せます。そのためJavaライブラリを活用するのは良い戦略です。しかし、宣言的な記述をしたい場合はJavaライブラリよりもKotlinライブラリに優位性があると思います。KotlinのドキュメントにType-safe buildersの章がありますが、KomapperもType-safe builderの一種と言えます。Kotlinのライブラリを作成する人にとっても選定する人とっても、Type-safe builderをどう捉えるかがKotlinを活用する上での1つのポイントかもしれません。

Komapperは、現在開発中で、本記事公開時点のバージョンはv0.24.0です。

感想を述べてくれる方、実際に使ってみてくれる方、一緒に開発してくれる方など歓迎いたします。どのような形でも構わないので興味を持っていただけたら嬉しいです。Komapperを使ってみたい場合は、QuickstartExamplesをご覧ください。今回紹介したサンプルコードそのものではありませんが、同様のサンプルコードはrepository-pattern-jdbc以下にあります。

ありがとうございました。

執筆:@nakamura.toshihiro、レビュー:@sato.taichiShodoで執筆されました