springboot手动事物回滚_net core系列:泛型仓储和声明式事物实现最优雅crud

哈哈哈哈,大家好,我就是那个高产似母猪的三合,长久以来,我一直在思考,如何才能实现高效而简洁的仓储模式(不是DDD里的仓储,更准确的说就是数据库表的mapper),实现spring boot里那样利用注解实现事物操作,日有所思,终有所得,本篇文章浓缩了我对于仓储模式和工作单元模式理解的精华,希望能对大家有所帮助,如果哪里说错了,也希望大家不吝赐教。由于ef core本身就实现了这2种模式,再在ef core的基础上进行封装就失去了学习的意义,所以本文用到的是ORM方案是dapper+dapper.contrib, 这2个库皆出自名门stackexchange,也就是大名鼎鼎的爆栈啦,他们出品的库还有StackExchange.Redis,所以品质自不用说,开始正文前,先在nuget上安装这2个库。BTW,动态代理,注解式编程,AOP贯穿本系列始终,no bb,正文开始。

1.定义用到的类

上次讲飙车,这次我们讲,去加油站加油,加油这个过程呢,存在一个事物操作,那就是,加油站必须给我加足够的油,我才给他付钱,有点像银行转账,那么引申出2张表,汽车油量表(oilQuantity)和现金余额表(cashBalance),对应的表结构和实体类如下,都比较简单,除了主键id,oilQuantity表只有一个油量quantity字段,cashBalance表只有一个余额balance字段,数据库使用的是mysql,实体类的注解TableAttribute使用的命名空间是System.ComponentModel.DataAnnotations.Schema。

CREATE TABLE test.oilQuantity (

id INT NOT NULL AUTO_INCREMENT,

quantity DECIMAL NULL,

CONSTRAINT caroil_pk PRIMARY KEY (id)

)

ENGINE=InnoDB

DEFAULT CHARSET=utf8

COLLATE=utf8_general_ci;

CREATE TABLE test.cashBalance (

id INT NOT NULL AUTO_INCREMENT,

balance DECIMAL NOT NULL,

CONSTRAINT cashbalance_pk PRIMARY KEY (id)

)

ENGINE=InnoDB

DEFAULT CHARSET=utf8

COLLATE=utf8_general_ci;

[Table("OilQuantity")]

public class OilQuantity

{

[Key]

public int Id { set; get; }

///

/// 油量

///

public decimal Quantity { set; get; }

}

[Table("CashBalance")]

public class CashBalance

{

[Key]

public int Id { set; get; }

///

/// 余额

///

public decimal Balance { set; get; }

}

定义数据库链接工厂类接口IDbFactory和他的实现类DbFactory,这个类主要负责数据库链接的创建,链接分为2种,一种是短链接,不开启事物的时候使用,用完即毁,每次获得都是全新的链接,另一种是长链接,用在事物操作中,DbFactory本身注册为scope级别,长链接创建后会保存在DbFactory的属性中,所以变相的实现了scope级别,同理,长链接的事务开启后也会被保存,用来在仓储中实现事物操作。

public interface IDbFactory:IDisposable

{

///

/// 长链接

///

IDbConnection LongDbConnection { get; }

///

/// 长链接的事物

///

IDbTransaction LongDbTransaction { get; }

///

/// 短链接

///

IDbConnection ShortDbConnection { get; }

///

/// 开启事务

///

void BeginTransaction();

}

///

/// 负责生成和销毁数据库链接

///

public class DbFactory:IDbFactory

{

[Value("MysqlConnectionStr")]

public string MysqlConnectionStr { set; get; }

///

/// 长连接

///

public IDbConnection LongDbConnection { private set; get; }

///

/// 长连接的事物

///

public IDbTransaction LongDbTransaction { private set; get; }

///

/// 短链接

///

public IDbConnection ShortDbConnection

{

get

{

var dbConnection = new MySqlConnection(MysqlConnectionStr);

dbConnection.Open();

return dbConnection;

}

}

///

/// 开启事务

///

///

public void BeginTransaction()

{

if (LongDbConnection == null)

{

LongDbConnection = new MySqlConnection(MysqlConnectionStr);

LongDbConnection.Open();

LongDbTransaction = LongDbConnection.BeginTransaction();

}

}

public void Dispose()

{

LongDbTransaction?.Dispose();

if (LongDbConnection?.State != ConnectionState.Closed)

{

LongDbConnection?.Close();

}

LongDbConnection?.Dispose();

LongDbTransaction = null;

LongDbConnection = null;

}

}

定义工作单元接口IUnitOfWork和他的实现类UnitOfWork,可以看到,IUnitOfWork中有一个引用次数ActiveNumber的属性,这个属性的作用主要是,如果一个标注了[Transactional]的方法里嵌套了另一个标注了[Transactional]的方法,我们就可以通过计数来确认,具体谁才是最外层的方法,从而达到不在内层方法里开启另一个事物,并且在内层方法结束时不会提前提交事务的效果。同时呢,UnitOfWork只负责与事务有关的操作,其他创建链接,创建事物等操作,都是由注入的IDbFactory完成的。

public interface IUnitOfWork : IDisposable

{

///

/// 引用次数,开启一次事物加+1,当次数为0时提交,主要是为了防止事物嵌套

///

int ActiveNumber { get; set; }

///

/// 开启事务

///

void BeginTransaction();

///

/// 提交

///

void Commit();

///

/// 事物回滚

///

void RollBack();

}

public class UnitOfWork : IUnitOfWork

{

///

/// 工作单元引用次数,当次数为0时提交,主要为了防止事物嵌套

///

public int ActiveNumber { get; set; } = 0;

[Autowired]

public IDbFactory DbFactory { set; get; }

public void BeginTransaction()

{

if (this.ActiveNumber == 0)

{

DbFactory.BeginTransaction();

Console.WriteLine("开启事务");

}

this.ActiveNumber++;

}

public void Commit()

{

this.ActiveNumber--;

if (this.ActiveNumber == 0)

{

if (DbFactory.LongDbConnection != null)

{

try

{

DbFactory.LongDbTransaction.Commit();

}

catch (Exception e)

{

DbFactory.LongDbTransaction.Rollback();

Console.WriteLine(e);

throw;

}

finally

{

this.Dispose();

}

}

Console.WriteLine("提交事务");

}

}

public void Dispose()

{

DbFactory.Dispose();

}

public void RollBack()

{

if (this.ActiveNumber > 0 && DbFactory.LongDbTransaction != null)

{

try

{

DbFactory.LongDbTransaction.Rollback();

}

catch (Exception e)

{

Console.WriteLine(e);

throw;

}

}

Console.WriteLine("回滚事务");

}

}

泛型仓储接口IRepository和他的实现类BaseRepository,为了偷懒,只写了同步部分,异步同理,若使用异步,拦截器也要使用异步拦截器。BaseRepository中通过属性注入了IUnitOfWork和IDbFactory,IUnitOfWork主要负责告诉仓储,该使用长连接还是短链接,IDbFactory负责提供具体的链接和事物,而更细节的crud操作,则都是由dapper和dapper.contrib完成的。看代码var dbcon = Uow.ActiveNumber == 0 ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;可以看到通过判断uow的引用计数ActiveNumber 来判断使用的是长链接还是短链接,并且如果ActiveNumber==0的话,在数据库操作结束后就会释放掉链接。

public interface IRepository where T : class

{

IList GetAll();

T Get(object id);

T Insert(T t);

IList Insert(IList t);

void Update(T t);

void Update(IList t);

void Delete(IList t);

void Delete(T t);

}

public class BaseRepository : IRepository where T : class

