通过一段时间的学习和了解以及前面几篇关于Slick的讨论后对Slick这个函数式数据库编程工具有了些具体的了解。回顾我学习Slick的目的,产生了许多想法,觉着应该从实际的工作应用角度把我对Slick目前能够达到的目的以及在现有功能优势和特点下如何进一步改进才能正真符合IT系统对数据库程序编程和运行效率的要求。想通过这篇博客把想法提出来跟大家分享一下,看看是否能够引起大家的共鸣,为我下一步的工作制定一个方向性的框架。
首先谈谈Slick的特点:主体方面Slick为函数式编程模式带来了SQL编程,可以把数据库表当作scala语言中的集合来对待。除了能实现FP的函数组合外又避免了嵌入SQL语句式的数据库编程,而且也实现了类型安全(type safe),可以由编译器(compiler)在编译时来捕捉语法错误。另一方面与同是基于jdbc之上的通用ORM库比较,Slick可以实现更高效率的关系表数据提取。
Slick实现函数组合部分主要分两个层次:一是Query组合:即把多个Query组成一个Query。我看主要是为了把几个数据源(table)join成一个源头,如下:
1 val q11 = for { 2 c <- coffees.filter(_.price > 100.0) 3 s <- suppliers.filter(_.id === c.supID) 4 } yield (c.id, c.name, s.name) 5 q11.result.statements.head 6 //res4: String = select x2."COF_ID", x2."COF_NAME", x3."SUP_NAME" from "COFFEES" x2, "SUPPLIERS" x3 where (x2."COF_PRICE" > 100.0) and (x3."SUP_ID" = x2."COF_SUP") 7
8 //inner join
9 val q12 = for { 10 c <- coffees.filter(_.price > 100.0) 11 s <- suppliers if s.id === c.supID 12 } yield (c.id, c.name, s.name) 13 q12.result.statements.head 14 //res12: String = select x2."COF_ID", x2."COF_NAME", x3."SUP_NAME" from "COFFEES" x2, "SUPPLIERS" x3 where x2."COF_SUP" = x3."SUP_ID" 15
16 //outter join
17 val q13 = for { 18 c <- coffees.filter(_.price > 100.0) 19 s <- suppliers.filter(_.name === "Acme") 20 } yield (c.id, c.name, s.name) 21 q13.result.statements.head 22 //res6: String = select x2."COF_ID", x2."COF_NAME", x3."SUP_NAME" from "COFFEES" x2, "SUPPLIERS" x3 where (x2."COF_PRICE" > 100.0) and (x3."SUP_NAME" = 'Acme')
上面的例子里示范了把coffees,suppliers两个Query组合成一个joined Query的几种方法,最终结果就是产生一条joined-table的SQL语句。我们也可以用Slick的Query函数组件(combinator)join来达到同样目的,如下:
1 val q14 = for { 2 (c,s) <- coffees join suppliers on (_.supID === _.id) 3 } yield (c.id, c.name, s.name) 4 q14.result.statements.head 5 //res7: String = select x2."COF_ID", x2."COF_NAME", x3."SUP_NAME" from "COFFEES" x2, "SUPPLIERS" x3 where x2."COF_SUP" = x3."SUP_ID"
从函数组合角度来讲:所谓Query组合,功能十分有限。这也符合逻辑:因为最终要形成一条SQL语句。这样说吧:把多条分别读取不同源头的Query组合成一条从合并的源头读取数据的SQL语句也说得通,但其它情况如把一条update Query和insert Query组合成一条SQL就不符合逻辑了。倒是Query的函数组件如filter,take,drop,sortBy,groupBy等在函数式编程中还是比较适用的。
另一个层次的组合是DBIOAction组合,主要目的是编排多个独立Query的运算流程:按照一个组合而成的顺序来运算这些Action。也就是按顺序把每条SQL语句发送到数据库去运算。从函数组合的角度解释又可以理解为按需要把多个独立的数据库操作动作组合成一个完整的数据库操作功能。一项功能里的所有动作可以在一个事务处理(transaction processing)里进行运算。从下面的例子里可以看出端倪:
1 val initSupAction = suppliers.schema.create andThen qInsert3 2 val createCoffeeAction = coffees.schema.create 3 val insertCoffeeAction = qInsert zip qInsert2 4 val initSupAndCoffee = for { 5 _ <- initSupAction 6 _ <- createCoffeeAction 7 (i1,i2) <- insertCoffeeAction 8 } yield (i1,i2) 9
10 //先选出所有ESPRESSO开头的coffee名称,然后逐个删除
11 val delESAction = (for { 12 ns <- coffees.filter(_.name.startsWith("ESPRESSO")).map(_.name).result 13 _ <- DBIO.seq(ns.map(n => coffees.filter(_.name === n).delete): _*) 14 } yield ()).transactionally 15 //delESAction: slick.dbio.DBIOAction[... 16