0Pricing
Scala for Backend Engineering & Functional Programming · 课时

使用 Slick/Doobie 集成数据库

学习使用 Slick 或 Doobie 等热门 Scala ORM/库,将数据库集成到 Play 应用程序中。

使用 Slick/Doobie 集成数据库 是 CoddyKit 上的免费 Scala for Backend Engineering & Functional Programming 课时。 这是第 3 节课,共 3 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Scala for Backend Engineering & Functional Programming 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Scala for Backend Engineering & Functional Programming 课程共包含 3 节课。

为什么要集成数据库?

Web 应用程序通常需要持久化存储和检索数据。这些数据可能包括用户资料、产品信息或博客文章。

  • 持久性:即使应用程序重启,数据仍然可用。
  • 可扩展性:处理大量数据和许多并发用户。
  • 可靠性:确保数据完整性,并支持数据恢复。

对于使用 Play Framework 构建的任何动态 Web 应用程序来说,集成数据库都至关重要。

Slick 与 Doobie:Scala 的数据库工具

Scala 提供了功能强大的数据库交互库,其中 Slick 和 Doobie 是两个常用选择。

  • Slick:一个函数式关系映射(FRM)库。它让您能够以类型安全、函数式的方式操作数据库,同时隐藏 SQL 的具体细节。
  • Doobie:一个纯函数式 JDBC 层。它让您能够直接控制 SQL 查询,同时与 Cats Effect 等函数式编程范式集成。

本课程将重点介绍 Slick,因为它提供了更高层次的抽象,通常能够简化与 Play Framework 的初始集成。

在 Play 中设置 Slick

首先,将 Slick 和数据库驱动程序添加到您的 build.sbt 中。为便于设置,我们将使用 H2 这一内存数据库。

然后,在 conf/application.conf 中配置数据库连接。

import play.sbt.PlayImport._

libraryDependencies ++= Seq(
  jdbc,
  ws,
  "com.typesafe.play" %% "play-slick" % "5.0.0",
  "com.typesafe.play" %% "play-slick-evolutions" % "5.0.0",
  "com.h2database" % "h2" % "2.2.224"
)

数据库配置

将以下代码行添加到 conf/application.conf,以设置 H2 数据库和 Slick 配置。slick.dbs.default.profile 指定数据库类型。

play.db.prototype.slick 定义数据库连接的模板。

slick.dbs.default.profile="slick.jdbc.H2Profile$"

play.db { 
  prototype.slick = {
    db.driver="org.h2.Driver"
    db.url="jdbc:h2:mem:play;DB_CLOSE_DELAY=-1"
    db.username="sa"
    db.password=""
  }

  # Apply the prototype to our default DB
  default = ${play.db.prototype.slick}
}

定义您的模型(Slick 表)

在 Slick 中,您可以将数据库表表示为扩展 slick.jdbc.H2Profile.api.Table 的 Scala 类。每一列都会映射到一个 Scala 类型。

我们会为每个表定义一个 TableQuery,它是执行查询的入口。

import slick.jdbc.H2Profile.api._

// Define a case class for our data model
case class User(id: Option[Long], name: String, email: String)

// Define the table mapping
class Users(tag: Tag) extends Table[User](tag, "users") {
  def id = column[Option[Long]]("id", O.PrimaryKey, O.AutoInc)
  def name = column[String]("name")
  def email = column[String]("email")
  
  // Every table needs a * projection with the default mapping
  def * = (id, name, email) <> (User.tupled, User.unapply)
}

// TableQuery for accessing the Users table
val users = TableQuery[Users]

代码:创建表和架构

此可运行示例展示了如何定义 User 表,然后在内存 H2 数据库中创建其架构。db.run 方法会异步执行数据库操作。

import slick.jdbc.H2Profile.api._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Await
import scala.concurrent.duration._

case class User(id: Option[Long], name: String, email: String)

class Users(tag: Tag) extends Table[User](tag, "users") {
  def id = column[Option[Long]]("id", O.PrimaryKey, O.AutoInc)
  def name = column[String]("name")
  def email = column[String]("email")
  def * = (id, name, email) <> (User.tupled, User.unapply)
}

object SlickDemo {
  val users = TableQuery[Users]

  def main(args: Array[String]): Unit = {
    val db = Database.forURL("jdbc:h2:mem:test1;DB_CLOSE_DELAY=-1", driver = "org.h2.Driver")
    try {
      val setup = DBIO.seq(
        users.schema.create
      )
      val future = db.run(setup)
      Await.result(future, 2.seconds)
      println("Users table created successfully!")
    } finally {
      db.close()
    }
  }
}

用于执行操作的 DAO 模式

数据访问对象(DAO)模式有助于封装数据库操作。它将应用程序的业务逻辑与持久化逻辑分离。

UserDAO 会包含用于插入、查询、更新和删除 User 对象的方法。

import slick.jdbc.H2Profile.api._
import scala.concurrent.Future

class UserDAO(db: Database) {
  import SlickDemo.users // Assuming users TableQuery is in SlickDemo object