{

[Autowired]

public IUnitOfWork Uow { set; get; }

[Autowired]

public IDbFactory DbFactory { set; get; }

public IList GetAll()

{

var dbcon = DbFactory.ShortDbConnection;

var result = dbcon.GetAll().ToList();

dbcon.Close();

dbcon.Dispose();

return result;

}

public T Get(object id)

{

var dbcon = DbFactory.ShortDbConnection;

var result = dbcon.Get(id);

dbcon.Close();

dbcon.Dispose();

return result;

}

public T Insert(T t)

{

var dbcon = Uow.ActiveNumber == 0 ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;

dbcon.Insert(t, DbFactory.LongDbTransaction);

if (Uow.ActiveNumber == 0)

{

dbcon.Close();

dbcon.Dispose();

}

return t;

}

public IList Insert(IList t)

{

var dbcon = Uow.ActiveNumber == 0 ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;

dbcon.Insert(t, DbFactory.LongDbTransaction);

if (Uow.ActiveNumber == 0)

{

dbcon.Close();

dbcon.Dispose();

}

return t;

}

public void Delete(T t)

{

var dbcon = Uow.ActiveNumber == 0 ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;

dbcon.Delete(t, DbFactory.LongDbTransaction);

if (Uow.ActiveNumber == 0)

{

dbcon.Close();

dbcon.Dispose();

}

}

public void Delete(IList t)

{

var dbcon = Uow.ActiveNumber == 0 ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;

dbcon.Delete(t, DbFactory.LongDbTransaction);

if (Uow.ActiveNumber == 0)

{

dbcon.Close();

dbcon.Dispose();

}

}

public void Update(T t)

{

var dbcon = Uow.ActiveNumber == 0 ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;

dbcon.Update(t, DbFactory.LongDbTransaction);

if (Uow.ActiveNumber == 0)

{

dbcon.Close();

dbcon.Dispose();

}

}

public void Update(IList t)

{

var dbcon = Uow.ActiveNumber == 0 ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;

dbcon.Update(t, DbFactory.LongDbTransaction);

if (Uow.ActiveNumber == 0)

{

dbcon.Close();

dbcon.Dispose();

}

}

}

事物拦截器TransactionalInterceptor,在方法开始前,如果拦截到的方法具有[TransactionalAttribute]注解,则通过uow开启事务,在方法结束后,如果拦截到的方法具有[TransactionalAttribute]注解,则通过uow结束事务。

///

/// 事物拦截器

///

public class TransactionalInterceptor : StandardInterceptor

{

private IUnitOfWork Uow { set; get; }

public TransactionalInterceptor(IUnitOfWork uow)

{

Uow = uow;

}

protected override void PreProceed(IInvocation invocation)

{

Console.WriteLine("{0}拦截前", invocation.Method.Name);

var method = invocation.MethodInvocationTarget;

if (method != null && method.GetCustomAttribute() != null)

{

Uow.BeginTransaction();

}

}

protected override void PerformProceed(IInvocation invocation)

{

invocation.Proceed();

}

protected override void PostProceed(IInvocation invocation)

{

Console.WriteLine("{0}拦截后, 返回值是{1}", invocation.Method.Name, invocation.ReturnValue);

var method = invocation.MethodInvocationTarget;

if (method != null && method.GetCustomAttribute() != null)

{

Uow.Commit();

}

}

}

IServiceCollection静态扩展类SummerBootExtentions.cs,和上一篇比较,主要就是添加了AddSbRepositoryService方法,这个方法主要通过反射获得由[TableAttribute]标注的实体类,并向IServiceCollection中添加相应的的仓储接口和相应的仓储实现类,为什么不用services.AddScoped(typeof(IRepository<>),typeof(BaseRepository<>));这种方法注入泛型仓储呢?因为net core原生DI并不支持泛型注入的工厂委托创建,那么就无法实现动态代理了,所以采用变通的方法,将通用泛型接口,转成具体的泛型接口,SummerBootExtentions.cs的另一个变动就是将ProxyGenerator注册成单例了,这样就可以利用缓存,提高创建动态代理的性能,SummerBootExtentions.cs代码如下:

public static class SummerBootExtentions

