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


原始标题:Kotlin Contracts