When you use Slick, you start by composing Scala queries, then get actions (like “get results” or “insert data”) associated with these queries, and finally run the actions on a database to obtain results.
This chapter explains how these core concepts relate to each other and how they fit into your application design.
The main type used by queries is
Rep[T] is a representation of a type
T that provides the necessary operations for building queries. Collection-valued queries are always of type
Query, which is a
Rep of a collection type like
Rep[Seq[Int]]. Queries can be composed from representations of database tables (
TableQuery), literal values and parameters. Query composition does not require a database or execute any part of the query. It only builds a description of what to execute at a later point.
Operations that can be executed on a database are called database I/O actions (
DBIOAction). Several operations on queries and tables create I/O actions, for example
myQuery += data or
myTable.schema.create. Actions can be composed with combinators like
Just like a query, an I/O action is only a description of an operation. Creating or composing actions does not execute anything on a database. Combined actions always consist of strictly linear sequences of other actions. Parts of an action never run concurrently.
As an alternative to Scala queries you can write queries and other database statements in SQL. This is done with string interpolators, for example
sql"select id from mytable".as[Int] or
sqlu"insert into mytable (id) values (1)". These interpolators (in the case of
sql with an extra
.as call) all produce database I/O actions.
Database object encapsulates the resources that are required to connect to a specific database. This can be just a number of connection parameters but in most cases it includes a connection pool and a thread pool. You should usually create a single
Database object when your application starts and shut it down when your application shuts down to ensure that all resources are released.
Any action can be run on a database to obtain the results (or perform side effects such as updating the database). Execution is always asynchronous, i.e. it does not block the caller thread. Any kind of action can be run to obtain a
Future that is eventually completed with a result when the execution is finished (
myDatabase.run(myAction)). Actions that produce a sequence of values usually support streaming results as well. Such an action can be combined with a database to produce a Reactive Streams
myDatabase.stream(myAction)). The action is executed when a consumer subscribes to the Publisher.
Even when using a standard interface for database drivers like JDBC there are many differences between databases in the SQL dialect they understand, the way they encode data types, or other idiosyncracies. Slick abstracts over these differences with profiles. Whenever you write queries (whether in Scala or SQL) or produce other database actions, you need a concrete profile for your database. Usually these profiles extend the abstract
Database objects are interchangeable between all subtypes of JdbcProfile but they are usually configured together with the profile because you need to pick the correct profile for the database.