{

///

/// 瞬时

///

///

///

///

///

///

public static IServiceCollection AddSbTransient(this IServiceCollection services, params Type[] interceptorTypes)

{

return services.AddSbService(typeof(TService), typeof(TImplementation), ServiceLifetime.Transient, interceptorTypes);

}

///

/// 瞬时

///

///

///

///

///

///

public static IServiceCollection AddSbTransient(this IServiceCollection services, Type serviceType,

Type implementationType, params Type[] interceptorTypes)

{

return services.AddSbService(serviceType, implementationType, ServiceLifetime.Transient, interceptorTypes);

}

///

/// 请求级别

///

///

///

///

///

///

public static IServiceCollection AddSbScoped(this IServiceCollection services, params Type[] interceptorTypes)

{

return services.AddSbService(typeof(TService), typeof(TImplementation), ServiceLifetime.Scoped, interceptorTypes);

}

///

/// 请求级别

///

///

///

///

///

///

public static IServiceCollection AddSbScoped(this IServiceCollection services, Type serviceType,

Type implementationType, params Type[] interceptorTypes)

{

return services.AddSbService(serviceType, implementationType, ServiceLifetime.Scoped, interceptorTypes);

}

///

/// 单例

///

///

///

///

///

///

public static IServiceCollection AddSbSingleton(this IServiceCollection services, params Type[] interceptorTypes)

{

return services.AddSbService(typeof(TService), typeof(TImplementation), ServiceLifetime.Singleton, interceptorTypes);

}

///

/// 单例

///

///

///

///

///

///

public static IServiceCollection AddSbSingleton(this IServiceCollection services, Type serviceType,

Type implementationType, params Type[] interceptorTypes)

{

return services.AddSbService(serviceType, implementationType, ServiceLifetime.Singleton, interceptorTypes);

}

public static IServiceCollection AddSbService(this IServiceCollection services, Type serviceType, Type implementationType,

ServiceLifetime lifetime, params Type[] interceptorTypes)

{

services.Add(new ServiceDescriptor(implementationType, implementationType, lifetime));

object Factory(IServiceProvider provider)

{

var target = provider.GetService(implementationType);

var properties = implementationType.GetTypeInfo().DeclaredProperties;

foreach (PropertyInfo info in properties)

{

//属性注入

if (info.GetCustomAttribute() != null)

{

var propertyType = info.PropertyType;

var impl = provider.GetService(propertyType);

if (impl != null)

{

info.SetValue(target, impl);

}

}

//配置值注入

if (info.GetCustomAttribute() is ValueAttribute valueAttribute)

{

var value = valueAttribute.Value;

if (provider.GetService(typeof(IConfiguration)) is IConfiguration configService)

{

var pathValue = configService.GetSection(value).Value;

if (pathValue != null)

{

var pathV = Convert.ChangeType(pathValue, info.PropertyType);

info.SetValue(target, pathV);

}

}

}

}

List interceptors = interceptorTypes.ToList()

.ConvertAll(interceptorType => provider.GetService(interceptorType) as IInterceptor);

var proxyGenerator = provider.GetService();

var proxy = proxyGenerator.CreateInterfaceProxyWithTarget(serviceType, target, interceptors.ToArray());

return proxy;

};

var serviceDescriptor = new ServiceDescriptor(serviceType, Factory, lifetime);

services.Add(serviceDescriptor);

return services;

}

///

/// 瞬时

///

///

///

///

///

public static IServiceCollection AddSbTransient(this IServiceCollection services, params Type[] interceptorTypes)

{

return services.AddSbService(typeof(TService), ServiceLifetime.Transient, interceptorTypes);

}

///

/// 瞬时

///

///

///

///

///

public static IServiceCollection AddSbTransient(this IServiceCollection services, Type serviceType, params Type[] interceptorTypes)

{

return services.AddSbService(serviceType, ServiceLifetime.Transient, interceptorTypes);

}

///

/// 请求

///

///

///

///

///

public static IServiceCollection AddSbScoped(this IServiceCollection services, params Type[] interceptorTypes)

{

return services.AddSbService(typeof(TService), ServiceLifetime.Scoped, interceptorTypes);

}

///

/// 请求

///

///

///

///

///

public static IServiceCollection AddSbScoped(this IServiceCollection services, Type serviceType,

params Type[] interceptorTypes)

{

return services.AddSbService(serviceType, ServiceLifetime.Scoped, interceptorTypes);

}

///

/// 单例

///

///

///

///

///

public static IServiceCollection AddSbSingleton(this IServiceCollection services, params Type[] interceptorTypes)

{

return services.AddSbService(typeof(TService), ServiceLifetime.Singleton, interceptorTypes);

}

///

/// 单例

///

///

///

///

///

public static IServiceCollection AddSbSingleton(this IServiceCollection services, Type serviceType, params Type[] interceptorTypes)

{

return services.AddSbService(serviceType, ServiceLifetime.Singleton, interceptorTypes);

}

public static IServiceCollection AddSbService(this IServiceCollection services, Type serviceType,

ServiceLifetime lifetime, params Type[] interceptorTypes)

{

if (services == null)

throw new ArgumentNullException(nameof(services));

if (serviceType == (Type)null)

throw new ArgumentNullException(nameof(serviceType));

object Factory(IServiceProvider provider)

{

List interceptors = interceptorTypes.ToList()

.ConvertAll(interceptorType => provider.GetService(interceptorType) as IInterceptor);

var proxyGenerator = provider.GetService();

var proxy = proxyGenerator.CreateClassProxy(serviceType, interceptors.ToArray());

var properties = serviceType.GetTypeInfo().DeclaredProperties;

foreach (PropertyInfo info in properties)

{

//属性注入

if (info.GetCustomAttribute() != null)

{

var propertyType = info.PropertyType;

var impl = provider.GetService(propertyType);

if (impl != null)

{

info.SetValue(proxy, impl);

}

}

//配置值注入

if (info.GetCustomAttribute() is ValueAttribute valueAttribute)

{

var value = valueAttribute.Value;

if (provider.GetService(typeof(IConfiguration)) is IConfiguration configService)

{

var pathValue = configService.GetSection(value).Value;

if (pathValue != null)

{

var pathV = Convert.ChangeType(pathValue, info.PropertyType);

info.SetValue(proxy, pathV);

}

}

}

}

return proxy;

};

var serviceDescriptor = new ServiceDescriptor(serviceType, Factory, lifetime);

services.Add(serviceDescriptor);

return services;

}

///

/// 添加summer boot扩展

///

///

///

public static IMvcBuilder AddSB(this IMvcBuilder builder)

{

if (builder == null)

throw new ArgumentNullException(nameof(builder));

ControllerFeature feature = new ControllerFeature();

builder.PartManager.PopulateFeature(feature);

foreach (Type type in feature.Controllers.Select((Func)(c => c.AsType())))

builder.Services.TryAddTransient(type, type);

builder.Services.Replace(ServiceDescriptor.Transient());

return builder;

}

public static IServiceCollection AddSbRepositoryService(this IServiceCollection services, params Type[] interceptorTypes)

{

var types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(it => it.GetTypes());

var tableType = types.Where(it => it.GetCustomAttribute() != null);

foreach (var type in tableType)

{

var injectServiceType = typeof(IRepository<>).MakeGenericType(type);

var injectImplType = typeof(BaseRepository<>).MakeGenericType(type);

services.AddSbScoped(injectServiceType, injectImplType, interceptorTypes);

}

return services;

}

}

