All Downloads are FREE. Search and download functionalities are using the official Maven repository.

skinny.orm.feature.OptimisticLockWithTimestampFeature.scala Maven / Gradle / Ivy

package skinny.orm.feature

import scalikejdbc._
import skinny.orm.exception.OptimisticLockException
import org.joda.time.DateTime
import org.slf4j.LoggerFactory

/**
 * Optimistic lock with timestamp.
 *
 * @tparam Entity entity
 */
trait OptimisticLockWithTimestampFeature[Entity]
  extends OptimisticLockWithTimestampFeatureWithId[Long, Entity]

trait OptimisticLockWithTimestampFeatureWithId[Id, Entity] extends CRUDFeatureWithId[Id, Entity] {

  private[this] val logger = LoggerFactory.getLogger(classOf[OptimisticLockWithTimestampFeatureWithId[Id, Entity]])

  /**
   * Lock timestamp field name.
   */
  def lockTimestampFieldName = "lockTimestamp"

  /**
   * Returns where condition part which search by primary key and lock timestamp.
   *
   * @param id primary key
   * @param timestamp lock timestamp
   * @return query part
   */
  protected def byIdAndTimestamp(id: Long, timestamp: Option[DateTime]): SQLSyntax = timestamp.map { t =>
    sqls.eq(column.field(primaryKeyFieldName), id).and.eq(column.field(lockTimestampFieldName), t)
  }.getOrElse {
    sqls.eq(column.field(primaryKeyFieldName), id).and.isNull(column.field(lockTimestampFieldName))
  }

  /**
   * Returns update query builder which updates a single entity by primary key and lock timestamp.
   *
   * @param id primary key
   * @param timestamp lock timestamp
   * @return updated count
   */
  def updateByIdAndTimestamp(id: Long, timestamp: Option[DateTime]): UpdateOperationBuilder = {
    updateBy(byIdAndTimestamp(id, timestamp))
  }

  /**
   * Returns update query builder which updates a single entity by primary key and lock timestamp.
   *
   * @param id primary key
   * @param timestamp lock timestamp
   * @return updated count
   */
  def updateByIdAndTimestamp(id: Long, timestamp: DateTime): UpdateOperationBuilder = {
    updateBy(byIdAndTimestamp(id, Option(timestamp)))
  }

  private[this] def updateByHandler(session: DBSession, where: SQLSyntax, namedValues: Seq[(SQLSyntax, Any)], count: Int): Unit = {
    if (count == 0) {
      throw new OptimisticLockException(
        s"Conflict ${lockTimestampFieldName} is detected (condition: '${where.value}', ${where.parameters.mkString(",")}})"
      )
    }
  }
  afterUpdateBy(updateByHandler _)

  override def updateBy(where: SQLSyntax): UpdateOperationBuilder = new UpdateOperationBuilderWithVersion(this, where)

  /**
   * Update query builder/executor.
   *
   * @param mapper mapper
   * @param where condition
   */
  class UpdateOperationBuilderWithVersion(mapper: CRUDFeatureWithId[Id, Entity], where: SQLSyntax)
      extends UpdateOperationBuilder(mapper, where, beforeUpdateByHandlers, afterUpdateByHandlers) {
    // appends additional part of update query
    private[this] val c = defaultAlias.support.column.field(lockTimestampFieldName)
    addUpdateSQLPart(sqls"${c} = ${sqls.currentTimestamp}")
  }

  /**
   * Deletes a single entity by primary key and lock timestamp.
   *
   * @param id primary key
   * @param timestamp lock timestamp
   * @param s db session
   * @return deleted count
   */
  def deleteByIdAndOptionalTimestamp(id: Long, timestamp: Option[DateTime])(implicit s: DBSession = autoSession): Int = {
    deleteBy(byIdAndTimestamp(id, timestamp))
  }

  /**
   * Deletes a single entity by primary key and lock timestamp.
   *
   * @param id primary key
   * @param timestamp lock timestamp
   * @param s db session
   * @return deleted count
   */
  def deleteByIdAndTimestamp(id: Long, timestamp: DateTime)(implicit s: DBSession = autoSession): Int = {
    deleteBy(byIdAndTimestamp(id, Option(timestamp)))
  }

  override def deleteBy(where: SQLSyntax)(implicit s: DBSession = autoSession): Int = {
    val count = super.deleteBy(where)(s)
    if (count == 0) {
      throw new OptimisticLockException(
        s"Conflict ${lockTimestampFieldName} is detected (condition: '${where.value}', ${where.parameters.mkString(",")}})"
      )
    } else {
      count
    }
  }

  override def updateById(id: Id): UpdateOperationBuilder = {
    logger.info("#updateById ignore optimistic lock. If you need to lock with version in this case, use #updateBy instead.")
    super.updateBy(byId(id))
  }

  override def deleteById(id: Id)(implicit s: DBSession = autoSession): Int = {
    logger.info("#deleteById ignore optimistic lock. If you need to lock with version in this case, use #deleteBy instead.")
    super.deleteBy(byId(id))
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy