不同于平常的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通过join
和on
来进行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并执行的。后边有时间详细了解一下。。(待填坑!)