定义一个加油的服务类接口IAddOilService和接口的实现类AddOilService,可以从代码中看到,我们通过属性注入添加了CashBalanceRepository和OilQuantityRepository,通过[Transactional]标注AddOil方法,使其成为事物性操作,AddOil主要就是初始化金额和油量,然后进行加减操作,最后更新。

public interface IAddOilService

{

void AddOil();

}

public class AddOilService : IAddOilService

{

[Autowired]

public IRepository CashBalanceRepository { set; get; }

[Autowired]

public IRepository OilQuantityRepository { set; get; }

[Transactional]

public void AddOil()

{

//初始化金额

var cashBalance = CashBalanceRepository.Insert(new CashBalance() { Balance = 100 });

//初始化油量

var oilQuantity = OilQuantityRepository.Insert(new OilQuantity() { Quantity = 5 });

cashBalance.Balance -= 95;

oilQuantity.Quantity += 50;

CashBalanceRepository.Update(cashBalance);

//throw new Exception("主动报错");

OilQuantityRepository.Update(oilQuantity);

}

}

修改Startup.cs中的ConfigureServices方法,代码如下:

public void ConfigureServices(IServiceCollection services)

{

services.Configure(options =>

{

// This lambda determines whether user consent for non-essential cookies is needed for a given request.

options.CheckConsentNeeded = context => true;

options.MinimumSameSitePolicy = SameSiteMode.None;

});

services.AddMvc()

.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)

