Spring.NET教程(十六)事务管理(应用篇)

目前有很多种数据访问技术。在.net FCL中,有三类API可以执行事务管理,分别是ADO.NET、企业服务和System.Transactions。其它的数据访问技术,如对象关系映射(object relational mappers)和结果集映射(result-set mapping)等等的应用也很广泛,每种技术也都有自己的事务管理API。事务管理的代码一般是直接和各种事务API绑定在一起的,所以在开发时必须根据所用的具体技术来决定采用哪种API。但是,这种代码与事务API的紧耦合决定了很难通过简单的重构来解决更换数据访问技术的问题。而 Spring.NET的事务框架允许在各种数据访问技术之上使用相同的API。通过配置或者集中的编程方式,可以很容易的更换后台事务API,而不需要对代码进行“大修”。

我们可以用业界公认的最佳方式来建立一种数据访问机制。Martin Fowler的著作《Patterns of EntERPrise Application Architecture》讲到了许多在实际应用中非常成功的数据访问方法。其一便是在应用程序架构中引入一个数据访问层。数据访问层不仅要考虑到与不同的数据库和数据访问技术的兼容性,而且职责要严格限制在数据访问功能上。数据访问层应该只包含数据访问对象(DAO)以及“创建/获取/更新/删除”(CRUD,Create/Retrieve/Update/Delete)的操作,不应该涉及任何业务逻辑。业务逻辑应该位于单独的业务服务层,并且需要与一或多个DAO协作来完成高层次的用户功能。

为了在事务中“要么全执行要么全不执行”这些用户功能,事务环境(transaction context)就应该由业务服务层(或某个“更高”的层次)控制。在实现上,一个很重要的细节是如何让DAO了解在其它层次中开始的“外部”事务。如果让DAO自己负责连接和事务的管理,就把问题看的过于简单化了,因为此时每个DAO都会执行自己的事务/资源管理,所以无法在同一事务中执行多个DAO操作。我们需要一种有效的手段,将连接/事务成对的从业务服务层传递给DAO。方法有很多种,最不具侵入性的就是将连接/事务作为方法参数显式的传递给DAO。另一种方法是将连接/事务放在线程本地存储内。不管使用哪种方法,只要在用ADO.NET,就必须得自己创建一个基础框架来完成这个任务。

 但是,等一下,企业服务不是能解决这个问题吗还有System.Transactions命名空间呢?关于这个嘛,答案是对...也不对。企业服务确实能够让我们在事务环境中使用“原生”的ADO.NET在同一事务中执行多个DAO操作。但它的缺点是必须通过MS-DTC(Microsoft Distributed Transaction Coordinator)使用分布式(全局的)事务。如果只为了使用全局事务就必须依赖MS-DTC,那应用程序在性能上就会大打折扣了。

使用.net2.0新增System.Transactions命名空间下的TransactonScope类时,也有相同的问题。TransactonScope类的目的实际上是用using语句使一段代码成为事务性代码。只要访问事务性的资源,using语句中的普通ADO.NET代码就会在一个ADO.NET本地事务中运行。但是,System.Transactions(和数据库)的“神奇“之处在于,如果需要访问第二个事务性资源,本地事务就会升级为分布式事务。这个过程叫做PSPE(Promotable Single Phase Enlistment)。此外,还需提醒读者:在同一个数据库上使用同一连接字符串打开第二个连接,就会使本地事务升级为分布式事务。所以,如果每个 DAO都执行自己的连接管理,那就完了:本地事务会突然升级为分布式事务!如果应用程序只使用一个数据库,要想避免这个问题,就必须将唯一的连接对象传递给系统中的所有DAO。另外要注意的是某些数据库不支持PSPE,就算用单个数据库连接也会使用分布式事务(比如Oracle)。

Spring.NET的声明式事务管理功能非常强大。在数据库事务领域,讨论声明式事务管理的话题并不是很多,因为现在开发人员已经可以不再直接用凌乱繁复的事务API来进行事务管理了,而是可以通过在类和方法上应用某些特性来进行。但是在FCL中,只有企业服务提供了这一功能。 Spring.NET则填补了这个空白不管使用哪种事务管理技术:ADO.NET,还是(最常用的)System.Transactions,都可以使用声明式的事务管理。另外,企业服务也有自己的问题,举例来说,如果需要为查询/读取操作和创建/更新/删除操作设置不同的隔离等级,就必须将这两组操作分隔在不同的类中实现,因为在企业服务中,声明式事务的元数据只对类有效。但总的来说,企业服务,特别是在XP sp2和Server 2003中新出现的“无组件服务”,还有在应用程序进程内驻留的功能都是相当不错的。不过,虽然有这些优点,企业服务仍尚未在开发社区中引起很大的关注。

