Scalaz(37)- Free :实践-DB Transaction free style

 我一直在不断的提示大家:FP就是Monadic Programming,是一种特殊的编程风格。在我们熟悉的数据库编程领域能不能实现FP风格呢?我们先设计一些示范例子来分析一下惯用的数据库编程过程:

 1 import scalaz._
 2 import Scalaz._
 3 import scala.language.higherKinds
 4 import scala.language.implicitConversions
 5 import com.jolbox.bonecp.BoneCP
 6 import com.jolbox.bonecp.BoneCPConfig
 7 import java.sql.Connection
 8 import java.sql.ResultSet
 9
10 object freedbtxns {
11 def getTutorId(courseId: Int, conn: Connection): Int = {
12   val sqlString = "select TUTOR from COURSES where ID=" + courseId
13   conn.createStatement().executeQuery(sqlString).getInt("ID")
14 }
15 def getTutorPay(courseId: Int, conn: Connection): Double = {
16   val sqlString = "select PAYAMT from COURSES where ID=" + courseId
17   conn.createStatement().executeQuery(sqlString).getDouble("PAYAMT")
18 }
19 def getStudentFee(courseId: Int, conn: Connection): Double = {
20   val sqlString = "select FEEAMT from COURSES where ID=" + courseId
21   conn.createStatement().executeQuery(sqlString).getDouble("FEEAMT")
22 }
23 def updateTutorPay(tutorId: Int, plusAmt: Double, conn: Connection): Unit = {
24   val sqlString = "update TUTORS set PAYABLE = PAYABLE+"+plusAmt.toString + " where ID=" + tutorId
25   conn.createStatement().executeUpdate(sqlString)
26 }
27 def updateStudentFee(studentId: Int, plusAmt: Double, conn: Connection): Unit = {
28   val sqlString = "update STUDENTS set DUEAMT = DUEAMT+"+plusAmt.toString + " where ID=" + studentId
29   conn.createStatement().executeUpdate(sqlString)
30 }
31 def findEmptySeat(courseId: Int, conn: Connection): Int = {
32   val sqlString = "select ID from SEATS where OCCUPIED='T' AND ID=" + courseId
33   conn.createStatement().executeQuery(sqlString).getInt("ID")
34 }
35 def updateSeatsStatus(seatId: Int, taken: Boolean, conn: Connection): Unit = {
36   val sqlString = "update SEATS set OCCUPIED ='"+taken.toString.toUpperCase.head + "' where ID=" + seatId
37   conn.createStatement().executeUpdate(sqlString)
38 }  

我这里模拟了一个培训学校内的一些业务。上面设计的是一些基本函数,可以分别对学员、导师、座位进行查询和更新。如果我们需要把更新工作放入事务处理(transaction)内的话我们可以这样做:

1 def updateStudent(studentId: Int, courseId: Int): Unit = {
 2    val config = new BoneCPConfig()
 3    val bonecp = new BoneCP(config)
 4    val conn = bonecp.getConnection()
 5    conn.setReadOnly(false)
 6    conn.setAutoCommit(false)
 7    conn.rollback()
 8    try {
 9      val fee = getStudentFee(courseId, conn)
10      updateStudentFee(studentId,fee, conn)
11      conn.commit()
12    } catch {
13      case (e:Exception) => conn.rollback()
14    } finally {
15      conn.close()
16    }
17 }
18 def updateStudentAndSeat(studentId: Int, courseId: Int): Unit = {
19    val config = new BoneCPConfig()
20    val bonecp = new BoneCP(config)
21    val conn = bonecp.getConnection()
22    conn.setReadOnly(false)
23    conn.setAutoCommit(false)
24    conn.rollback()
25    try {
26      val fee = getStudentFee(courseId, conn)
27      updateStudentFee(studentId,fee, conn)
28      val seatId = findEmptySeat(courseId, conn)
29      updateSeatsStatus(seatId, true, conn)
30      conn.commit()
31    } catch {
32      case (e:Exception) => conn.rollback()
33    } finally {
34      conn.close()
35    }
36 }

马上可以发现在我们对这些函数在事务处理内进行组合使用时我们必须重新对事务处理进行设置,无法实现真正意义上的函数组合。如果我们认可FP风格的话,这里起码有两项弊处:一是源代码增加了大量的铺垫(boilerplate code),重复事务处理设置、二是每个更新函数都会产生副作用,换句话说就是这里那里都会有副作用影响,很难控制,这样就增加了程序的复杂程度,造成代码分析的困难。

我们希望达到的目标:

 1 /*
 2 def updateStudentAndSeat(studentId: Int): program {
 3  // findEmptySeat
 4  // updateStudentFee
 5  // updateSeatStatus
 6 }
 7
 8 def runDBTxn(prg: program) {
 9   //conn= getConnection
10   //try
11   // run(pre)
12   //commit
13   //catch
14   //rollback
15 }
16 runDBTxn(updateStudent)
17 runDBTxn(updateStudentAndSeat)
18 runDBTxn(updateSeatStatus)
19 */  

我们只在一个地方设置和运行事务处理。我们希望能把不同的program传入runDBTxn去运算。这不就是Free Monad的编程、运算关注分离模式嘛。那我们就试着用Free Monad来提供数据库事务处理支持。按上篇讨论的设计流程我们先设计ADT:

1 case class SqlOp[A](run: Connection => A)

模拟sql指令很简单,两种情况:query或者update。两者都可以用函数run表示:传入Connection,返回结果A,A有可能是Unit。要成为Free Monad就必须先获取SqlOp的Functor实例:

1 case class SqlOp[A](run: Connection => A)
2 implicit val sqlOpFunctor = new Functor[SqlOp] {
3   def map[A,B](sa: SqlOp[A])(f: A => B): SqlOp[B] =
4     SqlOp{ (conn: Connection) => f(sa.run(conn)) }
5 }

基本功能的sql操作函数及升格Free:

1 type Sql[A] = Free[SqlOp,A]
 2 def getTutorId(courseId: Int): Sql[Int] =
 3   Free.liftF(SqlOp{
 4     (conn: Connection) => {
 5       val sqlString = "select TUTOR from COURSES where ID=" + courseId
 6       conn.createStatement().executeQuery(sqlString).getInt("ID")
 7     }
 8   })
 9
10 def getTutorPay(courseId: Int): Sql[Double] =
11   Free.liftF(SqlOp{
12     (conn: Connection) => {
13       val sqlString = "select PAYAMT from COURSES where ID=" + courseId
14       conn.createStatement().executeQuery(sqlString).getDouble("PAYAMT")
15     }
16   })
17 def getStudentFee(courseId: Int): Sql[Double] =
18   Free.liftF(SqlOp{
19     (conn: Connection) => {
20      val sqlString = "select FEEAMT from COURSES where ID=" + courseId
21      conn.createStatement().executeQuery(sqlString).getDouble("FEEAMT")
22     }
23   })
24 def updateTutorPay(tutorId: Int, plusAmt: Double): Sql[Unit] =
25   Free.liftF(SqlOp{
26     (conn: Connection) => {
27       val sqlString = "update TUTORS set PAYABLE = PAYABLE+"+plusAmt.toString + " where ID=" + tutorId
28       conn.createStatement().executeUpdate(sqlString)
29     }
30   })
31 def updateStudentFee(studentId: Int, plusAmt: Double): Sql[Unit] =
32   Free.liftF(SqlOp{
33     (conn: Connection) => {
34       val sqlString = "update STUDENTS set DUEAMT = DUEAMT+"+plusAmt.toString + " where ID=" + studentId
35       conn.createStatement().executeUpdate(sqlString)
36     }
37   })
38 def findEmptySeat(courseId: Int): Sql[Int] =
39   Free.liftF(SqlOp{
40     (conn: Connection) => {
41       val sqlString = "select ID from SEATS where OCCUPIED='T' AND ID=" + courseId
42       conn.createStatement().executeQuery(sqlString).getInt("ID")
43     }
44   })
45 def updateSeatsStatus(seatId: Int, taken: Boolean): Sql[Unit] =
46   Free.liftF(SqlOp{
47     (conn: Connection) => {
48       val sqlString = "update SEATS set OCCUPIED ='"+taken.toString.toUpperCase.head + "' where ID=" + seatId
49       conn.createStatement().executeUpdate(sqlString)
50     }
51   })

我们现在可以用这些升格成Free的函数来建设AST示范例子:

1   def takeSeat(courseId: Int): Sql[Unit] = for {
 2     emptySeat <- findEmptySeat(courseId)
 3     _ <- updateSeatsStatus(emptySeat, true)
 4   } yield()
 5   def addCourse(studentId: Int, courseId: Int): Sql[Unit] = for {
 6     fee <- getStudentFee(courseId)
 7     pay <- getTutorPay(courseId)
 8     tutorId <- getTutorId(courseId)
 9     _ <- updateStudentFee(studentId, fee)
10     _ <- updateTutorPay(tutorId, pay)
11     _ <- takeSeat(courseId)
12   } yield()

addCourse对基本函数进行了组合,又调用了已经组合过一次的takeSeat,证明AST可以实现高度的函数组合。

下面示范实现相关的Interpreter:

1   def runTransactionImpl[A](conn: Connection, ast: Sql[A]): A =
2     ast.resume.fold ({
3       case x: SqlOp[Sql[A]] => runTransactionImpl(conn, x.run(conn))
4     },
5     (a: A) => a
6    )

我们需要一个通用的事务处理方法:

 1   def runTransaction[A](ast: Sql[A]): Exception \/ A = {
 2     val config = new BoneCPConfig()
 3     val bonecp = new BoneCP(config)
 4     val conn = bonecp.getConnection()
 5     conn.setReadOnly(false)
 6     conn.setAutoCommit(false)
 7     conn.rollback()
 8     try {
 9       val result: A = runTransactionImpl(conn, ast)
10       result.right[Exception]
11     } catch {
12       case e: Exception => e.left[A]
13     } finally {
14       conn.close
15     }
16   }

这样,我们可以在一个地方使用事务处理来运算任何事先设计的AST。

我们可以用不同的方法来实现Interpreter。下面就是用Free.foldMap来运算AST的示范。由于我们需要注入Connection,所以采用了Sql to State的自然转换(natural transformation):

 1   type SqlState[A] = State[Connection, A]
 2   object SqlToState extends (SqlOp ~> SqlState) {
 3     def apply[A](sa: SqlOp[A]): SqlState[A] = sa match {
 4       case SqlOp(f) => State {
 5         conn => (conn,f(conn))
 6       }
 7     }
 8   }
 9   def runTransactionImplState[A](conn: Connection, ast: Sql[A]) =
10     ast.foldMap(SqlToState).run(conn)

下面是这个用Free来实现FP风格数据库事务处理的完整示范代码:

 1 import scalaz._
  2 import Scalaz._
  3 import scala.language.higherKinds
  4 import scala.language.implicitConversions
  5 import com.jolbox.bonecp.BoneCP
  6 import com.jolbox.bonecp.BoneCPConfig
  7 import java.sql.Connection
  8 import java.sql.ResultSet
  9
 10 object freedbtxns {
 11
 12 case class SqlOp[A](run: Connection => A)
 13 implicit val sqlOpFunctor = new Functor[SqlOp] {
 14   def map[A,B](sa: SqlOp[A])(f: A => B): SqlOp[B] =
 15     SqlOp{ (conn: Connection) => f(sa.run(conn)) }
 16 }
 17 type Sql[A] = Free[SqlOp,A]
 18 def getTutorId(courseId: Int): Sql[Int] =
 19   Free.liftF(SqlOp{
 20     (conn: Connection) => {
 21       val sqlString = "select TUTOR from COURSES where ID=" + courseId
 22       conn.createStatement().executeQuery(sqlString).getInt("ID")
 23     }
 24   })
 25
 26 def getTutorPay(courseId: Int): Sql[Double] =
 27   Free.liftF(SqlOp{
 28     (conn: Connection) => {
 29       val sqlString = "select PAYAMT from COURSES where ID=" + courseId
 30       conn.createStatement().executeQuery(sqlString).getDouble("PAYAMT")
 31     }
 32   })
 33 def getStudentFee(courseId: Int): Sql[Double] =
 34   Free.liftF(SqlOp{
 35     (conn: Connection) => {
 36      val sqlString = "select FEEAMT from COURSES where ID=" + courseId
 37      conn.createStatement().executeQuery(sqlString).getDouble("FEEAMT")
 38     }
 39   })
 40 def updateTutorPay(tutorId: Int, plusAmt: Double): Sql[Unit] =
 41   Free.liftF(SqlOp{
 42     (conn: Connection) => {
 43       val sqlString = "update TUTORS set PAYABLE = PAYABLE+"+plusAmt.toString + " where ID=" + tutorId
 44       conn.createStatement().executeUpdate(sqlString)
 45     }
 46   })
 47 def updateStudentFee(studentId: Int, plusAmt: Double): Sql[Unit] =
 48   Free.liftF(SqlOp{
 49     (conn: Connection) => {
 50       val sqlString = "update STUDENTS set DUEAMT = DUEAMT+"+plusAmt.toString + " where ID=" + studentId
 51       conn.createStatement().executeUpdate(sqlString)
 52     }
 53   })
 54 def findEmptySeat(courseId: Int): Sql[Int] =
 55   Free.liftF(SqlOp{
 56     (conn: Connection) => {
 57       val sqlString = "select ID from SEATS where OCCUPIED='T' AND ID=" + courseId
 58       conn.createStatement().executeQuery(sqlString).getInt("ID")
 59     }
 60   })
 61 def updateSeatsStatus(seatId: Int, taken: Boolean): Sql[Unit] =
 62   Free.liftF(SqlOp{
 63     (conn: Connection) => {
 64       val sqlString = "update SEATS set OCCUPIED ='"+taken.toString.toUpperCase.head + "' where ID=" + seatId
 65       conn.createStatement().executeUpdate(sqlString)
 66     }
 67   })
 68
 69   def takeSeat(courseId: Int): Sql[Unit] = for {
 70     emptySeat <- findEmptySeat(courseId)
 71     _ <- updateSeatsStatus(emptySeat, true)
 72   } yield()
 73   def addCourse(studentId: Int, courseId: Int): Sql[Unit] = for {
 74     fee <- getStudentFee(courseId)
 75     pay <- getTutorPay(courseId)
 76     tutorId <- getTutorId(courseId)
 77     _ <- updateStudentFee(studentId, fee)
 78     _ <- updateTutorPay(tutorId, pay)
 79     _ <- takeSeat(courseId)
 80   } yield()
 81
 82   def runTransactionImpl[A](conn: Connection, ast: Sql[A]): A =
 83     ast.resume.fold ({
 84       case x: SqlOp[Sql[A]] => runTransactionImpl(conn, x.run(conn))
 85     },
 86     (a: A) => a
 87    )
 88   def runTransaction[A](ast: Sql[A]): Exception \/ A = {
 89     val config = new BoneCPConfig()
 90     val bonecp = new BoneCP(config)
 91     val conn = bonecp.getConnection()
 92     conn.setReadOnly(false)
 93     conn.setAutoCommit(false)
 94     conn.rollback()
 95     try {
 96       val result: A = runTransactionImpl(conn, ast)
 97       result.right[Exception]
 98     } catch {
 99       case e: Exception => e.left[A]
100     } finally {
101       conn.close
102     }
103   }
104 }
时间: 2024-10-07 19:02:16

Scalaz(37)- Free :实践-DB Transaction free style的相关文章

java 开始DB transaction 之后 执行system.exit,结果?

问题描述 java开始DBtransaction之后执行system.exit,之后用不用手动结束transaction?还是什么都不用处理? 解决方案 解决方案二:有些数据库会回滚啊.一般在打开一个事务后要能保证要么事务提交,要么回滚.要是什么都不做说明你的程序写的不好呢.解决方案三:我那么做的话,是什么结果?

一个复杂系统的拆分改造实践

1 为什么要拆分? 先看一段对话. 从上面对话可以看出拆分的理由: 1)  应用间耦合严重.系统内各个应用之间不通,同样一个功能在各个应用中都有实现,后果就是改一处功能,需要同时改系统中的所有应用.这种情况多存在于历史较长的系统,因各种原因,系统内的各个应用都形成了自己的业务小闭环: 2)  业务扩展性差.数据模型从设计之初就只支持某一类的业务,来了新类型的业务后又得重新写代码实现,结果就是项目延期,大大影响业务的接入速度: 3)  代码老旧,难以维护.各种随意的if else.写死逻辑散落在应

