前言:首先介绍一下机房收费系统需求
机房收费系统是为我们学校管理机房做的一个小MIS。因为,现在大学的英语——《新视野大学英语》是网上学习的,而且用的是本学校的内网服务器,还有一些大学要参加相应的计算机等级考试,所以,希望学校的机房可以对本学校的学生开放(在指定的时间,如果晚上、周末等)。
但是,学校如果开放机房的话,那么一定会投入相应的人力与物力(顺便赚点钱,其实大家心里都明白……),所以,希望学生有偿上机,也就是对学生上机进行收费管理,并同时记录相应的上机记录等。
该系统可以实现的功能包括、学生注册、学生充值、学生退卡、学生上机、下机管理、学生信息维护、报表统计数据等。因为,此系统所用的磁卡,是学校的饭卡,属于另一个系统,所以,不涉及关于磁卡方面的管理与维护。
好了,今天我想说的的,在实现机房收费系统中的一个细节问题。
大家可以想到,比如,学习充值(数据库包括:充值记录表,卡表(加上充值金额)),所以,我认为,在这里一定要保证学习充值这个用例的原子性。想到原子性的话,那么大家自然而然的就会想到关于数据库的一个概念,对就是——事务。
下面我想说一下,我是怎么实现的,希望大家提出宝贵意见。
在系统架构方面,我用到了三层架构,底层加上了抽象工厂模式,并用了一个SQLHelper类
下面是我的构架图:
下面是实现充值的类图:
现在是我写的SQLHelper中关于事务的一个函数:
Public Shared Function ExecuteNonQuery(ByVal Trans As SqlTransaction, ByVal cmdType As CommandType, ByVal cmdText As String, ByVal param() As SqlParameter) As Integer
Dim dbCmd As New SqlCommand
Dim Conn As SqlConnection
If Trans IsNot Nothing Then '如果有事务,就用事务的连接 因为有事务,和没有事务,连接还有关系,所以分了两种情况
Conn = Trans.Connection
PrepareCommand(Conn, dbCmd, Trans, cmdType, cmdText, param)
Return dbCmd.ExecuteNonQuery()
dbCmd.Parameters.Clear()
Else
Conn = New SqlConnection(strConn) '读配置文件中的连接字符串
PrepareCommand(Conn, dbCmd, Trans, cmdType, cmdText, param)
Dim result As Integer
Using Conn
result = dbCmd.ExecuteNonQuery
End Using
dbCmd.Parameters.Clear()
Return result
End If
End Function
下面这个私有函数,也在SQLHelper里,用来帮助执行
''' <summary>
''' 帮助执行command
''' </summary>
''' <param name="Trans">事务</param>
''' <param name="dbConn">连线</param>
''' <param name="dbCmd">command</param>
''' <param name="cmdType">commandtype</param>
''' <param name="cmdText">sql语句 或者存储过程</param>
''' <param name="param">参数</param>
''' <remarks></remarks>
Public Shared Sub PrepareCommand(ByVal dbConn As SqlConnection, ByVal dbCmd As SqlCommand, ByVal Trans As SqlTransaction, ByVal cmdType As CommandType, ByVal cmdText As String, ByVal param() As SqlParameter)
If dbConn.State = ConnectionState.Closed Then
dbConn.Open()
End If
dbCmd.Connection = dbConn
dbCmd.CommandType = cmdType
dbCmd.CommandText = cmdText
If Trans IsNot Nothing Then
dbCmd.Transaction = Trans
End If
If param IsNot Nothing Then
dbCmd.Parameters.AddRange(param)
End If
End Sub
下面是RechargeRecordDAL类中的方法
''' <summary>
''' 插入充值记录
''' </summary>
''' <param name="Trans">在注册、充值时都要 事务</param>
''' <param name="RechargeRecordInfo">输入的充值信息</param>
''' <returns>受影响的行数</returns>
''' <remarks></remarks>
Public Function InsertRechargeRecord(ByVal Trans As SqlTransaction, ByVal RechargeRecordInfo As Entity.RechargeRecordInfo) As Integer Implements IDAL.IRechargeRecord.InsertRechargeRecord
Dim SQL As String = "Insert into RechargeRecord values(@CardNO,@StudentName,@RechargeDate,@RechargeTime,@Amount,@IsChecked,@Teacher)"
Dim param() As SqlParameter
param = {New SqlParameter("@CardNO", RechargeRecordInfo.CardNO), _
New SqlParameter("@StudentName", RechargeRecordInfo.StudentName), _
New SqlParameter("@RechargeDate", RechargeRecordInfo.RechargeDate), _
New SqlParameter("@RechargeTime", RechargeRecordInfo.RechargeTime), _
New SqlParameter("@Amount", RechargeRecordInfo.Amount), _
New SqlParameter("IsChecked", RechargeRecordInfo.ISChecked), _
New SqlParameter("Teacher", RechargeRecordInfo.Teacher)}
Return DBHelper.ExecuteNonQuery(Trans, CommandType.Text, SQL, param)
End Function
下面是CardDAL里的方法:
''' <summary>
''' 更新卡表里的金额 充值时或者注册时
''' </summary>
''' <param name="trans">更新金额 事务</param>
''' <param name="ConsumptionRecordInfo">传入的卡的信息</param>
''' <returns>受影响的行数</returns>
''' <remarks></remarks>
Public Function UpdateAmount(ByVal trans As SqlTransaction, ByVal ConsumptionRecordInfo As ConsumptionRecordInfo) As Integer Implements IDAL.ICards.UpdateAmount
Dim SQL As String = "Update Cards Set Amount=Amount-@Amount where CardNO=@CardNO"
Dim param() As SqlParameter
param = {New SqlParameter("@Amount", ConsumptionRecordInfo.ConsumptionAmount), _
New SqlParameter("@CardNO", ConsumptionRecordInfo.CardNO)}
Return DBHelper.ExecuteNonQuery(trans, CommandType.Text, SQL, param)
End Function
现在是RechargeBLL里的方法(注:里面还用到了其它的表,用来获取学习、卡等信息,还有就是有一个Verify类,用来帮助验证学生是否存在。):
Imports Entity
Imports DBFactory
Imports IDAL
Imports System.Data.SqlClient
Public Class RechargeBLL
Private IRechargeRecord As IRechargeRecord
Private ICard As ICards
Private IStudent As IStudent
Sub New()
ICard = DBAccess.CreatCardsDAL
IRechargeRecord = DBAccess.CreatRechargeRecordDAL
IStudent = DBAccess.CreatStudentDAL
End Sub
Public Function Recharge(ByVal RechargeRecordInfo As RechargeRecordInfo) As RechargeRecordInfo
Dim verify As New Verify
If verify.StudnetIfExist(RechargeRecordInfo.CardNO) = False Then
Throw New Exception("卡号不存在!")
Exit Function
End If
'获取该卡号学生的信息
Dim paramStudent As New StudentsInfo
paramStudent.CardNO = RechargeRecordInfo.CardNO
Dim returnStudentInfo As StudentsInfo
returnStudentInfo = IStudent.GetStudent(paramStudent)
'补充充值记录其余信息 充值卡号、金额已经在U层赋值
RechargeRecordInfo.StudentName = returnStudentInfo.StudentName
RechargeRecordInfo.RechargeDate = Today.ToString("yyyy-MM-dd")
RechargeRecordInfo.RechargeTime = TimeOfDay.ToString("HH:mm:ss")
RechargeRecordInfo.ISChecked = "未结账"
RechargeRecordInfo.Teacher = LoginBLL.Teacher
'实例化卡信息
Dim Card As New CardsInfo
Card.CardNO = RechargeRecordInfo.CardNO
Card.Amount = RechargeRecordInfo.Amount
'开始执行充值事务
Dim Conn As New SqlConnection(Configuration.ConfigurationManager.ConnectionStrings("strConnection").ConnectionString)
Dim Trans As SqlTransaction
Conn.Open()
Trans = Conn.BeginTransaction(IsolationLevel.ReadCommitted)
Try
ICard.Recharge(Trans, Card)
IRechargeRecord.InsertRechargeRecord(Trans, RechargeRecordInfo)
Trans.Commit()
Conn.Close()
Return RechargeRecordInfo
Catch ex As Exception
Trans.Rollback()
Conn.Close()
Throw New Exception("充值失败!")
End Try
End Function
End Class
其实想验证一下,事务有没有起作用,很简单,只要设置一个断点(事务执行到一半),到这个断点然后强制终止程序,可以看到,数据库里面的数据没有变化,说明事务起作用了。
这是我的想法,希望大家给出您的宝贵意见!