Spring.net事务管理的宗旨,就是要减轻FCL或第三方数据访问技术给开发人员带来的这些“痛苦”。它支持声明式事务管理,可以用配置方式获取事务选项的元数据目前支持两种声明方式:在代码中用.NET特性声明;在IoC容器中用XML声明。

最后,Spring.NET事务管理还允许在同一事务中使用不同的数据访问技术例如混合使用ADO.NET和NHibernate。

好了,再讲估计就有点烦人了,现在我们来看代码。

在Spring.NET中,提供了以下实现类:

AdoPlatformTransactionManager- 基于本地ADO.NET的事务。

ServiceDomainPlatformTransactionManager- 由企业服务提供的分布式事务管理器。

TxScopePlatformTransactionManager- 由System.Transactions提供的本地/分布式的事务管理器。

ITransactionDefinition接口封装了以下信息:

Isolation:该事务对其它事务操作的隔离级别。例如,用来表示某个事务是否能看到其它事务写入的、但尚未提交的信息。

Propagation:一般情况下,TransactionScope内的代码都会在为其指定的事务中运行。但是,该属性可用来设置如果某个事务环境已经存在时,该事务内的方法是否要执行:比如说,是简单的让它在现有事务中继续运行呢(这是一般情况),还是挂起现有事务然后创建一个新事务来运行。

Timeout:在超时(并且被事务基础框架自动回滚之前)前该事务可以运行多久。

Read-only状态:只读的事务不会修改任何数据。在某些情况下(比如使用NHibernate时),只读事务能显著提高性能。

让我们从实现代码中学习Spring.NET事务管理的机制。

准备条件:数据中建了2张表,如图1

UserTable为存储用户信息的表,AccountTable为存储用户账号的信息。

数据库访问层:

AccountDao

public interface IAccountDao

{

void Create(string name, string userName);

void Delete(string userName);

}

public class AccountDao : AdoDaoSupport, IAccountDao

{

public void Create(string name, string userName)

{

AdoTemplate.ExecuteNonQuery(CommandType.Text,

String.Format("INSERT INTO AccountTable (UserName,AccountName) VALUES (‘{0}‘, ‘{1}‘)", userName, name));

}

public void Delete(string userName)

{

AdoTemplate.ExecuteNonQuery(CommandType.Text,

String.Format("DELETE FROM AccountTable WHERE UserName = ‘{0}‘", userName));

}

}

UserDao

public interface IUserDao

{

void Create(string name, int age);

void Delete(string name);

DataSet Get(string name);

}

public class UserDao : AdoDaoSupport, IUserDao

{

public void Create(string name, int age)

{

AdoTemplate.ExecuteNonQuery(CommandType.Text,

string.Format("INSERT INTO UserTable (UserName,UserAge) VALUES (‘{0}‘, {1})", name, age));

}

public void Delete(string name)

{

AdoTemplate.ExecuteNonQuery(CommandType.Text,

string.Format("DELETE FROM UserTable WHERE UserName = ‘{0}‘", name));

}

public DataSet Get(string name)

{

return AdoTemplate.DataSetCreate(CommandType.Text, 

string.Format("SELECT * FROM UserTable WHERE UserName = ‘{0}‘", name));

}

}

业务处理层:当我们插入用户信息后,需要为这个用户建立一个账号,当我们删除用户时,需要将帐号信息一起删除。这里我们就会用到了事务。

UserService

public interface IUserService

{

void SaveData(string name, int age, string accountName);

void DeleteData(string name);

DataSet Get(string name);

}

public class UserService : IUserService

{

public IUserDao UserDao { get; set; }

public IAccountDao AccountDao { get; set; }

[Transaction]

public void SaveData(string name, int age, string accountName)

{

UserDao.Create(name, age);

AccountDao.Create(accountName, name);

}

[Transaction]

public void DeleteData(string name)

{

UserDao.Delete(name);

AccountDao.Delete(name);

}

[Transaction(ReadOnly = true)]

public DataSet Get(string name)

{

return UserDao.Get(name);

}

}

