事务处理简介
1.1什么是事务处理
事务是一组组合成逻辑工作单元的数据库操作,虽然系统中可能会出错,但事务将控制和维护每个数据库的一致性和完整性。
如果在事务过程中没有遇到错误,事物中的所有修改都将永久成为数据库的一部分。
如果遇到错误,则不会对数据库做出任何修改。
1.2事务处理过程
1. 开始一个事务。进入“事物待命”状态。
2. 在“事务待命”状态,记录事务中改变的数据库记录。此改变不能直接改变数据库中的值,必须先用一个顺序的“事务日志”记录在一边。同时,对于要改变的原始记录加锁,让其它用户无法读和写。如果记录已经被其它事务加锁,则报错。
3. 在“事务待命”,如果用户给出commit transaction 命令,则进入“事务拷贝”状态,拷贝所有的加锁的记录成备份。
4. 上面3执行完,则进入“事务更新”状态,用“事务日志”中记录一一更新实际的数据库记录。
5. 上面4执行完,则进入“事务结束”状态,释放所有的记录锁,然后抛弃“事务日志”和备份的原始数据库记录。
6. 上面5做完后,事务被删除。
但是,最为关键的是,事务系统必须执行以下过程:一但数据库由于软件、硬件问题发生故障,重启动后,一旦有事务没有正常删除,则:
7. 如果在“事务待命”、“事务结束”状态,则重新从5中结束事务的动作开始执行。
8.如果在“事务更新”状态,则重新从4开始更新记录,但继续想下执行。结果,虽然系统崩溃过,但事务仍然能正常提交。
1.3事务处理举例
1.4事务处理有关事项
事务处理的关键是在提交事务或者取消事务时,万一系统崩溃了,数据库再次启动时,仍然需要保持数据库逻辑一致性。
应用中包含的事务应当尽量让它“瞬间”完成,避免在比较忙时造成用户进程的互锁。
Informix、Oracle、DB2等数据库德实际事务处理流程更复杂,目的是具有更好的抵抗系统错误性质,因为事务保护是业务系统安全稳定的最后一道防线。
事务处理方法
直接写入SQL
通过ADO.NET实现
COM+事务(分布式事务)
2.1 直接写入SQL
在存储过程中使用BEGIN TRAN,COMMIT TRAN,ROLLABACK TRAN 实现
优点:
所有事务逻辑包含在一个单独的调用中
拥有运行一个事物的最佳性能
独立于应用程序
限制:
事务上下文仅存在于数据库调用中
数据库代码与数据库系统有关
DEMO1 直接写入SQL
--SqlAction--
CREATE PROCEDURE [SqlAction] AS
begin TRAN
declare @UserInfoError int
delete from [tbUserInfo] where username='邵志东'
select @UserInfoError =@@error
if(@UserInfoError =0)
COMMIT TRAN
else
ROLLBACK TRAN
GO
private void btnOK_Click(object sender, System.EventArgs e)
{
SqlConnection myConnection = new SqlConnection("Server=.;database=myDataBase;UID=sa;pwd=sa;");
myConnection.Open();
SqlCommand myCommand = new SqlCommand();
myCommand.Connection=myConnection;
myCommand.CommandType = CommandType.StoredProcedure;
myCommand.CommandText = "SqlAction";
myCommand.ExecuteNonQuery();
myConnection.Close();
}
2.2 ADO.NET 事务处理
在ADO.NET中,可以使用Connection和Transaction对象来控制事务。若要执行事务,请执行下列操作。
调用Connection对象的BeginTwansaction方法来标记事务的开始
将Transaction对象分配给要执行的Command的Transaction属性。
执行所需的命令
调用Transaction对象的Commit方法来完成事务,或调用Rpllback方法来取消事务。
DEMO 2 ADO.NET 事务处理
private void btnOK_Click(object sender, System.EventArgs e)
{
SqlConnection myConnection = new SqlConnection("Server=(local);Initial Catalog=Northwind;uid=sa;pwd=sa;");
myConnection.Open();
// 启动一个事务
SqlTransaction myTrans = myConnection.BeginTransaction();
// 为事务创建一个命令
SqlCommand myCommand = new SqlCommand();
myCommand.Connection=myConnection;
myCommand.Transaction = myTrans;
try
{
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (110, 'Description')";
myCommand.ExecuteNonQuery();
//myTrans.Commit();
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (111, 'Description')";
myCommand.ExecuteNonQuery();
myTrans.Commit();
Response.Write("成功写入记录!");
}
catch(Exception Ex)
{
myTrans.Rollback();
Response.Write(Ex.ToString());
Response.Write("写入数据库失败!");
}
finally
{
myConnection.Close();
}
}
ADO.NET事务处理
优点:
简单性
和数据库事务差不多快
独立于数据库,不同数据库的专有代码被隐藏了
缺点:
事务不能跨越多个数据库连接
事务执行在数据库连接层上,所以需要在事务过程中维护一个数据库连接。
2.3 COM+ 事务
一般的数据库事务控制要求事务里所有的操作必须在同一个数据库内,这样在出现错误的时候才能回滚(RllBack)到初始状态。这就存在一个问题,在分布式应用程序中,我们往往需要同时操作多个数据库,使用数据库本身的事务处理,很难满足程序对事务控制的要求。在COM+中,提供了完整的事务服务,我们可以利用它来完成在分布式应用程序中的事务控制。
创建参与自动事务的类
1. 将TransactionAttribute类应用于您的类,指定组件请求的自动事务类型。事务类型必须是TransactionOption枚举的成员。
2. 从ServicedComponent类派生您的类。SercivedComponent是所有使用COM+服务的类的基类。
3. 用强名称(stiong name)标记(sign)程序集(阿瑟sembly),确保程序集包含唯一的密钥对。
4. 在COM+catalog中注册包含您的类的程序集。
定义一个COM+事务处理的类
[Transaction(TransactionOption.Required)]
Public class
DataAccess:System。EnterpriseServices。ServicedComponent
{
}
TransactionOption枚举类型支持5个COM+值
Disabled 忽略当前上下文中的任何事务。
NotSupported 使用非受控事务在上下文中创建组件。
Required 如果事务存在则共享事务,并且如有必要则创建新事务。
RequiredNew使用新事务创建组建,而与当前上下文的状态无关。
Supported 如果事务存在,则共享该事务。
强名称
为使该组件正确运行,该组件必须有一个强名称。生成一个强名称,然后使用该强名称对程序集进行签名。为此,请按照下列步骤操作:
在命令提示符下,键入sn.exe-k snEnterprose.snk复制到您的项目文件中。
在Assemblyinfo.vc中,将以下代码添加到其它程序集属性语句之前后之后:[assembly:AssemblyKeyFileAttribute(”..//..//snEnterprise.snk”)]
进行保存,然后生成您的项目
使用regsvcs.exe将Dll注册到COM+Services里面。
DEMO 3 使用COM+事务处理
注意事项
确保使用COM+服务的所有项目都是一个强名称。
使用COM+服务的所有类都是必须继承服务组件。服务组件位于System.EnterpriseServices 命名空间中。
进行调试时,事务在提交或终止前可能会超时。要避免出现超时,请在事务属性中使用一个超时属性。在下面的示例中,在完成任何事务时,关联的方法在超时前都有1,200秒的执行时间。[Transaction(TransactionOption.Requried,timeout:=1022)]
异常处理
什么是异常处理
异常是正在执行的程序遇到的任何错误情形或者以外行为。
很多原因都是可以引起异常,例如,代码中错误、操作系统资源不可用、公共语言运行时(conmmon language runtime)中的以外情况等等
然而应用程序能够从上述的一些情况中恢复执行,但是大多数运行时异常不可恢复的。在这种情况下,需要一种有效的方法来处理这些异常并给调用者提供相同的异常。
用结构化的异常处理方法来处理异常
在.net web 服务中,对异常处理支持的关键点是由 try……catch……finally语句提供的。
关键字try放在可能抛出异常的普通处理代码块之前。
关键字catch放在异常处理代码块之前。
关键字finally放在那些经常在异常处理后还需要执行的代码块之前。
一旦异常从try代码块中抛出,程序流切换到后面的第一个catch代码块。
异常类
Exception 所有异常对象的基类
SystemException 运行时产生的所有错误的基类
IndexOutOfRangeException 当一个数组的下标超出范围是运行时引发
NullRefetenceException 当一个空对象被引用是运行时引发
InvaliOperationException当对方法的调用对对象的当前状态无效时,由某些方法引用。
ArgumentException所有参数异常的基类
ArgumentNullException 在参数为空(不允许)的情况下,由方法引用。
ArgumentOutOfRangeException 当参数不在一个给定范围之内时,由方法引发。
IneropException目标在或发生在CLR外面环境中的异常的基类
ComException 包含COM类的HRESULT信息的异常
SEHException 封装win32结构异常处理信息的异常
DEMO4 结构化异常处理
优化异常
理解异常是一定会发生的
大多数的软件系统都不是百分之百可靠的
要站在异常一定可能会发生的角度来编写异常处理程序,应对程序有可能发生的错误。
建立一个良好的异常处理策略。
处理未预料的异常
确保所有程序的入口都使用了try-catch
在catch中截获所有的异常。
异常处理注意事项
当引发异常时,要提供有意义的文本
要引发异常仅当条件是真正异常;也就是当一个正常的返回值不满足时。
如果你的方法或属性被传递一个坏参数,要引发一个ArgumentException异常
当调用操作不适合对象的当前状态时,要引发一个InvalidOoerationException异常。
要引发最合适的异常
要是链接异常,他们允许你跟踪异常树。
不要为正常或预期的错误使用异常
不要为流程的正常控制使用异常
不要在方法中引发NullReferenceException或IndexOutOfRangeException异常
异常处理技术
记录异常
在文件中记录异常
在数据库中记录异常
在eventlog中记录异常
发送email通知异常
异常产生时,用友好(user-发riendly) 的方式通知用户
处理错误
Page_Error事件
Application_Error事件
利用配置文件,自定义错误页面
<customErrors defaultRedirect=”url” mode”RemoteOnly”>
<error statusCode=”code” redirect=”url”></errpr>
</customErrors>
DEMO5 编写到windows错误日志把错误发送邮件到管理员自定义错误页面
protected void Application_Error(Object sender, EventArgs e)
{
//把错误信息发送到作者
string strPageUrl = Request.Path;
Exception ErrorInfo =Server.GetLastError();
//Server.ClearError();
string strMessage = "Url:" + strPageUrl + "</br>";
strMessage = strMessage + " Error: ";
strMessage = strMessage + ErrorInfo.ToString() + "</br>";
MailMessage Mymessage = new MailMessage();
Mymessage.To = "wkjs@163.com";
Mymessage.From = "wkjs@163.com";
Mymessage.Subject = "ASP.NET Error";
Mymessage.BodyFormat = MailFormat.Text;
Mymessage.Body = strMessage;
SmtpMail.Send(Mymessage);
}
private void btnCal_Click(object sender, System.EventArgs e)
{
try
{
int nInput = int.Parse(tbInput.Text);
long nResult=1;
for(int i=1;i<=nInput;i++)
checked{nResult *= i;}
lbResult.Text = nResult.ToString();
}
catch(OverflowException)//溢出错误异常处理
{
throw new Exception("输入的数字太大,我受不了了!");
}
catch(FormatException)//输入字符格式错误异常处理
{
Response.Write("嘿!要输入整数!");
}
catch(Exception ee)
{
Response.Write("发生错误,信息为:"+ee.Message);
}
}
private void WebForm1_Error(object sender, System.EventArgs e)
{
string strMessage = Server.GetLastError().Message;
//Response.Write(strMessage);
Server.ClearError();
//以下把信息写入windows日志
//要把aspnet用户添加到管理员组中,以便有写注册表权限
if(!EventLog.SourceExists("mySource"))
EventLog.CreateEventSource("mySource","myLog");
EventLog Event = new EventLog();
Event.Source = "mySource";
Event.WriteEntry(strMessage,EventLogEntryType.Warning);
//EventLog.Delete("myLog");
throw new Exception("我处理不了,请最高人民法院处理!");
}