Transaction


#readOnly block / session


This code block executes queries in read-only mode. This means that no update/execute operations are allowed.

val names = DB readOnly { implicit session => sql"select name from emp".map { rs => rs.string("name") }.list.apply() } // Alternatively, you can use a session variable this way: implicit val session = DB.readOnlySession try { val names = sql"select name from emp".map { rs => rs.string("name") }.list.apply() // do something } finally { session.close() } 

If you run an update operation within read-only mode, the code throws java.sql.SQLException:

DB readOnly { implicit session => sql"update emp set name = ${name} where id = ${id}".update.apply() } // will throw java.sql.SQLException 

#autoCommit block / session


This code block executes queries / update operations in auto-commit mode.

val count = DB autoCommit { implicit session => sql"update emp set name = ${name} where id = ${id}".update.apply() } 

When using autoCommitSession, an operation will be executed in auto-commit mode too.

implicit val session = DB.autoCommitSession try { sql"update emp set name = ${name1} where id = ${id1}".update.apply() // auto-commit sql"update emp set name = ${name2} where id = ${id2}".update.apply() // auto-commit } finally { session.close() } 

#localTx block


This code block executes queries / update operations in block-scoped transactions. When an Exception was thrown within the block, the ongoing transaction will be cancelled and then the transaction will be rolled back automatically.

val count = DB localTx { implicit session => // --- transcation scope start --- sql"update emp set name = ${name1} where id = ${id1}".update.apply() sql"update emp set name = ${name2} where id = ${id2}".update.apply() // --- transaction scope end --- } 

The TxBoundary class provides a differnt transaction boundary beyond throwing an Exception (This feature is available since version 2.2.0):

import scalikejdbc._ import scala.util.Try import scalikejdbc.TxBoundary.Try._ val result: Try[Result] = DB localTx { implicit session => Try { doSomeStaff() } } // localTx rolls back when `result` is `Failure` // https://www.scala-lang.org/api/current/scala/util/Try.html 

The Built-in type class instances are Try, Either and Future. You can use them by having import scalikejdbc.TxBoundary,***._ in your code.


#futureLocalTx block


The futureLocalTx block uses Future‘s state as the transaction boundary. When any of the Future operations fails, the transaction will be rolled back automatically.

object FutureDB { implicit val ec = myOwnExecutorContext def updateFirstName(id: Int, firstName: String)(implicit session: DBSession): Future[Int] = { Future { blocking { session.update("update users set first_name = ? where id = ?", firstName, id) } } } def updateLastName(id: Int, lastName: String)(implicit session: DBSession): Future[Int] = { Future { blocking { session.update("update users set last_name = ? where id = ?", lastName, id) } } } } object Example { import FutureDB._ val fResult = DB futureLocalTx { implicit s => updateFirstName(3, "John").flatMap(_ => updateLastName(3, "Smith")) } } Example.fResult.foreach(println(_)) // #=> 1 

Alternatively, you can use TxBoundary[Future[A]] too:

import scalikejdbc.TxBoundary.Future._ val fResult = DB localTx { implicit s => updateFirstName(3, "John").flatMap(_ => updateLastName(3, "Smith")) } 

Working with IO monads


This section guides you on how to implement a transaction boundary for IO monads.


IO monad minimal example


Let’s say you have a custom IO monad called MyIO:

sealed abstract class MyIO[+A] { import MyIO._ def flatMap[B](f: A => MyIO[B]): MyIO[B] = { this match { case Delay(thunk) => Delay(() => f(thunk()).run()) } } def map[B](f: A => B): MyIO[B] = flatMap(x => MyIO(f(x))) def run(): A = { this match { case Delay(f) => f.apply() } } def attempt: MyIO[Either[Throwable, A]] = MyIO(try { Right(run()) } catch { case scala.util.control.NonFatal(t) => Left(t) }) } object MyIO { def apply[A](a: => A): MyIO[A] = Delay(() => a) final case class Delay[+A](thunk: () => A) extends MyIO[A] } 

Here is an example of TxBoundary typeclass instance for MyIO[A] type:

import scalikejdbc._ implicit def myIOTxBoundary[A]: TxBoundary[MyIO[A]] = new TxBoundary[MyIO[A]] { def finishTx(result: MyIO[A], tx: Tx): MyIO[A] = { result.attempt.flatMap { case Right(a) => MyIO(tx.commit()).flatMap(_ => MyIO(a)) case Left(e) => MyIO(tx.rollback()).flatMap(_ => MyIO(throw e)) } } override def closeConnection(result: MyIO[A], doClose: () => Unit): MyIO[A] = { for { x <- result.attempt _ <- MyIO(doClose()) a <- MyIO(x.fold(throw _, identity)) } yield a } } 

Woth the above custom TxBoundary, you can use localTx code blocks in a simple way sa below:

import scalikejdbc._ type A = ??? def asyncExecution: DBSession => MyIO[A] = ??? // default DB.localTx(asyncExecution)(boundary = myIOTxBoundary) // named NamedDB("named").localTx(asyncExecution)(boundary = myIOTxBoundary) 

Cats Effect IO example


This section guides on how to define your own custom TxBoundary typeclass instance for cats.effect.IO[A].

cats.effect.IO offers two ways to handle completion/cancellation as below. You can use guaranteeCase for commit/rollback operations while going with guarantee for connection closure.

import scalikejdbc._ import cats.effect._ implicit def catsEffectIOTxBoundary[A]: TxBoundary[IO[A]] = new TxBoundary[IO[A]] { def finishTx(result: IO[A], tx: Tx): IO[A] = result.guaranteeCase { case ExitCase.Completed => IO(tx.commit()) case _ => IO(tx.rollback()) } override def closeConnection(result: IO[A], doClose: () => Unit): IO[A] = result.guarantee(IO(doClose())) } 

To take control of all the side-effects that happen within a localTx code block, you can use suspend method to wrap the code blocks using localTx:

import scalikejdbc._ import cats.effect._ type A = ??? def ioExecution: DBSession => IO[A] = ??? // default IO.suspend { DB.localTx(ioExecution)(boundary = catsEffectIOTxBoundary) } // named IO.suspend { NamedDB("named").localTx(ioExecution)(boundary = catsEffectIOTxBoundary) } 

#withinTx block / session


This code block joins an exsting transcation and executes queries / update operations with it.

In this case, your code is responsible to manage all transactional operations (such as Tx#begin(), Tx#rollback() or Tx#commit()). ScalikeJDBC never does anything under the hood.

val db = DB(conn) try { db.begin() val names = db withinTx { implicit session => // if a transaction has not been started, IllegalStateException will be thrown sql"select name from emp".map { rs => rs.string("name") }.list.apply() } db.rollback() // it might throw Exception } finally { db.close() } val db = DB(conn) try { db.begin() implicit val session = db.withinTxSession() val names = sql"select name from emp".map { rs => rs.string("name") }.list.apply() db.rollbackIfActive() // it NEVER throws Exception } finally { db.close() } 

If this webpage has a typo or something wrong, Please report or fix it. How?