我一直在不断的提示大家: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),重复事务处理设置、二是每个更新函数都会产生副作用,换句话说就是这里那里都会有副作用影响,很难控制,这样就增加了程序的复杂程度,造成代码分析的困难。
我们希望达到的目标: