Optimistic Lock
This chapter describes optimistic lock version fields in mutation APIs.
Declare the version column
Use @Version on the KPojo field that stores the optimistic lock version. The annotation is enabled by default.
Product.kt
import com.kotlinorm.annotations.Default
import com.kotlinorm.annotations.LogicDelete
import com.kotlinorm.annotations.PrimaryKey
import com.kotlinorm.annotations.Version
import com.kotlinorm.interfaces.KPojo
data class Product(
@PrimaryKey(identity = true)
var id: Int? = null,
var name: String? = null,
@Version
var version: Int? = null,
@LogicDelete
@Default("0") // @Default("false") for Postgres
var deleted: Boolean? = null
) : KPojoA global strategy can enable the same version field name across KPojo classes.
global config
import com.kotlinorm.Kronos
import com.kotlinorm.beans.config.KronosCommonStrategy
import com.kotlinorm.beans.dsl.Field
Kronos.optimisticLockStrategy = KronosCommonStrategy(enabled = true, field = Field("version"))Insert initializes the version
When the version strategy is active, insert writes the initial version value.
kotlin
Product(name = "Keyboard")
.insert()
.execute()Mysql
INSERT INTO `product` (`name`, `version`, `deleted`)
VALUES (:name, :version, :deleted)params
name = "Keyboard"
version = 0
deleted = 0Update increments the version field
update().execute() adds a version increment to the generated SET list. The version field cannot be assigned manually in the same update.
kotlin
Product(id = 1)
.update()
.set { it.name = "Keyboard Pro" }
.by { it.id }
.execute()Mysql
UPDATE `product`
SET `name` = :nameNew,
`version` = `version` + :version2PlusNew
WHERE `id` = :id
AND `deleted` = 0params
id = 1
nameNew = "Keyboard Pro"
version2PlusNew = 1Generated update SQL increments the version field. Add a version condition in where { ... } when the application needs the update to match the version read earlier.
version condition
Product(id = 1, version = 3)
.update()
.set { it.name = "Keyboard Pro" }
.where { it.id == 1 && it.version == 3 }
.execute()version condition SQL
UPDATE `product`
SET `name` = :nameNew,
`version` = `version` + :version2PlusNew
WHERE `product`.`id` = :id
AND `product`.`version` = :version
AND `deleted` = 0Logical delete also increments the version
For a KPojo that uses both @LogicDelete and @Version, logical delete writes the delete marker and increments the version field.
kotlin
Product(id = 1)
.delete()
.by { it.id }
.execute()Mysql
UPDATE `product`
SET `deleted` = :deletedNew,
`version` = `version` + :version2PlusNew
WHERE `id` = :id
AND `deleted` = 0Upsert uses the configured mutation paths
Match-field upsert() chooses update() or insert() from the row match result. The update branch gets the same version increment as a normal update, and the insert branch gets the same initial version as a normal insert.
onConflict() upsert also initializes the version on insert and increments it in the conflict update assignment.
kotlin
Product(id = 1, name = "Keyboard")
.upsert { it.name }
.on { it.id }
.execute()match SQL
SELECT COUNT(1)
FROM `product`
WHERE `id` = :id
-- update branch
UPDATE `product`
SET `name` = :nameNew,
`version` = `version` + :version2PlusNew
WHERE `id` = :id
AND `deleted` = 0
-- insert branch
INSERT INTO `product` (`id`, `name`, `version`, `deleted`)
VALUES (:id, :name, :version, :deleted)Mysql
INSERT INTO `product` (`id`, `name`, `version`, `deleted`)
VALUES (:id, :name, :version, :deleted)
ON DUPLICATE KEY UPDATE
`name` = :name,
`version` = `version` + :version2PlusNewSee 📚Upsert for on(...) and onConflict() examples.