Reputation: 469
I have a controller that makes various calls to database model classes that perform various operations on a PostgreSQL database. I want to perform all of these operations inside a single transaction. In Play 2.3 I was able to wrap code in DB.withTransaction { ... }
to get it to perform all actions inside a single transaction.
With Play 2.4 and Slick 3.0 it appears that Slick handles connection management, and DB.withTransaction
no longer seems the correct way to handle this (it throws IllegalArgumentException
with the message "Could not find database for default" anyway). What's the proper way to group Slick operations inside a transaction from within the controller?
Upvotes: 1
Views: 851
Reputation: 7877
The solution of your problem is DBIO composition.
Firstly, all your atomic operations should be implemented by a DBIOAction
(A DBIOAction
represents an operation which will be executed on the DB).
Next, you can compose all your DBIOAction together, with andThen
, flatMap
, or using a for comprehension
(see DBIOAction api)
When you have your composed DBIO, you can call .transactionally
to make the DBIOAction transactional when it will be executed on a db.
Sample:
def updateUsername(id: Long, username: String) = Schemas.users.filter(_.id === id).map(_.username).update(username)
def updateUserAddress(id: Long, address: String) = Schemas.addresses.filter(_.id === id).map(_.address).update(address)
def getUser(id: Long) = Schemas.users.filter(_.id === id).head
val operations = for {
_ <- updateUserName(1, "foo")
_ <- updateUserAddress(1, "bar")
user <- getUser(1)
} yield user
db.run(operations.transactionally)
The way to obtain the db
object is explained in the Play documentation.
Upvotes: 2