配置:

Dao.XML

<?XML version="1.0" encoding="utf-8" ?>

<objects xmlns="http://www.springFramework.net" 

 xmlns:db="http://www.springframework.net/database" 

 xmlns:tx="http://www.springframework.net/tx">

<db:provider id="DbProvider"

provider="SqlServer-1.1"

connectionString="Server=(local);Database=SpringLesson16;Uid=sa;Pwd=;Trusted_Connection=False"/>

<object id="userDao" type="Dao.UserDao, Dao">

<property name="AdoTemplate" ref="adoTemplate"/>

</object>

<object id="accountDao" type="Dao.AccountDao, Dao">

<property name="AdoTemplate" ref="adoTemplate"/>

</object>

<object id="userService" type="Service.UserService, Service">

<property name="UserDao" ref="userDao"/>

<property name="AccountDao" ref="accountDao"/>

</object>

<object id="adoTemplate" type="Spring.Data.Core.AdoTemplate, Spring.Data">

<property name="DbProvider" ref="DbProvider"/>

<property name="DataReaderWrapperType" value="Spring.Data.Support.NullMappingDataReader, Spring.Data"/>

</object>

<!--事务管理器-->

<object id="transactionManager"

 type="Spring.Data.Core.AdoPlatformTransactionManager, Spring.Data">

<property name="DbProvider" ref="DbProvider"/>

</object>

<!--事务切面-->

<tx:attribute-driven/>

</objects>

App.config

<?XML version="1.0" encoding="utf-8" ?>

<configuration>

<configSections>

<sectionGroup name="spring">

<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/>

<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core"/>

<section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core"/>

</sectionGroup>

</configSections>

<spring>

<parsers>

<parser type="Spring.Data.Config.DatabaseNamespaceParser, Spring.Data"/>

<parser type="Spring.Transaction.Config.TxNamespaceParser, Spring.Data"/>

</parsers>

<context>

<resource uri="assembly://Dao/Dao/Dao.XML"/>

</context>

</spring>

</configuration>

我们新建一个单元测试:

TransactionTest

[TestFixture]

public class TransactionTest

{

[Test]

public void AdoTransaction()

{

IApplicationContext ctx = ContextReGIStry.GetContext();

IUserService service = (IUserService)ctx.GetObject("userService");

service.SaveData("刘冬", 26, "1233456");

}

}

输出效果:两条数据已经插入数据库(图2)

我们修改一下UserService的DeleteData方法,在调用 UserDao.Delete(name)以后抛出异常,测试数据是否回滚。

DeleteData

[Transaction]

public void DeleteData(string name)

{

UserDao.Delete(name);

new Exception("测试数据是否回滚");

AccountDao.Delete(name);

}

如果数据没有回滚,则我们得到的数据为UserTable表中没有数据,AccountTable表中有数据。

TransactionTest

public class TransactionTest

{

[Test]

public void AdoTransaction()

{

IApplicationContext ctx = ContextReGIStry.GetContext();

IUserService service = (IUserService)ctx.GetObject("userService");

service.DeleteData("刘冬");

}

}

输出效果:

图3

我们发现数据已经回滚。

Spring.net框架帮我们很好的管理了事务。但在我们的编码过程中经常使用到try...catch语句。要是在DeleteData方法中加入try...catch语句会回滚吗?我们修改一下UserService的SaveData方法

SaveData

 [Transaction]

public void SaveData(string name, int age, string accountName)

{

try

{

UserDao.Create(name, age);

AccountDao.Create(accountName, name);

}

catch (Exception ex)

{

Console.WriteLine(ex);

}

}

当前的数据库AccountTable表中已经存在字段为AccountName的“123456”记录。因为AccountName字段是主键,我再插入一条数据就会出现异常。

AdoTransaction

[Test]

public void AdoTransaction()

{

IApplicationContext ctx = ContextReGIStry.GetContext();

IUserService service = (IUserService)ctx.GetObject("userService");

service.SaveData("刘冬冬", 27, "1233456");

}

输出效果:

 图4

我们发现在使用try...catch语句以后并没有回滚。从以上的效果中,我们可以得出结论:使用try...catch的异常叫作“运行期异常”,Spring.NET的事务管理默认是不对运行期异常回滚的。