.AddSB();

services.AddSingleton();

services.AddSbScoped(typeof(TransactionalInterceptor));

services.AddSbScoped();

services.AddScoped(typeof(TransactionalInterceptor));

services.AddSbScoped(typeof(TransactionalInterceptor));

services.AddSbScoped();

services.AddSbRepositoryService(typeof(TransactionalInterceptor));

services.AddSbScoped(typeof(TransactionalInterceptor));

}

控制器HomeController

public class HomeController : Controller

{

[Autowired]

public ICar Car { set; get; }

[Autowired]

public IDistributedCache Cache { set; get; }

[Value("description")]

public string Description { set; get; }

[Autowired]

public IAddOilService AddOilService { set; get; }

public IActionResult Index()

{

var car = Car;

AddOilService.AddOil();

Car.Fire();

Console.WriteLine(Description);

return View();

}

}

2.效果图

2.1 清空2张表里的数据,在AddOil末尾打断点。

8514b6335e4be2bb4ede25a85132887d.png
82953b732c93d74e7c302bd8a4310496.png
0b6b38002043313c58ed17370340dd32.png

虽然前面执行了insert操作,但是我们查询2张表,发现里面并没有新增数据,因为事物还未提交,符合预期。从断点处继续执行,然后查询数据库。

3dabdc31d8c7f01d209a47650509ba38.png
65eee6d9b72a25bcc3f8c6a9f2e4e3a1.png

执行完事物后,数据正确,符合预期。

2.2 清空2张表里的数据,注释掉AddOil方法的[Transactional]注解,在AddOil末尾打断点。

95d91d72435ec3239cd8caf28788a985.png
e2bddb829ef8b882af30aab9854ac4db.png
b799dec8aecf337c187be0e1b90b152a.png

查看数据库,因为没开启事务,所以数据已经正确插入到表中,并且由于oilQuantity仓储未更新,所以数值正确,从断点处继续执行

58619f33a02701c9f44e9c887c540858.png

oilQuantity仓储更新,数值正确,符合预期。

2.3 清空2张表里的数据,开启AddOil方法的[Transactional]注解,并在方法中主动抛出一个错误。

1dc70488743c4f40131031f8b1cfa938.png
82953b732c93d74e7c302bd8a4310496.png
0b6b38002043313c58ed17370340dd32.png

表中并未添加数据,因为事物未提交,回滚了,符合预期。

BTW,事物的开启,除了使用[Transactional]注解外,也可以通过注入uow,手动开启和提交。

3. 写在最后

只需要在数据库实体类上注解[Table("表名")]就可以直接使用仓储了,是不是很简洁优雅呢?这里实现的仓储都是通用的,如果有特殊需求的仓储,则需要自定义接口和实现类,接口继承IRepository,实现类继承BaseRepository,然后注入自己的特殊仓储就行了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值