Scalaz(37)- Free :实践-DB Transaction free style
我一直在不断的提示大家:FP就是Monadic Programming,是一种特殊的编程风格。在我们熟悉的数据库编程领域能不能实现FP风格呢?我们先设计一些示范例子来分析一下惯用的数据库编程过程:
import scalaz._
import Scalaz._
import scala.language.higherKinds
import scala.language.implicitConversions
import com.jolbox.bonecp.BoneCP
import com.jolbox.bonecp.BoneCPConfig
import java.sql.Connection
import java.sql.ResultSet object freedbtxns {
def getTutorId(courseId: Int, conn: Connection): Int = {
val sqlString = "select TUTOR from COURSES where ID=" + courseId
conn.createStatement().executeQuery(sqlString).getInt("ID")
}
def getTutorPay(courseId: Int, conn: Connection): Double = {
val sqlString = "select PAYAMT from COURSES where ID=" + courseId
conn.createStatement().executeQuery(sqlString).getDouble("PAYAMT")
}
def getStudentFee(courseId: Int, conn: Connection): Double = {
val sqlString = "select FEEAMT from COURSES where ID=" + courseId
conn.createStatement().executeQuery(sqlString).getDouble("FEEAMT")
}
def updateTutorPay(tutorId: Int, plusAmt: Double, conn: Connection): Unit = {
val sqlString = "update TUTORS set PAYABLE = PAYABLE+"+plusAmt.toString + " where ID=" + tutorId
conn.createStatement().executeUpdate(sqlString)
}
def updateStudentFee(studentId: Int, plusAmt: Double, conn: Connection): Unit = {
val sqlString = "update STUDENTS set DUEAMT = DUEAMT+"+plusAmt.toString + " where ID=" + studentId
conn.createStatement().executeUpdate(sqlString)
}
def findEmptySeat(courseId: Int, conn: Connection): Int = {
val sqlString = "select ID from SEATS where OCCUPIED='T' AND ID=" + courseId
conn.createStatement().executeQuery(sqlString).getInt("ID")
}
def updateSeatsStatus(seatId: Int, taken: Boolean, conn: Connection): Unit = {
val sqlString = "update SEATS set OCCUPIED ='"+taken.toString.toUpperCase.head + "' where ID=" + seatId
conn.createStatement().executeUpdate(sqlString)
}
我这里模拟了一个培训学校内的一些业务。上面设计的是一些基本函数,可以分别对学员、导师、座位进行查询和更新。如果我们需要把更新工作放入事务处理(transaction)内的话我们可以这样做:
def updateStudent(studentId: Int, courseId: Int): Unit = {
val config = new BoneCPConfig()
val bonecp = new BoneCP(config)
val conn = bonecp.getConnection()
conn.setReadOnly(false)
conn.setAutoCommit(false)
conn.rollback()
try {
val fee = getStudentFee(courseId, conn)
updateStudentFee(studentId,fee, conn)
conn.commit()
} catch {
case (e:Exception) => conn.rollback()
} finally {
conn.close()
}
}
def updateStudentAndSeat(studentId: Int, courseId: Int): Unit = {
val config = new BoneCPConfig()
val bonecp = new BoneCP(config)
val conn = bonecp.getConnection()
conn.setReadOnly(false)
conn.setAutoCommit(false)
conn.rollback()
try {
val fee = getStudentFee(courseId, conn)
updateStudentFee(studentId,fee, conn)
val seatId = findEmptySeat(courseId, conn)
updateSeatsStatus(seatId, true, conn)
conn.commit()
} catch {
case (e:Exception) => conn.rollback()
} finally {
conn.close()
}
}
马上可以发现在我们对这些函数在事务处理内进行组合使用时我们必须重新对事务处理进行设置,无法实现真正意义上的函数组合。如果我们认可FP风格的话,这里起码有两项弊处:一是源代码增加了大量的铺垫(boilerplate code),重复事务处理设置、二是每个更新函数都会产生副作用,换句话说就是这里那里都会有副作用影响,很难控制,这样就增加了程序的复杂程度,造成代码分析的困难。
我们希望达到的目标:
/*
def updateStudentAndSeat(studentId: Int): program {
// findEmptySeat
// updateStudentFee
// updateSeatStatus
} def runDBTxn(prg: program) {
//conn= getConnection
//try
// run(pre)
//commit
//catch
//rollback
}
runDBTxn(updateStudent)
runDBTxn(updateStudentAndSeat)
runDBTxn(updateSeatStatus)
*/
我们只在一个地方设置和运行事务处理。我们希望能把不同的program传入runDBTxn去运算。这不就是Free Monad的编程、运算关注分离模式嘛。那我们就试着用Free Monad来提供数据库事务处理支持。按上篇讨论的设计流程我们先设计ADT:
case class SqlOp[A](run: Connection => A)
模拟sql指令很简单,两种情况:query或者update。两者都可以用函数run表示:传入Connection,返回结果A,A有可能是Unit。要成为Free Monad就必须先获取SqlOp的Functor实例:
case class SqlOp[A](run: Connection => A)
implicit val sqlOpFunctor = new Functor[SqlOp] {
def map[A,B](sa: SqlOp[A])(f: A => B): SqlOp[B] =
SqlOp{ (conn: Connection) => f(sa.run(conn)) }
}
基本功能的sql操作函数及升格Free:
type Sql[A] = Free[SqlOp,A]
def getTutorId(courseId: Int): Sql[Int] =
Free.liftF(SqlOp{
(conn: Connection) => {
val sqlString = "select TUTOR from COURSES where ID=" + courseId
conn.createStatement().executeQuery(sqlString).getInt("ID")
}
}) def getTutorPay(courseId: Int): Sql[Double] =
Free.liftF(SqlOp{
(conn: Connection) => {
val sqlString = "select PAYAMT from COURSES where ID=" + courseId
conn.createStatement().executeQuery(sqlString).getDouble("PAYAMT")
}
})
def getStudentFee(courseId: Int): Sql[Double] =
Free.liftF(SqlOp{
(conn: Connection) => {
val sqlString = "select FEEAMT from COURSES where ID=" + courseId
conn.createStatement().executeQuery(sqlString).getDouble("FEEAMT")
}
})
def updateTutorPay(tutorId: Int, plusAmt: Double): Sql[Unit] =
Free.liftF(SqlOp{
(conn: Connection) => {
val sqlString = "update TUTORS set PAYABLE = PAYABLE+"+plusAmt.toString + " where ID=" + tutorId
conn.createStatement().executeUpdate(sqlString)
}
})
def updateStudentFee(studentId: Int, plusAmt: Double): Sql[Unit] =
Free.liftF(SqlOp{
(conn: Connection) => {
val sqlString = "update STUDENTS set DUEAMT = DUEAMT+"+plusAmt.toString + " where ID=" + studentId
conn.createStatement().executeUpdate(sqlString)
}
})
def findEmptySeat(courseId: Int): Sql[Int] =
Free.liftF(SqlOp{
(conn: Connection) => {
val sqlString = "select ID from SEATS where OCCUPIED='T' AND ID=" + courseId
conn.createStatement().executeQuery(sqlString).getInt("ID")
}
})
def updateSeatsStatus(seatId: Int, taken: Boolean): Sql[Unit] =
Free.liftF(SqlOp{
(conn: Connection) => {
val sqlString = "update SEATS set OCCUPIED ='"+taken.toString.toUpperCase.head + "' where ID=" + seatId
conn.createStatement().executeUpdate(sqlString)
}
})
我们现在可以用这些升格成Free的函数来建设AST示范例子:
def takeSeat(courseId: Int): Sql[Unit] = for {
emptySeat <- findEmptySeat(courseId)
_ <- updateSeatsStatus(emptySeat, true)
} yield()
def addCourse(studentId: Int, courseId: Int): Sql[Unit] = for {
fee <- getStudentFee(courseId)
pay <- getTutorPay(courseId)
tutorId <- getTutorId(courseId)
_ <- updateStudentFee(studentId, fee)
_ <- updateTutorPay(tutorId, pay)
_ <- takeSeat(courseId)
} yield()
addCourse对基本函数进行了组合,又调用了已经组合过一次的takeSeat,证明AST可以实现高度的函数组合。
下面示范实现相关的Interpreter:
def runTransactionImpl[A](conn: Connection, ast: Sql[A]): A =
ast.resume.fold ({
case x: SqlOp[Sql[A]] => runTransactionImpl(conn, x.run(conn))
},
(a: A) => a
)
我们需要一个通用的事务处理方法:
def runTransaction[A](ast: Sql[A]): Exception \/ A = {
val config = new BoneCPConfig()
val bonecp = new BoneCP(config)
val conn = bonecp.getConnection()
conn.setReadOnly(false)
conn.setAutoCommit(false)
conn.rollback()
try {
val result: A = runTransactionImpl(conn, ast)
result.right[Exception]
} catch {
case e: Exception => e.left[A]
} finally {
conn.close
}
}
这样,我们可以在一个地方使用事务处理来运算任何事先设计的AST。
我们可以用不同的方法来实现Interpreter。下面就是用Free.foldMap来运算AST的示范。由于我们需要注入Connection,所以采用了Sql to State的自然转换(natural transformation):
type SqlState[A] = State[Connection, A]
object SqlToState extends (SqlOp ~> SqlState) {
def apply[A](sa: SqlOp[A]): SqlState[A] = sa match {
case SqlOp(f) => State {
conn => (conn,f(conn))
}
}
}
def runTransactionImplState[A](conn: Connection, ast: Sql[A]) =
ast.foldMap(SqlToState).run(conn)
下面是这个用Free来实现FP风格数据库事务处理的完整示范代码:
import scalaz._
import Scalaz._
import scala.language.higherKinds
import scala.language.implicitConversions
import com.jolbox.bonecp.BoneCP
import com.jolbox.bonecp.BoneCPConfig
import java.sql.Connection
import java.sql.ResultSet object freedbtxns { case class SqlOp[A](run: Connection => A)
implicit val sqlOpFunctor = new Functor[SqlOp] {
def map[A,B](sa: SqlOp[A])(f: A => B): SqlOp[B] =
SqlOp{ (conn: Connection) => f(sa.run(conn)) }
}
type Sql[A] = Free[SqlOp,A]
def getTutorId(courseId: Int): Sql[Int] =
Free.liftF(SqlOp{
(conn: Connection) => {
val sqlString = "select TUTOR from COURSES where ID=" + courseId
conn.createStatement().executeQuery(sqlString).getInt("ID")
}
}) def getTutorPay(courseId: Int): Sql[Double] =
Free.liftF(SqlOp{
(conn: Connection) => {
val sqlString = "select PAYAMT from COURSES where ID=" + courseId
conn.createStatement().executeQuery(sqlString).getDouble("PAYAMT")
}
})
def getStudentFee(courseId: Int): Sql[Double] =
Free.liftF(SqlOp{
(conn: Connection) => {
val sqlString = "select FEEAMT from COURSES where ID=" + courseId
conn.createStatement().executeQuery(sqlString).getDouble("FEEAMT")
}
})
def updateTutorPay(tutorId: Int, plusAmt: Double): Sql[Unit] =
Free.liftF(SqlOp{
(conn: Connection) => {
val sqlString = "update TUTORS set PAYABLE = PAYABLE+"+plusAmt.toString + " where ID=" + tutorId
conn.createStatement().executeUpdate(sqlString)
}
})
def updateStudentFee(studentId: Int, plusAmt: Double): Sql[Unit] =
Free.liftF(SqlOp{
(conn: Connection) => {
val sqlString = "update STUDENTS set DUEAMT = DUEAMT+"+plusAmt.toString + " where ID=" + studentId
conn.createStatement().executeUpdate(sqlString)
}
})
def findEmptySeat(courseId: Int): Sql[Int] =
Free.liftF(SqlOp{
(conn: Connection) => {
val sqlString = "select ID from SEATS where OCCUPIED='T' AND ID=" + courseId
conn.createStatement().executeQuery(sqlString).getInt("ID")
}
})
def updateSeatsStatus(seatId: Int, taken: Boolean): Sql[Unit] =
Free.liftF(SqlOp{
(conn: Connection) => {
val sqlString = "update SEATS set OCCUPIED ='"+taken.toString.toUpperCase.head + "' where ID=" + seatId
conn.createStatement().executeUpdate(sqlString)
}
}) def takeSeat(courseId: Int): Sql[Unit] = for {
emptySeat <- findEmptySeat(courseId)
_ <- updateSeatsStatus(emptySeat, true)
} yield()
def addCourse(studentId: Int, courseId: Int): Sql[Unit] = for {
fee <- getStudentFee(courseId)
pay <- getTutorPay(courseId)
tutorId <- getTutorId(courseId)
_ <- updateStudentFee(studentId, fee)
_ <- updateTutorPay(tutorId, pay)
_ <- takeSeat(courseId)
} yield() def runTransactionImpl[A](conn: Connection, ast: Sql[A]): A =
ast.resume.fold ({
case x: SqlOp[Sql[A]] => runTransactionImpl(conn, x.run(conn))
},
(a: A) => a
)
def runTransaction[A](ast: Sql[A]): Exception \/ A = {
val config = new BoneCPConfig()
val bonecp = new BoneCP(config)
val conn = bonecp.getConnection()
conn.setReadOnly(false)
conn.setAutoCommit(false)
conn.rollback()
try {
val result: A = runTransactionImpl(conn, ast)
result.right[Exception]
} catch {
case e: Exception => e.left[A]
} finally {
conn.close
}
}
}
Scalaz(37)- Free :实践-DB Transaction free style的更多相关文章
- 在 laravel 的 DB::transaction 中,为外部变量赋值
例如,我想在 laravel 的事务中,对某个外部变量赋值,然后在后续的逻辑中判断该变量的属性 $user = null; // init DB::transaction(function() use ...
- Django深入----django.db.transaction
django 的事务: transaction.py atomic---原子性 def atomic(using=None, savepoint=True): # Bare decorator: @a ...
- laravel transaction : laravel 的事务是不支持eloquent的, 要用DB::的方式
数据库事务处理# 你可以使用 transaction 方法,去执行一组数据库事务处理的操作: DB::transaction(function() { DB::table('users')->u ...
- js-新兴的API,最佳实践,离线应用于客户端存储
离线应用于客户端存储: 1.离线检测:online以及offline事件,都是在window对象上触发 navigator.online为true的时候是表示设备能够上网 2.使用一个描述文件(man ...
- [PWA] 13. New db and object store
Create a db: import idb from 'idb'; var dbPromise = idb.open('test-db', 2, function (upgradeDb) { sw ...
- File already exists: filesystem '/path/file', transaction svn常见错误解决方法
前言 多人任务基本都会用到SVN,于是提交的时候如果不先更新在提交或者操作顺序不对,会经常出现错误,其中File already exists: filesystem这个就是个常见问题,上网找了半天没 ...
- yii源码三 -- db
<AR> CActiveRecord:path:/framework/db/ar/CActiveRecord.phpoverview:is the base class for class ...
- The transaction associated with this command is not the connection's active transaction
The fix is fairly simple: if you want a Dapper query to participate in a connection, explicitly deno ...
- PhoneGap下Web SQL实践
HTML5里的Web SQL数据库,内置了SQLite数据库, 对数据库的操作使用executeSql执行增删改查 1. 创建数据库 function creatDatabase(){ db = op ...
随机推荐
- atitit 短信接口规范与短信解决方案.docx
atitit 短信接口规范与短信解决方案.docx 1.1. 国内比较著名的短信提供商1 1.2. 短信接口规范1 1.3. 短信sdk构成1 1.4. 短信的实现1 1.5. SmsServiceY ...
- paip.java gui swt/jface 最佳实践
paip.java gui swt/jface 最佳实践 1. 工具:Eclipse +jigloo4 1 2. 安装插件: 1 1. IMPORT swt lib 2 2. 新建立窗体 2 3. 运 ...
- c#设计模式-命令模式
一. 命令(Command)模式 命令(Command)模式属于对象的行为模式[GOF95].命令模式又称为行动(Action)模式或交易(Transaction)模式.命令模式把一个请求或者操作封装 ...
- Java Annotation 总结
Annotation 被称为注解,在Java开发中是相当常见的,通过注解,我们可以简化代码提高开发效率.例如Override Annotation,这个应该算是在开发过程中使用最多的注解了.下面这个例 ...
- SQL server 临时表
创建临时表,#代表局部临时表,##代表全局临时表.局部临时表和全局临时表的具体含义是什么呢? 举例说明一下比较清晰些,先来看下局部临时表,[新建查询],在里面输入如下文本: 运行后,我们在此文件执行输 ...
- java容器详细解析
前言:在java开发中我们肯定会大量的使用集合,在这里我将总结常见的集合类,每个集合类的优点和缺点,以便我们能更好的使用集合.下面我用一幅图来表示 其中淡绿色的表示接口,红色的表示我们经常使用的类. ...
- 强制SQL Server执行计划使用并行提升在复杂查询语句下的性能
最近在给一个客户做调优的时候发现一个很有意思的现象,对于一个复杂查询(涉及12个表)建立必要的索引后,语句使用的IO急剧下降,但执行时间不降反升,由原来的8秒升到20秒. 通过观察执行 ...
- Oracle工具之DBNEWID
DBNEWID是Oracle提供的一个用于修改数据库DBID和DBNAME的工具. 在引进该工具之前,如果我们想修改数据库的数据库名,必须重建控制文件.但即便如此,也无法修改该数据库的DBID.众所周 ...
- Unity 3D制作2D游戏的几种方法
1.使用本身UGUI. 2.把摄像机的投影改为正交投影,不考虑Z轴. 3.使用Untiy自身的2D模式. 4.使用2D TooKit插件.
- 栈的存储结构和常见操作(c 语言实现)
俗话说得好,线性表(尤其是链表)是一切数据结构和算法的基础,很多复杂甚至是高级的数据结构和算法,细节处,除去数学和计算机程序基础的知识,大量的都在应用线性表. 一.栈 其实本质还是线性表:限定仅在表尾 ...