1. 引言
Slick 是一个用于 Scala 的 函数式关系映射(FRM) 库,它允许我们像操作普通的 Scala 集合一样来访问和查询数据库。这意味着我们可以用 Scala 编写数据库查询语句,而不是手写 SQL,从而实现类型安全的查询。
在这篇文章中,我们将一起看看如何通过 Slick 连接数据库并执行常见的查询操作。
2. Slick 的优势 ✅
Slick 是 Scala 生态中最流行的数据库访问库之一。它的主要优势包括:
- 编译时类型安全:避免运行时错误。
- 组合性好:可以轻松地将多个查询组合起来。
- 默认异步非阻塞:符合现代响应式编程风格。
- 支持 Reactive Streams 规范:便于与其他响应式组件集成。
- 支持多种主流数据库:PostgreSQL、MySQL、Oracle、MS SQL Server 等。
- 可以编写原生 SQL 查询,并以统一方式执行。
3. 添加依赖项
在项目中引入 Slick 最简单的方式是通过 sbt 添加依赖。比如添加 Slick 3.5.0 版本:
libraryDependencies += "com.typesafe.slick" %% "slick" % "3.5.0"
为了简化演示,我们这里使用 H2 内存数据库:
libraryDependencies += "com.h2database" % "h2" % "2.1.220"
如果你需要连接其他数据库,比如 PostgreSQL,记得加上对应 JDBC 驱动:
libraryDependencies += "org.postgresql" % "postgresql" % "42.2.14"
4. 连接数据库
4.1. 提供数据库配置
通常我们在 application.conf
文件中定义数据库配置信息。例如,H2 内存数据库的配置如下:
h2mem {
url = "jdbc:h2:mem:testDB"
driver = org.h2.Driver
keepAliveConnection = true
connectionPool = disabled
}
如果要连接 PostgreSQL 数据库,可以这样配置:
postgres {
dataSourceClass = "org.postgresql.ds.PGSimpleDataSource"
properties = {
serverName = "localhost"
portNumber = "5432"
databaseName = "slick-tutorial"
user = "postgres"
password = "admin"
}
}
4.2. 建立连接
配置完成后,就可以建立数据库连接了。首先导入对应数据库的 API,比如 H2:
import slick.jdbc.H2Profile.api._
然后使用 Database.forConfig
方法创建数据库连接对象:
val db = Database.forConfig("h2mem")
这个 db
实例将用于执行后续所有数据库操作。
5. 定义 Schema(表结构)
在执行任何查询之前,我们需要定义表结构。Slick 使用 Table 类来映射数据库表到 Scala 类型。
假设我们要管理网球选手信息,可以用如下 case class 表示:
case class Player(id: Long, name: String, country: String, dob: Option[LocalDate])
接着创建对应的 Table Schema:
class PlayerTable(tag: Tag) extends Table[Player](tag, None, "Player") {
override def * = (id, name, country, dob).mapTo[Player]
val id : Rep[Long] = column[Long]("PlayerId", O.AutoInc, O.PrimaryKey)
val name: Rep[String] = column[String]("Name")
val country : Rep[String] = column[String]("Country")
val dob : Rep[Option[LocalDate]] = column[Option[LocalDate]]("Dob")
}
其中:
PlayerTable
继承自 Slick 的Table
类。- 构造函数中的
"Player"
是实际数据库表名。 *
方法定义了如何将数据库字段映射为Player
实例。- 每个字段都用
column[T]
定义,并指定列名及约束(如主键、自增等)。
6. 执行数据库操作
现在我们已经准备好进行各种数据库操作了。
6.1. 查询数据
先创建一个 TableQuery
实例:
val playerTable = TableQuery[PlayerTable]
查询所有来自德国的球员:
val germanPlayersQuery = playerTable.filter(_.country === "Germany")
这相当于下面这条 SQL:
SELECT "PlayerId", "Name", "Country", "Dob" FROM "Player" WHERE "Country" = 'Germany'
因为 Slick 是异步的,所以需要调用 .result
并传给 db.run()
来获取结果:
val germanPlayers: Future[Seq[Player]] = db.run[Seq[Player]](germanPlayersQuery.result)
⚠️ 注意:germanPlayersQuery
是一个 Query 对象,必须调用 .result
才能转为 DBIOAction 才能执行。
Slick 提供了丰富的操作符,如 drop
, take
, groupBy
等,可用于更复杂的查询逻辑。
6.2. 插入数据
插入单条记录:
val insertPlayerQuery = playerTable += player
val insertResult: Future[Int] = db.run(insertPlayerQuery)
其中 +=
会忽略自增字段。如果想强制插入 ID,可以使用 forceInsert
:
val forceInsertAction = playerTable.forceInsert(player)
批量插入:
val insertMultipleAction = playerTable ++= Seq(player)
6.3. 更新数据
更新某个字段:
val updateCountryAction = playerTable.filter(_.id === 500L).map(_.country).update("Germany")
批量更新:
val updateMultipleAction = playerTable.filter(_.country === "Swiss").map(_.country).update("Switzerland")
6.4. 删除数据
删除指定记录:
val deleteAction = playerTable.filter(_.name === "Nadal").delete
6.5. 组合多个查询
Slick 支持将多个操作组合成一个动作:
val insertAction = playerTable += player1
val insertAnotherAction = playerTable += player2
val updateAction = playerTable
.filter(_.name === "Federer")
.map(_.country)
.update("Swiss")
val combinedAction = DBIO.seq(insertAction, updateAction, insertAnotherAction)
然后统一执行:
db.run(combinedAction)
6.6. 事务执行
多个操作最好放在一个事务中执行,以保证原子性:
val transactionStatus: Future[Unit] = db.run(combinedAction.transactionally)
如果不加 .transactionally
,每个操作会在独立事务中执行。
7. 使用原生 SQL 查询
虽然 Slick 提供了强大的 DSL,但在某些场景下你可能还是想直接写 SQL。
Slick 提供了三种插值器:
sql
:用于返回结果集的查询sqlu
:用于更新/插入/删除操作tsql
:编译期验证的 SQL(需额外配置)
示例:创建表
val createQuery: DBIO[Int] = sqlu"""
CREATE TABLE "Player" (
"PlayerId" SERIAL PRIMARY KEY,
"Name" VARCHAR NOT NULL,
"Country" VARCHAR NOT NULL,
"Dob" DATE
)
"""
查询数据:
val selectCountryAction: DBIO[Seq[String]] =
sql"""select "name" from "Player" where "country" = 'Spain' """.as[String]
8. 总结
本文介绍了 Slick 的基本用法,包括连接数据库、定义 schema、执行 CRUD 操作以及事务控制等内容。作为 Scala 生态中强大的 FRM 工具,Slick 在类型安全和组合性方面表现优秀。
相关代码示例可以在 GitHub 上找到:Baeldung Scala Tutorials