【.Net设计模式系列】工作单元(Unit Of Work)模式 ( 二 )
回顾
在上一篇博客【.Net设计模式系列】仓储(Repository)模式 ( 一 ) 中,通过各位兄台的评论中,可以看出在设计上还有很多的问题,在这里特别感谢 @横竖都溢 @ 浮云飞梦 2位兄台对博文中存在的问题给予指出,并提供出好的解决方案,同时也感谢其他园友的支持。欢迎各位园友对博文中出现的错误或者是设计误区给予指出,一方面防止“误人子弟”,另一方面则可以让大家共同成长。
对于上一篇博客,只是给大家提供了一种对于小型项目数据访问层的一种实现方式,通过Sql语句和传递参数来实现CRUD。并未达到真正意义上的解耦。特此在本篇继续完善。
理论介绍
在进行数据库添加、修改、删除时,为了保证事务的一致性,即操作要么全部成功,要么全部失败。例如银行A、B两个账户的转账业务。一方失败都会导致事务的不完整性,从而事务回滚。而工作单元模式可以跟踪事务,在操作完成时对事务进行统一提交。
理论参考:http://martinfowler.com/eaaCatalog/unitOfWork.html
具体实践
首先,讲解下设计思想:领域层通过相应的库实现泛型仓储接口来持久化聚合类,之后在抽象库中将对泛型仓储接口提供基础实现,并将对应的实体转化为SQl语句。这样领域层就不需要操作Sql语句即可完成CRUD操作,同时使用工作单元对事务进行统一提交。
1)定义仓储接口,包含基本的CRUD操作及其重载不同的查询
- public interface IRepository<T>
- {
- /// <summary>
- /// 插入对象
- /// </summary>
- /// <param name="entity"></param>
- int Insert(T entity);
- /// <summary>
- /// 更新对象
- /// </summary>
- /// <param name="entity"></param>
- /// <param name="predicate"></param>
- int Update(T entity, Expression<Func<T, bool>> express);
- /// <summary>
- /// 删除对象
- /// </summary>
- /// <param name="predicate"></param>
- int Delete(Expression<Func<T, bool>> express = null);
- /// <summary>
- /// 查询对象集合
- /// </summary>
- /// <param name="predicate"></param>
- /// <returns></returns>
- List<T> QueryAll(Expression<Func<T, bool>> express = null);
- /// <summary>
- /// 查询对象集合
- /// </summary>
- /// <param name="index"></param>
- /// <param name="pagesize"></param>
- /// <param name="order"></param>
- /// <param name="asc"></param>
- /// <param name="express"></param>
- /// <returns></returns>
- List<T> QueryAll(int index,int pagesize,List<PropertySortCondition> orderFields, Expression<Func<T, bool>> express = null);
- /// <summary>
- /// 查询对象集合
- /// </summary>
- /// <param name="type"></param>
- /// <param name="predicate"></param>
- /// <returns></returns>
- List<object> QueryAll(Type type, Expression<Func<T, bool>> express = null);
- /// <summary>
- /// 查询对象
- /// </summary>
- /// <param name="predicate"></param>
- /// <returns></returns>
- T Query(Expression<Func<T, bool>> express);
- /// <summary>
- /// 查询数量
- /// </summary>
- /// <param name="predicate"></param>
- /// <returns></returns>
- object QueryCount(Expression<Func<T, bool>> express = null);
- }
其次,对仓储接口提供基本实现,这里由于使用了Lambda表达式,所以就需要进行表达式树的解析(这里我希望园友能自己去研究)。
- public abstract class BaseRepository<T> : IRepository<T>
- where T:class,new()
- {
- private IUnitOfWork unitOfWork;
- private IUnitOfWorkContext context;
- public BaseRepository(IUnitOfWork unitOfWork, IUnitOfWorkContext context)
- {
- this.unitOfWork = unitOfWork;
- this.context = context;
- }
- Lazy<ConditionBuilder> builder = new Lazy<ConditionBuilder>();
- public string tableName {
- get
- {
- TableNameAttribute attr= (TableNameAttribute)typeof(T).GetCustomAttribute(typeof(TableNameAttribute));
- return attr.Name;
- }
- }
- /// <summary>
- /// 插入对象
- /// </summary>
- /// <param name="entity"></param>
- public virtual int Insert(T entity)
- {
- Func<PropertyInfo[], string, IDictionary<string, object>, int> excute = (propertys, condition, parameters) =>
- {
- List<string> names = new List<string>();
- foreach (PropertyInfo property in propertys)
- {
- if (property.GetCustomAttribute(typeof(IncrementAttribute)) == null)
- {
- string attrName = property.Name;
- object value = property.GetValue(entity);
- names.Add(string.Format("@{0}", attrName));
- parameters.Add(attrName, value);
- }
- }
- string sql = "Insert into {0} values({1})";
- string combineSql = string.Format(sql, tableName, string.Join(",", names), builder.Value.Condition);
- return unitOfWork.Command(combineSql, parameters);
- };
- return CreateExcute<int>(null, excute);
- }
- /// <summary>
- /// 修改对象
- /// </summary>
- /// <param name="entity"></param>
- /// <param name="express"></param>
- public virtual int Update(T entity, Expression<Func<T, bool>> express)
- {
- Func<PropertyInfo[], string, IDictionary<string, object>, int> excute = (propertys, condition, parameters) =>
- {
- List<string> names = new List<string>();
- foreach (PropertyInfo property in propertys)
- {
- if (property.GetCustomAttribute(typeof(IncrementAttribute)) == null)
- {
- string attrName = property.Name;
- object value = property.GetValue(entity);
- names.Add(string.Format("{0}=@{1}", attrName, attrName));
- parameters.Add(attrName, value);
- }
- }
- string sql = "update {0} set {1} where {2}";
- string combineSql = string.Format(sql, tableName, string.Join(",", names), builder.Value.Condition);
- return unitOfWork.Command(combineSql, parameters);
- };
- return CreateExcute<int>(express, excute);
- }
- /// <summary>
- /// 删除对象
- /// </summary>
- /// <param name="express"></param>
- public virtual int Delete(Expression<Func<T, bool>> express = null)
- {
- Func<PropertyInfo[], string, IDictionary<string, object>, int> excute = (propertys, condition, parameters) =>
- {
- string sql = "delete from {0} {1}";
- string combineSql = string.Format(sql, tableName, condition);
- return unitOfWork.Command(combineSql, parameters);
- };
- return CreateExcute<int>(express, excute);
- }
- /// <summary>
- /// 查询对象集合
- /// </summary>
- /// <param name="express"></param>
- /// <returns></returns>
- public virtual List<T> QueryAll(Expression<Func<T, bool>> express = null)
- {
- Func<PropertyInfo[], string, IDictionary<string, object>, List<T>> excute = (propertys, condition, parameters) =>
- {
- string sql = "select {0} from {1} {2}";
- string combineSql = string.Format(sql, string.Join(",", propertys.Select(x => x.Name)), tableName, condition);
- return context.ReadValues<T>(combineSql, parameters);
- };
- return CreateExcute<List<T>>(express, excute);
- }
- /// <summary>
- /// 查询对象集合(分页)
- /// </summary>
- /// <param name="index"></param>
- /// <param name="pagesize"></param>
- /// <param name="order"></param>
- /// <param name="asc"></param>
- /// <param name="express"></param>
- /// <returns></returns>
- public virtual List<T> QueryAll(int index,int pagesize,List<PropertySortCondition> orderFields,Expression<Func<T, bool>> express = null)
- {
- Func<PropertyInfo[], string, IDictionary<string, object>, List<T>> excute = (propertys, condition, parameters) =>
- {
- if (orderFields == null) { throw new Exception("排序字段不能为空"); }
- string sql = "select * from (select {0} , ROW_NUMBER() over(order by {1}) as rownum from {2} {3}) as t where t.rownum >= {4} and t.rownum < {5}";
- string combineSql = string.Format(sql, string.Join(",", propertys.Select(x => x.Name)),string.Join(",", orderFields), tableName, condition, (index - ) * pagesize + , index * pagesize);
- return context.ReadValues<T>(combineSql, parameters);
- };
- return CreateExcute<List<T>>(express, excute);
- }
- /// <summary>
- /// 查询对象集合
- /// </summary>
- /// <param name="type"></param>
- /// <param name="express"></param>
- /// <returns></returns>
- public virtual List<object> QueryAll(Type type, Expression<Func<T, bool>> express = null)
- {
- Func<PropertyInfo[], string, IDictionary<string, object>, List<object>> excute = (propertys, condition, parameters) =>
- {
- string sql = "select {0} from {1} {2}";
- string combineSql = string.Format(sql, string.Join(",", propertys.Select(x => x.Name)), tableName, condition);
- return context.ReadValues(combineSql, type, parameters);
- };
- return CreateExcute<List<object>>(express, excute);
- }
- /// <summary>
- /// 查询对象
- /// </summary>
- /// <param name="express"></param>
- /// <returns></returns>
- public virtual T Query(Expression<Func<T, bool>> express)
- {
- Func<PropertyInfo[], string, IDictionary<string, object>, T> excute = (propertys, condition, parameters) =>
- {
- string sql = "select {0} from {1} {2}";
- string combineSql = string.Format(sql, string.Join(",", propertys.Select(x => x.Name)), tableName, condition);
- return context.ExecuteReader<T>(combineSql, parameters);
- };
- return CreateExcute<T>(express, excute);
- }
- /// <summary>
- /// 查询数量
- /// </summary>
- /// <param name="express"></param>
- /// <returns></returns>
- public virtual object QueryCount(Expression<Func<T, bool>> express = null)
- {
- Func<PropertyInfo[], string, IDictionary<string, object>, object> excute = (propertys, condition, parameters) =>
- {
- string sql = "select * from {0} {1}";
- string combineSql = string.Format(sql, string.Join(",", propertys.Select(x => x.Name)), tableName, condition);
- return context.ExecuteScalar(combineSql, parameters);
- };
- return CreateExcute<object>(express, excute);
- }
- private TValue CreateExcute<TValue>(Expression<Func<T, bool>> express, Func<PropertyInfo[], string, IDictionary<string, object>, TValue> excute)
- {
- Dictionary<string, object> parameters = new Dictionary<string, object>();
- PropertyInfo[] propertys = typeof(T).GetProperties();
- string condition = "";
- if (express != null)
- {
- builder.Value.Build(express, tableName);
- condition = string.Format("where {0} ", builder.Value.Condition);
- for (int i = ; i < builder.Value.Arguments.Length; i++)
- {
- parameters.Add(string.Format("Param{0}", i), builder.Value.Arguments[i]);
- }
- }
- return excute(propertys, condition, parameters);
- }
- }
接下来,定义工作单元,所有的添加、删除、修改操作都会被储存到工作单元中。
- public interface IUnitOfWork
- {
- /// <summary>
- /// 命令
- /// </summary>
- /// <param name="commandText"></param>
- /// <param name="parameters"></param>
- /// <returns></returns>
- int Command(string commandText, IDictionary<string, object> parameters);
- /// <summary>
- /// 事务的提交状态
- /// </summary>
- bool IsCommited { get; set; }
- /// <summary>
- /// 提交事务
- /// </summary>
- /// <returns></returns>
- void Commit();
- /// <summary>
- /// 回滚事务
- /// </summary>
- void RollBack();
- }
接下来是对工作单元的实现,其内部维护了一个命令集合,存储Sql语句。
- public class UnitOfWork:IUnitOfWork
- {
- /// <summary>
- /// 注入对象
- /// </summary>
- private IUnitOfWorkContext context;
- /// <summary>
- /// 维护一个Sql语句的命令列表
- /// </summary>
- private List<CommandObject> commands;
- public UnitOfWork(IUnitOfWorkContext context)
- {
- commands = new List<CommandObject>();
- this.context = context;
- }
- /// <summary>
- /// 增、删、改命令
- /// </summary>
- /// <param name="commandText"></param>
- /// <param name="parameters"></param>
- /// <returns></returns>
- public int Command(string commandText, IDictionary<string, object> parameters)
- {
- IsCommited = false;
- commands.Add(new CommandObject(commandText, parameters));
- return ;
- }
- /// <summary>
- /// 提交状态
- /// </summary>
- public bool IsCommited{ get; set; }
- /// <summary>
- /// 提交方法
- /// </summary>
- /// <returns></returns>
- public void Commit()
- {
- if (IsCommited) { return ; }
- using (TransactionScope scope = new TransactionScope())
- {
- foreach (var command in commands)
- {
- context.ExecuteNonQuery(command.command, command.parameters);
- }
- scope.Complete();
- IsCommited = true;
- }
- }
- /// <summary>
- /// 事务回滚
- /// </summary>
- public void RollBack()
- {
- IsCommited = false;
- }
- }
最后定义工作单元对事务提交处理的上下文及其实现,同仓储模式中的代码。
- public interface IUnitOfWorkContext
- {
- /// <summary>
- /// 注册新对象到上下文
- /// </summary>
- /// <param name="commandText"></param>
- /// <param name="parameters"></param>
- int ExecuteNonQuery(string commandText, IDictionary<string, object> parameters = null);
- /// <summary>
- /// 查询对象集合
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="commandText"></param>
- /// <param name="parameters"></param>
- /// <param name="load">自定义处理</param>
- /// <returns></returns>
- List<T> ReadValues<T>(string commandText, IDictionary<string, object> parameters = null, Func<IDataReader, T> load = null) where T : class, new();
- /// <summary>
- /// 查询对象集合
- /// </summary>
- /// <param name="commandText"></param>
- /// <param name="type"></param>
- /// <param name="parameters"></param>
- /// <param name="setItem"></param>
- /// <returns></returns>
- List<object> ReadValues(string commandText, Type type, IDictionary<string, object> parameters = null, Action<dynamic> setItem = null);
- /// <summary>
- /// 查询对象
- /// </summary>
- /// <typeparam name="TEntity"></typeparam>
- /// <param name="commandText"></param>
- /// <param name="parameters"></param>
- /// <param name="excute"></param>
- /// <returns></returns>
- T ExecuteReader<T>(string commandText, IDictionary<string, object> parameters = null, Func<IDataReader, T> load = null) where T : class,new();
- /// <summary>
- /// 查询数量
- /// </summary>
- /// <param name="commandText"></param>
- /// <param name="parameters"></param>
- /// <returns></returns>
- object ExecuteScalar(string commandText, IDictionary<string, object> parameters = null);
- }
最后实现。
- public abstract class UnitOfWorkContext : IUnitOfWorkContext,IDisposable
- {
- /// <summary>
- /// 数据库连接字符串标识
- /// </summary>
- public abstract string Key { get; }
- private SqlConnection connection;
- private SqlConnection Connection
- {
- get
- {
- if (connection == null)
- {
- ConnectionStringSettings settings = ConfigurationManager.ConnectionStrings[Key];
- connection = new SqlConnection(settings.ConnectionString);
- }
- return connection;
- }
- }
- /// <summary>
- /// 注册新对象到事务
- /// </summary>
- /// <typeparam name="TEntity"></typeparam>
- /// <param name="entity"></param>
- public int ExecuteNonQuery(string commandText, IDictionary<string, object> parameters = null)
- {
- Func<SqlCommand, int> excute = (commend) =>
- {
- return commend.ExecuteNonQuery();
- };
- return CreateDbCommondAndExcute<int>(commandText, parameters, excute);
- }
- /// <summary>
- /// 查询对象集合
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="commandText"></param>
- /// <param name="parameters"></param>
- /// <param name="load">自定义处理</param>
- /// <returns>泛型实体集合</returns>
- public List<T> ReadValues<T>(string commandText, IDictionary<string, object> parameters = null, Func<IDataReader, T> load = null) where T : class,new()
- {
- Func<SqlCommand, List<T>> excute = (dbCommand) =>
- {
- List<T> result = new List<T>();
- using (IDataReader reader = dbCommand.ExecuteReader())
- {
- while (reader.Read())
- {
- if (load == null)
- {
- load = (s) => { return s.GetReaderData<T>(); };
- }
- var item = load(reader);
- result.Add(item);
- }
- return result;
- }
- };
- return CreateDbCommondAndExcute(commandText, parameters, excute);
- }
- /// <summary>
- /// 查询对象集合
- /// </summary>
- /// <param name="commandText"></param>
- /// <param name="parameters"></param>
- /// <param name="setItem"></param>
- /// <returns></returns>
- public List<object> ReadValues(string commandText, Type type, IDictionary<string, object> parameters = null, Action<dynamic> setItem = null)
- {
- Func<SqlCommand, List<object>> excute = (dbCommand) =>
- {
- var result = new List<object>();
- using (IDataReader dataReader = dbCommand.ExecuteReader())
- {
- while (dataReader.Read())
- {
- var item = dataReader.GetReaderData(type);
- if (setItem != null)
- {
- setItem(item);
- }
- result.Add(item);
- }
- }
- return result;
- };
- return CreateDbCommondAndExcute<List<object>>(commandText, parameters,
- excute);
- }
- /// <summary>
- /// 查询对象
- /// </summary>
- /// <typeparam name="TEntity"></typeparam>
- /// <param name="commandText"></param>
- /// <param name="parameters"></param>
- /// <param name="excute"></param>
- /// <returns></returns>
- public T ExecuteReader<T>(string commandText, IDictionary<string, object> parameters = null, Func<IDataReader, T> load = null) where T : class,new()
- {
- Func<SqlCommand, T> excute = (dbCommand) =>
- {
- var result = default(T);
- using (IDataReader reader = dbCommand.ExecuteReader())
- {
- while (reader.Read())
- {
- if (load == null)
- {
- load = (s) => { return s.GetReaderData<T>(); };
- }
- result = load(reader);
- }
- return result;
- }
- };
- return CreateDbCommondAndExcute<T>(commandText, parameters, excute);
- }
- /// <summary>
- /// 查询数量
- /// </summary>
- /// <param name="commandText"></param>
- /// <param name="parameters"></param>
- /// <returns></returns>
- public object ExecuteScalar(string commandText, IDictionary<string, object> parameters = null)
- {
- Func<SqlCommand, object> excute = (dbCommand) =>
- {
- return dbCommand.ExecuteScalar();
- };
- return CreateDbCommondAndExcute(commandText, parameters, excute);
- }
- /// <summary>
- /// 创建命令并执行
- /// </summary>
- /// <typeparam name="TValue"></typeparam>
- /// <param name="commandText"></param>
- /// <param name="parameters"></param>
- /// <param name="excute"></param>
- /// <returns></returns>
- private TValue CreateDbCommondAndExcute<TValue>(string commandText,
- IDictionary<string, object> parameters, Func<SqlCommand, TValue> excute)
- {
- if (Connection.State == ConnectionState.Closed) { Connection.Open(); };
- using (SqlCommand command = new SqlCommand())
- {
- command.CommandType = CommandType.Text;
- command.CommandText = commandText;;
- command.Connection = Connection;
- command.SetParameters(parameters);
- return excute(command);
- }
- }
- /// <summary>
- /// 关闭连接
- /// </summary>
- public void Dispose()
- {
- if (connection != null)
- {
- Connection.Dispose();//非托管资源
- }
- }
- }
在调用方法时需要注意,一个事务涉及多个聚合时,需要保证传递同一工作单元,并在方法的最后调用Commit() 方法。
- public class Services : IService
- {
- private IMemberRespository member;
- private IUnitOfWork unitOfWork;
- public Services(IMemberRespository member, IUnitOfWork unitOfWork)
- {
- this.member = member;
- this.unitOfWork = unitOfWork;
- }
- /// <summary>
- /// 测试用例
- /// </summary>
- public void Demo()
- {
- member.Test();
- unitOfWork.Commit();
- }
- }
后记
该实现中并未实现对多表进行的联合查询,使用Lambda的方式去多表查询,有点自己写一个ORM的性质,由于Lz能力有限,顾有需求的园友可以自行扩展或者使用ORM,若有实现自行扩展的园友,望指教。
至此,既实现对数据访问层和领域层解耦,如果园友对我的比较认可,欢迎尝试去使用,在使用中遇到什么问题或有什么好的意见,也希望及时反馈给我。若某些园友不太认可我的设计,也希望批评指出。
源码网盘地址:链接:http://pan.baidu.com/s/1hqXJ3GK 密码:o0he
【.Net设计模式系列】工作单元(Unit Of Work)模式 ( 二 )的更多相关文章
- Java 设计模式系列(十八)备忘录模式(Memento)
Java 设计模式系列(十八)备忘录模式(Memento) 备忘录模式又叫做快照模式(Snapshot Pattern)或Token模式,是对象的行为模式.备忘录对象是一个用来存储另外一个对象内部状态 ...
- Java 设计模式系列(十五)迭代器模式(Iterator)
Java 设计模式系列(十五)迭代器模式(Iterator) 迭代器模式又叫游标(Cursor)模式,是对象的行为模式.迭代子模式可以顺序地访问一个聚集中的元素而不必暴露聚集的内部表象(interna ...
- Java 设计模式系列(十四)命令模式(Command)
Java 设计模式系列(十四)命令模式(Command) 命令模式把一个请求或者操作封装到一个对象中.命令模式允许系统使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复 ...
- Java 设计模式系列(十七)中介者模式
Java 设计模式系列(十七)中介者模式 用一个中介对象来封装一系列的对象交互.中介者使得各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立的改变它们之间的交互 一.中介者模式结构 Media ...
- Java 设计模式系列(十一)享元模式
Java 设计模式系列(十一)享元模式 Flyweight 享元模式是对象的结构模式.享元模式以共享的方式高效地支持大量的细粒度对象. 一.享元模式的结构 享元模式采用一个共享来避免大量拥有相同内容对 ...
- Java 设计模式系列(八)装饰者模式
Java 设计模式系列(八)装饰者模式 装饰模式又名包装(Wrapper)模式.装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案.Decorator 或 Wrapper 一.装饰模 ...
- 基于DDD的.NET开发框架 - ABP工作单元(Unit of Work)
返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应 ...
- 工作单元(Unit of Work)
维护受业务事务影响的对象列表,并协调变化的写入和并发问题的解决. 从DB中存取Data时,必须记录增删改动作,以将对DB有影响的数据写会到DB中去. 如果在每次修改对象模型时就对DB进行相应的修改,会 ...
- ABP理论学习之工作单元(Unit of Work)
返回总目录 本篇目录 公共连接和事务管理方法 ABP中的连接和事务管理 仓储类 应用服务 工作单元 工作单元详解 关闭工作单元 非事务的工作单元 工作单元方法调用其它 工作单元作用域 自动保存 IRe ...
随机推荐
- PAT甲级题分类汇编——线性
本文为PAT甲级分类汇编系列文章. 线性类,指线性时间复杂度可以完成的题.在1051到1100中,有7道: 题号 标题 分数 大意 时间 1054 The Dominant Color 20 寻找出现 ...
- 1. Spark基础解析
1.1 Spark概述 1.1.1 什么是Spark 官网:http://spark.apache.org Spark是一种快速.通用.可扩展的大数据分析引擎,2009年诞生于加州大学伯克利分校AMP ...
- maven配置阿里镜像
在conf\settings.xml 在<mirrors>里面添加 <mirror> <id>nexus-aliyun</id> < ...
- Tomcat HTTP connector和AJP connector
Tomcat服务器通过Connector连接器组件与客户程序建立连接,“连接器”表示接收请求并返回响应的端点.即Connector组件负责接收客户的请求,以及把Tomcat服务器的响应结果发送给客户. ...
- Powershell学习笔记:(一)、初识Powershell
什么是Powershell? MSDN上的说明是:PowerShell 是构建于 .NET 上基于任务的命令行 shell 和脚本语言. PowerShell 可帮助系统管理员和高级用户快速自动执行用 ...
- 效率提升工具Listary
效率提升工具Listary https://baijiahao.baidu.com/s?id=1590032175308204846&wfr=spider&for=pc
- axios 内部原理学习记录
前提:一次面试被问到了,axios有什么特点,对比一下ajax.答的很不满意. axios是一个基于Promise的http请求库,可用于浏览器和 Node.可以说是目前最为常用的http库,有必要了 ...
- @objc
Swift 和 Objective-C 的互调这个话题很大,今天我们重点看看其中一个小的知识点:@objc的使用. 用法 在 Swift 代码中,使用@objc修饰后的类型,可以直接供 Objecti ...
- English-培训6-Do you like rap?
- Hibernate更新、删除后数据库无变化
转自:https://ask.csdn.net/questions/756109 !-- 配置事务管理器 --> <tx:advice id="advice" tran ...