Exposed: Optimistic/Pessimistic locking

Created on 25 Aug 2019  路  5Comments  路  Source: JetBrains/Exposed

How can I use optimistic / pessimistic locking with exposed?
In Spring/hibernate it can be done using @Version annotation on a version attribute in a table or using @Lock attribute on the JPA Query.

Is there any way to do something like that here?

enhancement

Most helpful comment

It could be implemented on your own, consider such table:

object UserTable : Table("user") {
    val name = text("name")
    val version = integer("version")

    override val primaryKey = PrimaryKey(name)
}

When performing update, you can check that version is equal to provided version:

val updatedRows = UserTable.update({ (UserTable.name eq name) and (UserTable.version eq version) }) {
    it[UserTable.version] = version + 1
    // update something else
}

if (updatedRows == 0) {
    throw RuntimeException("Optimistic locking occurred")
}

This could be improved by such extensions:

interface OptimisticLockingAware {
    val optimisticLockingVersion: Column<Int>
}

fun <T> T.optimisticLockingAwareUpdate(where: (SqlExpressionBuilder.() -> Op<Boolean>)? = null,
                                       version: Int,
                                       limit: Int? = null,
                                       body: T.(UpdateStatement) -> Unit
): Int where T : Table, T : OptimisticLockingAware {
    val optimisticLockingAwareWhereClause: (SqlExpressionBuilder.() -> Op<Boolean>) = {
        val versionEquals = [email protected] eq version
        when (where != null) {
            true -> where(this).and(versionEquals)
            else -> versionEquals
        }
    }

    val updateVersionAwareStatement: T.(UpdateStatement) -> Unit = {
        body(it)
        it[this.optimisticLockingVersion] = version + 1
    }

    return this.update(optimisticLockingAwareWhereClause, limit, updateVersionAwareStatement)
}

Then, UserTable should implement OptimisticLockingAware interface:

object UserTable : Table("user"), OptimisticLockingAware {
    val name = text("name")
    val version = integer("version")

    override val optimisticLockingVersion = version
    override val primaryKey = PrimaryKey(name)
}

And here is improved invocation of update which is aware of optimistic locking:

val updatedRows = UserTable.optimisticLockingAwareUpdate(where = { UserTable.name eq name }, version = version) {
    // update something
}

if (updatedRows == 0) {
    throw RuntimeException("Optimistic locking occurred")
}

Note that exception could be thrown inside optimisticLockingAwareUpdate, it depends on your use case ;)

All 5 comments

Not at the moment

@Tapac As I see in source code, forUpdate() function in Query class provides Pessimistic lock.

I don't see it in any document, is there a special reason for this? Is there any objection to using this function?

any news on this?

It could be implemented on your own, consider such table:

object UserTable : Table("user") {
    val name = text("name")
    val version = integer("version")

    override val primaryKey = PrimaryKey(name)
}

When performing update, you can check that version is equal to provided version:

val updatedRows = UserTable.update({ (UserTable.name eq name) and (UserTable.version eq version) }) {
    it[UserTable.version] = version + 1
    // update something else
}

if (updatedRows == 0) {
    throw RuntimeException("Optimistic locking occurred")
}

This could be improved by such extensions:

interface OptimisticLockingAware {
    val optimisticLockingVersion: Column<Int>
}

fun <T> T.optimisticLockingAwareUpdate(where: (SqlExpressionBuilder.() -> Op<Boolean>)? = null,
                                       version: Int,
                                       limit: Int? = null,
                                       body: T.(UpdateStatement) -> Unit
): Int where T : Table, T : OptimisticLockingAware {
    val optimisticLockingAwareWhereClause: (SqlExpressionBuilder.() -> Op<Boolean>) = {
        val versionEquals = [email protected] eq version
        when (where != null) {
            true -> where(this).and(versionEquals)
            else -> versionEquals
        }
    }

    val updateVersionAwareStatement: T.(UpdateStatement) -> Unit = {
        body(it)
        it[this.optimisticLockingVersion] = version + 1
    }

    return this.update(optimisticLockingAwareWhereClause, limit, updateVersionAwareStatement)
}

Then, UserTable should implement OptimisticLockingAware interface:

object UserTable : Table("user"), OptimisticLockingAware {
    val name = text("name")
    val version = integer("version")

    override val optimisticLockingVersion = version
    override val primaryKey = PrimaryKey(name)
}

And here is improved invocation of update which is aware of optimistic locking:

val updatedRows = UserTable.optimisticLockingAwareUpdate(where = { UserTable.name eq name }, version = version) {
    // update something
}

if (updatedRows == 0) {
    throw RuntimeException("Optimistic locking occurred")
}

Note that exception could be thrown inside optimisticLockingAwareUpdate, it depends on your use case ;)

Any updates on this?
While it's easy for us at table level, I think it's not doable with DAO without framework support

Was this page helpful?
0 / 5 - 0 ratings

Related issues

gcscaglia picture gcscaglia  路  3Comments

supertote picture supertote  路  3Comments

barry-m picture barry-m  路  3Comments

fmgonsalves picture fmgonsalves  路  3Comments

mgmeiner picture mgmeiner  路  3Comments