7
votes

I have recently switched from Slick-2 to Slick-3. Everything is working very well with slick-3. However, I am having some issues when it comes to transaction. I have seen different questions and sample code in which transactionally and withPinnedSession are used to handle the transaction. But my case is slightly different. Both transcationally and withPinnedSession can be applied on Query. But what I want to do is to pass the same session to another method which will do some operations and want to wrap multiple methods in same transaction.

I have the below slick-2 code, I am not sure how this can be implemented with Slick-3.

def insertWithTransaction(row: TTable#TableElementType)(implicit session: Session) = {
      val entity = (query returning query.map(obj => obj) += row).asInstanceOf[TEntity]
      // do some operations after insert
      //eg: invoke another method for sending the notification
      entity
}

override def insert(row: TTable#TableElementType) = {
    db.withSession {
      implicit session => {
        insertWithTransaction(row)
      }
    }
}

Now, if someone is not interested in having transactions, they can just invoke the insert() method. If we need to do some transaction, it can be done by using insertWithTransaction() in db.withTransaction block.

For eg :

db.withTransaction { implicit session =>
    insertWithTransaction(row1)
    insertWithTransaction(row2)
    //check some condition, invoke session.rollback if something goes wrong
}

But with slick-3, the transactionally can be applied on query only. That means, wherever we need to do some logic centrally after insertion, it is possible. Every developer needs to manually handle those scenarios explicitly, if they are using transactions. I believe this could potentially cause errors. I am trying to abstract the whole logic in insert operation so that the implementors need to worry only about the transaction success/failure

Is there any other way, in slick-3, in which I can pass the same session to multiple methods so that everything can be done in single db session.

1

1 Answers

1
votes

You are missing something : .transactionally doesn't apply to a Query, but to a DBIOAction. Then, a DBIOAction can be composed of multiple queries by using monadic composition.

Here is a exemple coming from the documentation :

val action = (for {
  ns <- coffees.filter(_.name.startsWith("ESPRESSO")).map(_.name).result
  _ <- DBIO.seq(ns.map(n => coffees.filter(_.name === n).delete): _*)
} yield ()).transactionally

action is composed of a select query and as many delete queries as rows returned by the first query. All that creates DBIOAction that be executed in a transaction.

Then, to run the action against the database, you have to call db.run, so, like this:

val f: Future[Unit] = db.run(action)

Now, to come back to your exemple, let's say you want to apply an update query after your insert, you can create an action this way

val action = (for {
  entity <- (query returning query.map(obj => obj) += row)
  _ <- query.map(_.foo).update(newFoo)
} yield entity).transactionally

Hope it helps.