Anything that you can execute on a database, whether it is a getting the result of a query
myQuery.result), creating a table (
myTable.schema.create), inserting data
myTable += item) or something else, is an instance of
DBIOAction, parameterized by the result type it will produce when you execute it.
Database I/O Actions can be combined with several different combinators (see the
DBIOAction class and DBIOAction object, which is also
available under the alias
DBIO, for details), but they will always be executed strictly sequentially and (at least
conceptually) in a single database session.
DBIOActions can be executed either with the goal of producing a fully materialized result or streaming
data back from the database.
You can use
run to execute a
DBIOAction on a Database and produce a materialized result. This can
be, for example, a scalar query result (
myTable.length.result), a collection-valued query
myTable.to[Set].result), or any other action. Every
DBIOAction supports this mode of
Execution of the action starts in the background when
run is called. The calling thread is not blocked. The
materialized result is returned as a Future which is completed asynchronously as soon as the result
Collection-valued queries also support streaming results. In this case, the actual collection type
is ignored and elements are streamed directly from the result set through a Reactive Streams
Publisher, which can be processed and consumed by Akka Streams.
Execution of the
DBIOAction does not start until a
Subscriber is attached to the stream. If multiple
Subscribers subscribe to the same
Publisher, each one triggers an independent execution of the
Stream elements are signaled as soon as they become available in the streaming part of the
DBIOAction. The end of
the stream is signaled only after the entire action has completed. For example, when streaming inside a transaction
and all elements have been delivered successfully, the stream can still fail afterwards if the transaction cannot be
Subscriber is notififed of this failure.
When streaming a JDBC result set, the next result page will be buffered in the background if the
Subscriber is not ready to receive more data, but all elements are signaled synchronously and the
result set is not advanced before synchronous processing is finished. This allows synchronous
callbacks to low-level JDBC values like
Blob which depend on the state of the result set. The
mapResult is provided for this purpose:
Note: Some database systems may require session parameters to be set in a certain way to support streaming without caching all data at once in memory on the client side. For example, PostgreSQL requires both
.withStatementParameters(rsType = ResultSetType.ForwardOnly, rsConcurrency = ResultSetConcurrency.ReadOnly, fetchSize = n)(with the desired page size
.transactionallyfor proper streaming.
DBIOActions describe sequences of individual actions to execute in strictly sequential order on
one database session (at least conceptually), therefore the most commonly used combinators deal with
sequencing. Since a
DBIOAction eventually results in a
Failure, its combinators,
just like the ones on
Future, have to distinguish between successful and failed executions. Unless
specifically noted, all combinators only apply to successful actions. Any failure will abort the
sequence of execution and result in a failed
Future or Reactive Stream.
The available DBIO combinators are a purely functional subset of Future combinators. You should be familiar with working with Scala Futures before diving into DBIO combinators. Since the result of a database action is usually a Future, this knowledge is required anyway for composing database results and other asynchronous code.
The simplest combinator is DBIO.seq
which takes a varargs list of actions to run in sequence, discarding their return value. If you
need the return value, you can use andThen
to combine two actions and keep the result of the second one. If you need both return values of two
actions, there is the zip
combinator. For getting all result values from a sequence of actions (of compatible types), use
All these combinators work with pre-existing
DBIOActions which are composed eagerly:
If an action depends on a previous action in the sequence, you have to compute it on the fly with
These two methods plus filter
enable the use of for comprehensions for action sequencing. Since they take function
arguments, they also require an implicit
ExecutionContext on which to run the function. This
way Slick ensures that no non-database code is run on the database thread pool. This ExecutionContext should be
provided by your application or framework (e.g. Akka or Play).
Note: You should prefer the less flexible methods without an
ExecutionContextwhere possible. The resulting actions can be executed more efficiently.
You can use andFinally
to perform a cleanup action, no matter whether the previous action succeeded or failed. This is similar to using
try ... finally ... in imperative Scala code. A more flexible version of
It lets you transform the failure and decide how to fail the resulting action if both the original
one and the cleanup failed.
You can convert a
Future into an action with DBIO.from.
This allows the result of the
Future to be used in an action sequence. A pre-existing value or
failure can be converted with DBIO.successful
and DBIO.failed, respectively.
When executing a
DBIOAction which is composed of several smaller actions, Slick acquires sessions from
the connection pool and releases them again as needed so that a session is not kept in use
unnecessarily while waiting for the result of a non-database computation (e.g. the function passed to
that determines the next action to run). You can use withPinnedSession to
force the use of a single session, keeping the existing session open even when waiting for non-database computations.
All DBIOAction combinators which combine database actions without any non-database
computations in between (e.g.
applied to two database computations) can fuse these actions for more efficient execution, with the side-effect that
the fused action runs inside a single session, even without
There is a related combinator called
to force the use of a transaction. This guarantees that the entire
DBIOAction that is executed will
either succeed or fail atomically. Without it, all database actions run in auto-commit mode. The use of a transaction
always implies a pinned session.
Warning: Failure is not guaranteed to be atomic at the level of an individual
DBIOActionthat is wrapped with
transactionally, so you need to be careful where you apply error recovery combinators. An actual database transaction is only created and committed or rolled back for the outermost
transactionallyactions simply execute inside the existing transaction without additional savepoints.
In case you want to force a rollback, you can return
DBIO.failed within a
In order to drop down to the JDBC level for functionality that is not available in Slick, you can
SimpleDBIO action which is run on a database thread and gets access to the JDBC
If you need to access state of the database session across multiple
SimpleDBIO actions, make sure to
transactionally accordingly (see above).