Style在Android中的继承关系

Android的Styles(样式)和Themes(主题)非常类似Web开发里的CSS,方便开发者将页面内容和布局呈现分开.Style和Theme在Android里的定义方式是完全一样的,两者只是概念上的区别:Style作用在单个视图或控件上,而Theme用于Activity或整个应用程序.由于作用范围的不同,Theme也就需要比Style包含更多的定义属性值的项目(item).不过本文,我将Style和Theme都归为Style来称呼. Android的Style和Web的CSS相比,有一个缺

HTML5 离线存储之Web SQL

本篇没有考虑异步,多线程及SQL注入 WebDatabase 规范中说这份规范不再维护了,原因是同质化(几乎实现者都选择了Sqlite),且不说这些,单看在HTML5中如何实现离线数据的CRUD,最基本的用法(入门级别) 1,打开数据库 2,创建表 3,新增数据 4,更新数据 5,读取数据 6,删除数据 事实上,关键点在于如何拿到一个可执行SQL语句的上下文,像创建表,删除表,CRUD操作等仅区别于SQL语句的写法.OK,貌似 "SqlHelper"啊,换个名字,dataBaseOpe

使用Python的web.py框架实现类似Django的ORM查询的教程

  这篇文章主要介绍了使用Python的web.py框架实现类似Django的ORM查询的教程,集成的ORM操作数据库向来是Python最强大的功能之一,本文则探讨如何在web.py框架上实现,需要的朋友可以参考下 Django中的对象查询 Django框架自带了ORM,实现了一些比较强大而且方便的查询功能,这些功能和表无关.比如下面这个例子: ? 1 2 3 4 5 6 7 class Question(models.Model): question_text = models.CharFie