如果要实现没有try...catch语句不回滚事务,我们需要在方法上标记[Transaction(NoRollbackFor = new Type[] { typeof(Exception) })]。NoRollbackFor 属性是Type数组,意思是运到Exception异常就不回滚,您也可以在增加其它的异常条件,如typeof(ArithmeticException)。这样,当遇到标有包含NoRollbackFor 属性的异常时,就不进行回滚。

原文地址:https://www.cnblogs.com/lzhdim/p/9910154.html

时间: 2024-10-29 19:11:49

Spring.NET教程(十六)事务管理(应用篇)的相关文章

RabbitMQ入门教程(十六):RabbitMQ与Spring集成

原文:RabbitMQ入门教程(十六):RabbitMQ与Spring集成 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/vbirdbest/article/details/78805591 分享一个朋友的人工智能教程.比较通俗易懂,风趣幽默,感兴趣的朋友可以去看看. 简介 集成示例基本目录结构 一:引入相关依赖 引入Spring核心的依赖和spring-rabbit依赖,注意sprin

无废话ExtJs 入门教程十六[页面布局:Layout]

无废话ExtJs 入门教程十六[页面布局:Layout] extjs技术交流,欢迎加群(201926085) 首先解释什么是布局: 来自百度词典的官方解释:◎ 布局 bùjú: [distribution;layout] 对事物的全面规划和安排,布:陈设:设置. 我对布局理解是“把**东西放在**位置显示”[动词]. ok,我们这节课就讲一下怎么样把 ExtJs 的组件,放到我们想放置的位置. 一.常用布局 (1)ContainerLayout:默认布局方式,其他布局继承该类进行扩展功能.显示:

Unity3D脚本中文系列教程(十六)

Unity3D脚本中文系列教程(十五) ◆ function OnPostprocessAudio (clip:AudioClip):void 描述:◆  function OnPostprocessGameObjectWithUserProperties (root : GameObject, propNames : string[], values : object[]) : void 描述:在导入文件时,为每个至少附加了一个用户属性的游戏物体调用propNames是一个string[ ],

spring+mybatis之声明式事务管理初识(小实例)

前几篇的文章都只是初步学习spring和mybatis框架,所写的实例也都非常简单,所进行的数据访问控制也都很简单,没有加入事务管理.这篇文章将初步接触事务管理. 1.事务管理 理解事务管理之前,先通过一个例子讲一下什么是事务管理:取钱. 比如你去ATM机取1000块钱,大体有两个步骤:首先输入密码金额,银行卡扣掉1000元钱:然后ATM出1000元钱.这两个步骤必须是要么都执行要么都不执行.如果银行卡扣除了1000块但是ATM出钱失败的话,你将会损失1000元:如果银行卡扣钱失败但是ATM却出

Spring 使用注解方式进行事务管理

使用步骤: 步骤一.在spring配置文件中引入<tx:>命名空间<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation

Spring中实现多数据源事务管理

Spring中实现多数据源事务管理 前言 由于项目中引入了多个数据源,并且需要对多个数据源进行写操作,那么多数据源的事务管理自然成了不可避免的问题,这也让我对@Transactional注解有了进一步的理解(但实际上也并不是非常深入) 然而这是一个演进的过程,刚开始项目中并没有使用@Transactional指定具体的TransactionManager,所以新增一个数据源后,对原有的事务产生了影响了,这也是偶尔在一次测试报错而结果没有回滚之后才发现的,遂对于@Transactional注解的一

【转】Spring 使用注解方式进行事务管理

Spring 使用注解方式进行事务管理 原文链接 http://www.cnblogs.com/younggun/archive/2013/07/16/3193800.html#top 使用步骤: 步骤一.在spring配置文件中引入<tx:>命名空间 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-i

Spring入门第二十六课

Spring中的事务管理 事务简介 事务管理是企业级应用程序开发中必不可少的技术,用来确保数据的完整性和一致性. 事务就是一系列的动作,他们被当做一个单独的工作单元,这些动作要么全部完成,要么全部不起作用. 事务的四个关键属性(ACID) -原子性(atomicity):事务是一个原子操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用. -一致性(consistency):一旦所有事务动作完成,事务就被提交,数据和资源就处于一种满足业务规则的一致性状态中. -隔离性(is

spring boot配置mybatis和事务管理

spring boot配置mybatis和事务管理 一.spring boot与mybatis的配置 1.首先,spring boot 配置mybatis需要的全部依赖如下: <!-- Spring Boot 启动父依赖 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId>