52周技术系列之Slick 3:面向初学者的函数式关系映射(上)
引言
在现代应用开发中,与数据库交互是不可或缺的一部分。传统方式中,我们通常直接编写SQL语句或使用ORM框架。今天我们要介绍的Slick,为Scala开发者提供了一种全新的数据库交互方式——函数式关系映射(FRM)。
Slick概述
Slick(Scala Language-Integrated Connection Kit)是Scala生态中一个强大的关系数据库访问库。与传统的ORM不同,Slick基于函数式编程理念设计,它不会将数据库隐藏在ORM层之后,而是让你能够像操作Scala集合一样操作数据库。
Slick的核心特点
- 类型安全:所有查询在编译时进行类型检查
- 多数据库支持:同一套代码可生成不同数据库的SQL
- 组合性:查询可以像乐高积木一样组合
- 异步设计:天然支持响应式编程
- 流式处理:内置响应式流支持
- 原生SQL支持:必要时可以直接使用SQL
项目搭建
首先创建一个基本的SBT项目,添加以下依赖:
libraryDependencies += "com.typesafe.slick" %% "slick" % "3.1.1"
libraryDependencies += "com.h2database" % "h2" % "1.4.191"
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.1.3"
libraryDependencies += "org.scalatest" %% "scalatest" % "2.2.6" % "test"
这里我们使用H2作为内存数据库,适合开发和测试环境。
数据模型定义
案例类定义
我们定义一个简单的任务管理模型:
case class Task(
title: String,
description: String = "",
createdAt: LocalDateTime = LocalDateTime.now(),
dueBy: LocalDateTime,
tags: Set[String] = Set(),
id: Long = 0L
)
这个案例类包含了任务的基本属性,其中一些字段有默认值。
表定义
Slick使用Table类来映射数据库表:
class TaskTable(tag: Tag) extends Table[Task](tag, "tasks") {
def title = column[String]("title")
def description = column[String]("description")
def createdAt = column[LocalDateTime]("createdAt")
def dueBy = column[LocalDateTime]("dueBy")
def tags = column[Set[String]]("tags")
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
override def * = (title, description, createdAt, dueBy, tags, id) <> (Task.tupled, Task.unapply)
}
关键点说明:
Table
需要指定存储的类型参数和表名- 每个列定义对应数据库表中的一列
*
方法定义了默认投影,将元组与案例类相互转换id
列被定义为自增主键
自定义类型映射
Slick默认不支持Java 8的LocalDateTime和Set[String]类型,我们需要自定义映射:
implicit val localDateTimeColumnType = MappedColumnType.base[LocalDateTime, Timestamp](
ldt => Timestamp.valueOf(ldt),
t => t.toLocalDateTime
)
implicit val setStringColumnType = MappedColumnType.base[Set[String], String](
tags => tags.mkString(","),
tagsString => tagsString.split(",").toSet
)
数据库操作
创建表
定义创建表的Action:
lazy val Tasks = TableQuery[TaskTable]
val createTaskTableAction = Tasks.schema.create
Action定义后需要通过Database实例执行:
val db = Database.forConfig("taskydb")
val result = Await.result(db.run(DataModel.createTaskTableAction), 2 seconds)
插入数据
插入操作使用++=
方法:
def insertTaskAction(tasks: Task*) = Tasks ++= tasks.toSeq
测试用例:
it("should insert single task") {
val result = Await.result(db.run(
DataModel.insertTaskAction(Task(title = "Learn Slick", dueBy = LocalDateTime.now().plusDays(1)))
), 2 seconds)
result should be(Some(1))
}
查询数据
查询所有任务:
val listTasksAction = Tasks.result
测试用例:
it("should list all tasks") {
val tasks = Seq(
Task(title = "Learn Slick", dueBy = LocalDateTime.now().plusDays(1)),
Task(title = "Write blog on Slick", dueBy = LocalDateTime.now().plusDays(2))
)
Await.result(db.run(DataModel.insertTaskAction(tasks: _*)), 2 seconds)
val result = Await.result(db.run(DataModel.listTasksAction), 2 seconds)
result should have length 2
}
总结
本文介绍了Slick 3的基础用法,包括:
- 项目搭建和依赖配置
- 数据模型定义和表映射
- 自定义类型映射
- 基本的CRUD操作
Slick的强大之处在于它将数据库操作抽象为类型安全的Scala集合操作,同时保留了直接控制SQL生成的能力。在后续文章中,我们将深入探讨更高级的查询、事务处理以及性能优化等内容。
对于Scala开发者来说,Slick提供了一种既安全又灵活的方式来处理关系型数据库,是构建现代Scala应用的理想选择。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考