15

I'm confused by the way the slick 3 documentation describes transactions. I have slick 2 code that looks like this:

def doSomething(???) = DB.withTransaction { implicit session => 
    userDao.doSomething(???)
    addressDao.doSomething(???)
    contactDao.doSomething(???)
}

How can i span a transaction in slick 3?

Bomgar
  • 553
  • 1
  • 3
  • 13

2 Answers2

17

Please have a look at the documentation here http://slick.typesafe.com/doc/3.0.0/dbio.html#transactions-and-pinned-sessions

The idea is that you wrap a sequence of IO operations into a transactionally like shown in this example:

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

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

This way Slick still process all the operations reactively, but it runs them all in one transaction sequentially.

So your example would look like this:

def doSomething(???) = (for {
  _ <- userDao.doSomething(???)
  _ <- addressDao.doSomething(???)
  _ <- contactDao.doSomething(???)
} yield()).transactionally
Gregor Raýman
  • 3,051
  • 13
  • 19
  • 7
    sad that i have to deal with action concatenation on that abstraction level. – Bomgar May 19 '15 at 14:22
  • 2
    also that i have to "run" it on that abstraction layer is annoying – Bomgar May 19 '15 at 14:26
  • Well, it's not really an abstraction level, it is just chaining of futures. Note that the 3 lines in the `for` are not executed in the same thread. I think this is the price to pay for the reactive DB access. If you want have asynchronous non blocking functionality, then working with futures and co. is the natural way to do it. – Gregor Raýman May 19 '15 at 15:18
  • 3
    the db.run part is what annoys me. Also the return values are not "normal" futures. – Bomgar May 20 '15 at 13:38
  • In what way are they nor normal? – Gregor Raýman May 20 '15 at 13:52
  • Ah, I see. But is is not the futures that have to be executed manually. The `db.run` creates the future, which is then executed like any other future. The `a` in the example is not a future it a `DBIOAction`. – Gregor Raýman May 21 '15 at 10:20
  • @GregorRayman What should the three dao functions return in this example? I guess it doesn't work if they return Future[SomeResultClass]? Or in other words, I guess the dao functions should not execute db.run(...)? – ulejon Aug 02 '15 at 07:31
  • They should return `DBIOAction` – Gregor Raýman Aug 03 '15 at 13:30
  • 3
    @GregorRayman ok, that's what I thought. Then my next question is: what is the "best" way to design a dao in slick 3? Should all dao functions return a DBIOAction, and when you need the actual result from a dao function you run db.run(someDao.someFunction())? – ulejon Aug 03 '15 at 15:56
  • 7
    I find the function scope to be a natural intuitive way to scope transactions .. this new Action based API is complex, unreadable and unnatural, I think they regressed with those poor design decisions. – SkyWalker Oct 09 '15 at 08:01
  • Another question came to mind - what is the default transaction isolation level for actions run with transactionally function? – Artemis Jun 07 '16 at 14:14
  • 2
    This seems problematic when you need to get an intermediate result and remain inside a transaction. For example, if you have an if-statement inside the transaction. – Charles Capps Dec 02 '17 at 00:52
  • 3
    a common scenario is for one these chained actions to be an insert and a subsequent action updates its recored to include the inserts primary key as a foreign key in its table. How do you pull the value of the new generated id from the one before you execute the other and still keep it all transactional? – Andrew Norman Apr 23 '18 at 23:34
15
val dbAction = (
  for {
    user <- userTable.doSomething
    address <- addressTable.doSomething
    contact <- contactTable.doSomething
  } yield()
).transactionally

val resultFuture = db run dbAction

You just need to wrap your action into 'transactionally'. Slick would take care of running all wrapped DB actions as a transaction.

Apart from the standard advantages of having a more reactive/functional/async way of writing code, it allows couple of performance improvements. Like it can determine at runtime if multiple actions can use the same session or not. In Slick 2.0, whenever you use 'withTransaction' or 'withSession' that opens a new jdbc session while here it has potential to reuse the same.

panther
  • 767
  • 5
  • 21