Slick 是 Scala 生态中一个将数据库访问“集合化”的框架。它并非传统意义上的 ORM,但通过类型安全的查询构造,让开发者可以像操作 Scala 集合一样操作 PostgreSQL 数据表,同时保留 SQL 的表达能力。使用 Slick 连接 PostgreSQL 时,项目需要同时引入 Slick 核心库、HikariCP 连接池以及 PostgreSQL JDBC 驱动,并通过配置文件统一管理连接参数。下面从零开始,搭建一个可运行的 Slick + PostgreSQL 应用。

一、添加依赖与连接配置
在 Scala 项目的 build.sbt 文件中,需要添加以下依赖。Slick 的版本与 Scala 版本有对应关系,例如 Slick 3.4.x 支持 Scala 2.13,而 Scala 3 用户可以选择 Slick 3.5.x。除了 slick 核心包,还需要 slick-hikaricp 来提供连接池支持,以及 postgresql 驱动。完整的依赖片段如下:
libraryDependencies ++= Seq( "com.typesafe.slick" %% "slick" % "3.4.1", "com.typesafe.slick" %% "slick-hikaricp" % "3.4.1", "org.postgresql" % "postgresql" % "42.7.3", "org.slf4j" % "slf4j-nop" % "2.0.13" )
这里的 %% 会自动追加 Scala 版本后缀,确保依赖与当前项目二进制兼容。版本号可以根据实际需要调整,但要注意 JDBC 驱动版本必须与 PostgreSQL 服务器版本匹配,否则在使用某些新特性时可能出现协议不兼容。
数据库连接参数可以写在 application.conf 中,Slick 可以通过 Typesafe Config 读取。典型的配置项包括 JDBC URL、用户名、密码以及连接池大小。下面是一个适合本地开发的例子:
mydb = {
driver = "slick.jdbc.PostgresProfile$"
db {
url = "jdbc:postgresql://127.0.0.1:5432/mydatabase"
user = "postgres"
password = "secret"
connectionPool = "HikariCP"
numThreads = 10
maxConnections = 10
minConnections = 2
}
}
注意 driver 的值需要指向 PostgreSQL 对应的 Slick profile 类。如果写成 MySQL 的 profile,后续查询生成器会用错误的方言。加载配置后,可以通过 Database.forConfig("mydb") 获取数据库对象,并在应用关闭时调用 db.close() 释放资源。
二、表映射与基础查询
在 Slick 中,每张表对应一个继承自 Table 的类,列通过 column 方法定义,并附带数据类型。PostgreSQL 的常用类型如 VARCHAR、INTEGER、TIMESTAMP 都可以直接对应到 Scala 的基本类型。下面是一个 users 表的映射示例:
import slick.jdbc.PostgresProfile.api._
import java.time.LocalDateTime
class Users(tag: Tag) extends Table[(Long, String, String, LocalDateTime)](tag, "users") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def email = column[String]("email")
def createdAt = column[LocalDateTime]("created_at")
def * = (id, name, email, createdAt)
}
val users = TableQuery[Users]
这里的 * 方法是默认投影,将四列映射为一个四元组。如果后续需要更结构化的映射,可以定义 case class 并使用 mapTo 或自定义 ProvenShape。基础查询可以直接调用 filter、map、sortBy 等方法,这些方法与 Scala 集合的 API 高度相似,但底层会生成对应的 SQL SELECT 语句。
例如,查询所有 email 以 @ipipp.com 结尾的用户,并按创建时间倒序排列,可以这样写:
val query = users.filter(_.email.endsWith("@ipipp.com"))
.sortBy(_.createdAt.desc)
.map(u => (u.id, u.name))
val result: Future[Seq[(Long, String)]] = db.run(query.result)
需要注意的是,filter 中的条件使用的是列对象的扩展方法,例如 endsWith 会被翻译成 SQL 的 LIKE 表达式,而不是在客户端做字符串处理。这正是 Slick 类型安全设计的体现:任何不存在的列或类型不匹配的操作都会在编译阶段报错。
执行查询必须通过 db.run,它返回一个 Future,因此整个流程是非阻塞的。如果同步环境下需要获取结果,可以使用 Await.result,但应避免在异步上下文中阻塞线程。
三、事务与 DBIO 组合
Slick 的所有操作都被抽象为 DBIOAction,例如 query.result 返回的是 DBIO[Seq[R]],而不是直接执行。这种设计允许我们将多个数据库操作组合成一个动作序列,再统一发送给数据库执行。组合操作可以使用 for 推导式,非常直观。
例如一个创建用户并同时插入一条审计日志的场景,两个插入需要保持原子性。如果第二个插入失败,第一个插入也必须回滚。使用 transactionally 可以将整个组合动作放入一个事务:
val createUserWithLog = (for {
userId <- (users returning users.map(_.id)) += (0L, "张三", "zhangsan@ipipp.com", LocalDateTime.now())
_ <- auditLogs += (0L, userId, "用户创建")
} yield userId).transactionally
val newUserId: Future[Long] = db.run(createUserWithLog)
这里 += 是 Slick 的插入语法,returning 用于获取 AutoInc 主键。组合后的 DBIO 在执行时会被翻译成一个事务内的多条 SQL 语句。如果数据库连接支持事务,transactionally 能保证整体成功或失败,极大简化了应用层的回滚逻辑。
除了 for 推导式,Slick 还提供了 DBIO.seq、DBIO.sequence 等辅助方法,用于批量执行或按顺序执行多个动作。值得注意的是,组合并不会立即执行,必须通过 db.run 触发。
四、PostgreSQL 特有类型与优化
PostgreSQL 支持许多传统关系型数据库不具备的类型,如 JSONB、ARRAY、UUID、ENUM 等。Slick 对其中一部分类型有原生支持,但有些需要自定义 ColumnType 来映射。以 JSONB 为例,假设我们希望在 users 表中存储一个 metadata 字段,类型为 Map[String, String],可以通过继承 JdbcType 并注册隐式转换来实现。
import java.sql.{PreparedStatement, ResultSet}
import slick.jdbc.PostgresProfile.api._
import io.circe.parser._
import io.circe.syntax._
implicit val jsonbColumnType: BaseColumnType[Map[String, String]] =
MappedColumnType.base[Map[String, String], String](
map => map.asJson.noSpaces,
str => parse(str).getOrElse(throw new Exception("非法JSON")).as[Map[String, String]].getOrElse(Map.empty)
)
这个转换会在写入时将 Map 序列化为 JSON 字符串,读取时再解析回来。但更高效的做法是使用 PostgreSQL 的原生 JSONB 类型,而不是 TEXT,这需要自定义 JdbcType 并设置 sqlType 为 java.sql.Types.OTHER。不过在不追求极致性能的场景下,上述映射已经能够满足大多数需求。
性能优化方面,Slick 生成的 SQL 通常会复用预处理语句,但需要注意 inSet 和 inSetBind 的区别。inSet 会根据元素数量动态生成 IN 子句,如果元素很多可能导致 SQL 过长;而 inSetBind 使用参数绑定,更适合大量值的情况。另外,批量插入可以使用 ++= 操作符,Slick 会生成一条多值 INSERT 语句,显著减少网络往返。
val batchInsert: DBIO[Option[Int]] = users ++= Seq( (0L, "李四", "lisi@ipipp.com", LocalDateTime.now()), (0L, "王五", "wangwu@ipipp.com", LocalDateTime.now()) )
最后需要提醒的是,Slick 虽然提供了接近 SQL 的表达能力,但它并不隐藏底层执行计划。当遇到复杂查询时,建议开启 PostgreSQL 的 EXPLAIN 日志,结合 Slick 生成的 SQL 分析索引使用情况。对于连接池,HikariCP 的默认参数在大多数场景下表现良好,但要根据并发量和数据库连接数上限合理调整 maxConnections,避免连接耗尽。
PostgreSQLScalaSlick修改时间:2026-09-21 02:01:59