Laravel5.0学习--01 入门

本文以laravel5.0.22为例. 环境需求 Laravel5.0 框架有一些系统上的需求: PHP 版本 >= 5.4 Mcrypt PHP 扩展 OpenSSL PHP 扩展 Mbstring PHP 扩展 Tokenizer PHP 扩展 在 PHP 5.5 之后, 有些操作系统需要手动安装 PHP JSON 扩展包.如果你是使用 Ubuntu,可以通过 apt-get install php5-json 来进行安装. 我该使用Laravel吗 来自知乎网友的讨论:PHP框架Larav

webpy使用笔记(一)

工作环境中需要经常生产和测试服务器,机房一直很混乱,因此萌生了开发一个简单方便的服务器管理系统(说的好高大上,其实就是个可以获取服务器信息的小web应用).之所以选择webpy,正式因为它够简单,尤其是对于我这种python新人来说.它是一款轻量级的python web开发框架,对于个人开发小应用来说很适合. webpy install 下载:wget http://webpy.org/static/web.py-0.37.tar.gz 安装:python setup.py install we

Linq to Sql : 三种事务处理方式

原文:Linq to Sql : 三种事务处理方式     Linq to SQL支持三种事务处理模型:显式本地事务.显式可分发事务.隐式事务.(from  MSDN: 事务 (LINQ to SQL)).MSDN中描述得相对比较粗狂,下面就结合实例来对此进行阐述. 0. 测试环境 OS Windows Server 2008 Enterprise + sp1 IDE Visual Studio 2008, .net framework 3.5 + SP1 DB SQL Server 2000

40 个重要的 HTML5 面试题及答案

40 个重要的 HTML5 面试题及答案 介绍 我是一个ASP.NET MVC的开发者,最近在我找工作的时候被问到很多与HTML5相关的问题和新特性.所以以下40个重要的问题将帮助你复习HTML5相关的知识. 这些问题不是你得到工作的高效解决方案,但是可以在你想快速复习相关主题的时候有所帮助. 快乐地找工作. SGML(标准通用标记语言)和HTML(超文本标记语言),XML(可扩展标记语言)和HTML的之间有什么关系? SGML(标准通用标记语言)是一个标准,告诉我们怎么去指定文档标记.他是只描