  def insert(user: User): Future[User] = {
    db.run(users returning users.map(_.id) into ((user, id) => user.copy(id = id)) += user)
  }

  def findAll(): Future[Seq[User]] = {
    db.run(users.result)
  }

  def findById(id: Long): Future[Option[User]] = {
    db.run(users.filter(_.id === id).result.headOption)
  }
}

代码:插入和查询数据

下面介绍如何使用刚才定义的 DAO 方法插入新用户,然后从数据库中查询所有用户。

import slick.jdbc.H2Profile.api._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Await
import scala.concurrent.duration._

case class User(id: Option[Long], name: String, email: String)

class Users(tag: Tag) extends Table[User](tag, "users") {
  def id = column[Option[Long]]("id", O.PrimaryKey, O.AutoInc)
  def name = column[String]("name")
  def email = column[String]("email")
  def * = (id, name, email) <> (User.tupled, User.unapply)
}

class UserDAO(db: Database) {
  val users = TableQuery[Users]
  def insert(user: User): Future[User] = 
    db.run(users returning users.map(_.id) into ((u, id) => u.copy(id = id)) += user)
  def findAll(): Future[Seq[User]] = 
    db.run(users.result)
}

object SlickOperations {
  def main(args: Array[String]): Unit = {
    val db = Database.forURL("jdbc:h2:mem:test2;DB_CLOSE_DELAY=-1", driver = "org.h2.Driver")
    val userDAO = new UserDAO(db)
    try {
      Await.result(db.run(userDAO.users.schema.create), 2.seconds)
      println("Table created.")

      val future = for {
        user1 <- userDAO.insert(User(None, "Alice", "alice@example.com"))
        user2 <- userDAO.insert(User(None, "Bob", "bob@example.com"))
        allUsers <- userDAO.findAll()
      } yield allUsers

      val result = Await.result(future, 5.seconds)
      println(s"All users: $result")

    } finally {
      db.close()
    }
  }
}

将 DAO 集成到 Play 中

在 Play 应用程序中,通常会通过依赖注入将 UserDAO 注入控制器。这样,控制器操作就可以与数据库交互。

您需要配置 Play 的 DI(例如 Guice),以提供 Database 和 UserDAO 的实例。

import play.api.mvc._
import play.api.libs.json._
import scala.concurrent.ExecutionContext
import javax.inject._

// Assuming User and UserDAO are defined as before

@Singleton
class UserController @Inject()(cc: ControllerComponents, userDAO: UserDAO)
                              (implicit ec: ExecutionContext) extends AbstractController(cc) {

  def listUsers = Action.async {
    userDAO.findAll().map { users =>
      Ok(Json.toJson(users))
    }
  }

  def createUser = Action.async(parse.json) {
    request =>
      request.body.validate[User].map { user =>
        userDAO.insert(user).map(u => Created(Json.toJson(u)))
      }.getOrElse(Future.successful(BadRequest("Invalid User Data")))
  }
}

快速检查:Slick 表

请考虑以下 Scala 样例类和部分定义的 Slick Table 映射:

case class Product(id: Option[Int], name: String, price: Double)

假设已经定义了 id、name 和 price 列,哪一行代码能够正确补全 Product 表的 * 投影?

回顾:数据库集成

在本课程中,我们探索了如何使用 Slick 将数据库集成到 Play Framework 应用程序中。

  • 我们了解了 Slick 作为函数式关系映射(FRM)库所发挥的作用。
  • 我们介绍了如何设置 Slick 依赖项,以及如何在 application.conf 中配置数据库。
  • 您了解了如何定义 Scala 样例类,并使用 Slick 的 Table 类将其映射到数据库表。
  • 我们介绍了 DAO 模式,用于组织数据库操作,并提供了插入和查询数据的示例。
  • 最后,我们简要介绍了如何将这些 DAO 操作集成到 Play 控制器中,以提供 Web API 端点。

掌握数据库集成是构建健壮、数据驱动的 Play 应用程序的关键!

常见问题解答

「使用 Slick/Doobie 集成数据库」课时是免费的吗?

是的 — 「使用 Slick/Doobie 集成数据库」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Scala for Backend Engineering & Functional Programming 课程的其余内容,请升级到 CoddyKit PRO。 Scala for Backend Engineering & Functional Programming 课程共包含 3 节课。

「使用 Slick/Doobie 集成数据库」这节课中我会学到什么?

学习使用 Slick 或 Doobie 等热门 Scala ORM/库,将数据库集成到 Play 应用程序中。 你通过在浏览器中直接运行的动手代码来练习 Scala for Backend Engineering & Functional Programming,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Scala for Backend Engineering & Functional Programming 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Scala for Backend Engineering & Functional Programming 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 3 节。

「使用 Slick/Doobie 集成数据库」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Scala for Backend Engineering & Functional Programming 课中编写并运行代码吗?

能。每节 Scala for Backend Engineering & Functional Programming 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. Play Framework 基础
  2. 使用 Play 构建 RESTful API
  3. 使用 Slick/Doobie 集成数据库
← 返回 Scala for Backend Engineering & Functional Programming