Slick 3 总结 | Functional Relational Mapping

不同于平常的ORM框架,Slick提供了一种函数响应式操作数据库的方式:Functional Relational Mapping。这种函数响应式的操作方式允许我们将数据库的每一个表都看作是一个 集合,对数据库的CRUD操作可以转化为对这些“集合”的操作,因此我们可以充分利用各种Monadic的算子而不需要自己编写SQL(当然,需要保证Slick解析的SQL性能好)。并且,Slick是以 异步 的方式操作数据库,返回Future,这有助于我们编写异步响应式的程序,比MyBatis等等的同步阻塞ORM框架之流好很多。这里我们就来总结一下Slick中Functional Relational Mapping的使用以及与SQL语句的对应关系(MySQL语法)。后面有时间的话,我还会总结一下Slick中FRM的实现原理。

Functional Relational Mapping ⇔ SQL

首先我们需要自己定义对应的映射Table,当然也可以用Generator生成。假设有以下两个表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
CREATE TABLE `article` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(85) NOT NULL,
`author` varchar(45) NOT NULL,
`url` varchar(150) NOT NULL,
`cid` int(11) NOT NULL,
`update_date` date NOT NULL,
PRIMARY KEY (`id`),
KEY `index_title` (`title`),
KEY `index_cid` (`cid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `category` (
`cid` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(45) NOT NULL,
`abbr` varchar(45) NOT NULL,
PRIMARY KEY (`cid`),
UNIQUE KEY `name_UNIQUE` (`name`),
UNIQUE KEY `abbr_UNIQUE` (`abbr`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

它们对应的Table可以简化成下面的两个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class ArticleTable(tag: Tag) extends Table[Article](tag, "article") {
override def * = (id, title, author, url, cid, updateDate) <> (entity.Article.tupled, entity.Article.unapply) // 投影关系
val id: Rep[Int] = column[Int]("id", O.AutoInc, O.PrimaryKey)
val title: Rep[String] = column[String]("title", O.Length(85, varying = true))
val author: Rep[String] = column[String]("author", O.Length(45, varying = true))
val url: Rep[String] = column[String]("url", O.Length(150, varying = true))
val cid: Rep[Int] = column[Int]("cid")
val updateDate: Rep[java.sql.Date] = column[java.sql.Date]("update_date")
val index1 = index("index_cid", cid)
val index2 = index("index_title", title)
}
class CategoryTable(tag: Tag) extends Table[Category](tag, "category") {
override def * = (cid, name, abbr) <> (entity.Category.tupled, entity.Category.unapply)
val cid: Rep[Int] = column[Int]("cid", O.AutoInc, O.PrimaryKey)
val name: Rep[String] = column[String]("name", O.Length(45, varying = true))
val abbr: Rep[String] = column[String]("abbr", O.Length(45, varying = true))
val index1 = index("abbr_UNIQUE", abbr, unique=true)
val index2 = index("name_UNIQUE", name, unique=true)
}

接着我们就可以定义这两个表对应的Slick TableQuery集合:

1
2
val articles = TableQuery[ArticleTable]
val categories = TableQuery[CategoryTable]

下面我们就可以对这两个“集合”进行操作了~

SELECT

普通的SELECT语句非常简单,比如SELECT * FROM article对应articles.result,即article表中所有的数据。

如果要选择的话可以用map算子映射出对应的投影。

WHERE

含WHERE的SQL:

1
SELECT title, url FROM article WHERE id = 10

WHERE通过filter算子实现,对应FRM:

1
2
3
articles.map(x => (x.title, x.url))
.filter(_.id === 10)
.result

ORDER BY

1
SELECT * FROM article ORDER BY id DESC

ORDER BY通过sortBy算子实现:

1
articles.sortBy(_.id.desc).result

LIMIT(分页)

1
SELECT * FROM article LIMIT 10000, 10

LIMIT通过take算子和drop算子实现。drop(offset)表示忽略前offset条记录,take(n)代表取n条记录。对应FRM:

1
articles.drop(10000).take(10).result

聚合函数

使用各种聚合函数也非常方便:

1
2
3
articles.map(_.id).max.result
articles.map(_.id).min.result
articles.map(_.id).avg.result

JOIN

Slick中的连接操作分为Applicative Join和Monadic Join两种。Monadic Join,顾名思义就是用Monad的思想去进行JOIN操作(通过flatMap算子)。

Applicative Join

Applicative Join通过joinon来进行JOIN操作。

内连接的例子:

1
2
3
4
SELECT a.*, c.name
FROM article AS a
INNER JOIN category as c
ON a.cid = c.cid

对应Applicative Join:

1
2
3
val innerJoin = for {
(a, c) <- articles join categories on (_.cid === _.cid)
} yield (a, c.name)

左外连接和右外连接也差不多,只不过外连接可能带来NULL项,因此对应的可能出现NULL的位置被包装为Option,例:

1
2
3
val leftOuterJoin = for {
(a, c) <- articles joinLeft categories on (_.cid === _.cid)
} yield (a, c.map(_.name))

Monadic Join

Monadic Join通过flatMap来进行JOIN操作,这里只举一个内连接的例子:

1
2
3
4
val monadicInnerJoin = for {
a <- articles
c <- categories if c.cid === a.cid
} yield (a, c.name)

注意它会被转化成implicit join(SELECT ... FROM a, b WHERE ...)的形式(貌似是Slick的规则):

1
2
3
SELECT a.*, c.name
FROM article AS a, category AS c
WHERE a.cid = c.cid

UNION

UNION操作比较简单,直接q1 union q2即可。

COUNT

若需要计算COUNT值,直接使用length算子:articles.filter(_.id > 100).length.result

FRM的实现原理

Slick实现Functional Relational Mapping的方式比较强悍。粗略地看了下代码,Slick底层应该是将每个Action逐层解析后转化为AST,然后转化为对应的SQL并执行的。后边有时间详细了解一下。。(待填坑!)

文章目录
  1. 1. Functional Relational Mapping ⇔ SQL
    1. 1.1. SELECT
    2. 1.2. WHERE
    3. 1.3. ORDER BY
    4. 1.4. LIMIT(分页)
    5. 1.5. 聚合函数
    6. 1.6. JOIN
      1. 1.6.1. Applicative Join
      2. 1.6.2. Monadic Join
    7. 1.7. UNION
    8. 1.8. COUNT
  2. 2. FRM的实现原理