Home About Contact
Kotlin Script , Kotlin Exposed

Kotlin Exposed DAO で Entity を apply したときの調査

Exposed で Entity インスタンスを apply したときに 変更のあるプロパティだけ更新される件を確認する。

環境はこれ:

$ kotlin -version
Kotlin version 2.1.0-release-394 (JRE 17.0.10+7-LTS)

基本の定義

まず、 name, hp をプロパティを持つ Pokemon オブジェクトを考えます。 これを DAO で扱うために、テーブル、DAO(Entity) を定義します。

テーブル:

object PokemonsTable: IntIdTable("pokemons") {
    val name = varchar("name", length = 128)
    val hp = integer("hp") // hit point
}

DAO(Entity):

class PokemonDAO(id: EntityID<Int>): IntEntity(id) {
  companion object: IntEntityClass<PokemonDAO>(PokemonsTable)
  
  var name by PokemonsTable.name
  var hp by PokemonsTable.hp
}

操作する

先ほど定義した Tables / DAO を使ってアプリケーションを書いていきます。

データベースとの接続を最初につくります。

Database.connect(
  url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1",
  driver = "org.h2.Driver",
  user = "root",
  password = "",
)

それからテーブルを用意:

  SchemaUtils.drop(PokemonsTable)
  SchemaUtils.create(PokemonsTable)

それでは最初の操作は「テーブルにポケモンをひとつ追加」です。

  PokemonDAO.new {
    name = "Pikachu"
    hp = 120
  }

ただし、これらのデータベース処理は transaction ブロックで実行する必要があるので、 全体では結局次のようになります。

transaction {
  // 0)
  addLogger(StdOutSqlLogger)

  // 1)
  SchemaUtils.drop(PokemonsTable)
  SchemaUtils.create(PokemonsTable)

  // 2)
  PokemonDAO.new {
    name = "Pikachu"
    hp = 120
  }
}

処理内容がわかるように addLogger(StdOutSqlLogger) を入れています。 これで内部で発行される SQL を標準出力として見ることができます。

実行してみます。

$ kotlin pokemons.main.kts
SQL: SELECT SETTING_VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME = 'MODE'
SQL: DROP TABLE IF EXISTS POKEMONS
SQL: CREATE TABLE IF NOT EXISTS POKEMONS (ID INT AUTO_INCREMENT PRIMARY KEY, "name" VARCHAR(128) NOT NULL, HP INT NOT NULL)
SQL: INSERT INTO POKEMONS (HP, "name") VALUES (120, 'Pikachu')

追加したポケモンを取得する

ここでは一つのポケモンしか存在しないことが自明なので、次のようにして取得します。

  val pokemon: PokemonDAO? = PokemonDAO.all().firstOrNull()
  println(pokemon?.name) // => Pikachu

ここで、この pokemon を apply を使って更新します。

  pokemon?.apply {
    name = "Charamander"
    hp = 110
  }

これで実行してみると 該当部分の SQL は次のように発行されます。

SQL: UPDATE POKEMONS SET HP=110, "name"='Charamander' WHERE ID = 1

まあこれは当然の振る舞いです。

次に、更新するときに name は "Pikachu" のままにしてみます。

  pokemon?.apply {
    name = "Pikachu"
    hp = 110
  }

これを実行すると次のようになります。

SQL: UPDATE POKEMONS SET HP=110 WHERE ID = 1

へー。 どうやら既存の値と同じ場合は該当フィールド(ここでは name)の更新は起きないようです。

まとめ

今回書いたコードを掲載します。

// pokemons.main.kts

@file:Repository("https://repo1.maven.org/maven2/")

@file:DependsOn("org.jetbrains.exposed:exposed-dao:0.56.0")
@file:DependsOn("org.jetbrains.exposed:exposed-core:0.56.0")
@file:DependsOn("org.jetbrains.exposed:exposed-jdbc:0.56.0")
@file:DependsOn("com.h2database:h2:2.3.232")

import org.jetbrains.exposed.dao.IntEntity
import org.jetbrains.exposed.dao.IntEntityClass

import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.IntIdTable

import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.addLogger
import org.jetbrains.exposed.sql.StdOutSqlLogger
import org.jetbrains.exposed.sql.SchemaUtils


object PokemonsTable: IntIdTable("pokemons") {
    val name = varchar("name", length = 128)
    val hp = integer("hp") // hit point
}

class PokemonDAO(id: EntityID<Int>): IntEntity(id) {
  companion object: IntEntityClass<PokemonDAO>(PokemonsTable)
  
  var name by PokemonsTable.name
  var hp by PokemonsTable.hp
}


Database.connect(
  url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1",
  driver = "org.h2.Driver",
  user = "root",
  password = "",
)

transaction {
  // 0)
  addLogger(StdOutSqlLogger)

  // 1)
  SchemaUtils.drop(PokemonsTable)
  SchemaUtils.create(PokemonsTable)

  // 2)
  PokemonDAO.new {
    name = "Pikachu"
    hp = 120
  }

  // 3)
  val pokemon: PokemonDAO? = PokemonDAO.all().firstOrNull()
  println(pokemon?.name)

  // 4)
  pokemon?.apply {
    //name = "Charamander"
    name = "Pikachu"
    hp = 110
  }
}

以上です。