总结Spring中事务的使用、抽象机制及模拟Spring事务实现
本篇内容介绍了“总结Spring中事务的使用、抽象机制及模拟Spring事务实现”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
本文大纲如下:
Spring事务应用大纲
编程式事务
Spring提供了两种编程式事务管理的方法
使用 TransactionTemplate 或者 TransactionalOperator.
直接实现TransactionManager接口
如果是使用的是命令式编程,Spring推荐使用TransactionTemplate 来完成编程式事务管理,如果是响应式编程,那么使用TransactionalOperator更加合适。
TransactionTemplate
使用示例(我这里直接用的官网提供的例子了)
publicclassSimpleServiceimplementsService{privatefinalTransactionTemplatetransactionTemplate;//使用构造对transactionTemplate进行初始化//需要提供一个transactionManagerpublicSimpleService(PlatformTransactionManagertransactionManager){this.transactionTemplate=newTransactionTemplate(transactionManager);}publicObjectsomeServiceMethod(){returntransactionTemplate.execute(newTransactionCallback(){publicObjectdoInTransaction(TransactionStatusstatus){//这里实现自己的相关业务逻辑updateOperation1();returnresultOfUpdateOperation2();}});}}
在上面的例子中,我们显示的使用了TransactionTemplate来完成事务管理,通过实现TransactionCallback接口并在其doInTransaction方法中完成了我们对业务的处理。我们可以大概看下TransactionTemplate的execute方法的实现:
public<T>Texecute(TransactionCallback<T>action)throwsTransactionException{Assert.state(this.transactionManager!=null,"NoPlatformTransactionManagerset");if(this.transactionManagerinstanceofCallbackPreferringPlatformTransactionManager){return((CallbackPreferringPlatformTransactionManager)this.transactionManager).execute(this,action);}else{//1.通过事务管理器开启事务TransactionStatusstatus=this.transactionManager.getTransaction(this);Tresult;try{//2.执行传入的业务逻辑result=action.doInTransaction(status);}catch(RuntimeException|Errorex){//3.出现异常,进行回滚rollbackOnException(status,ex);throwex;}catch(Throwableex){//3.出现异常,进行回滚rollbackOnException(status,ex);thrownewUndeclaredThrowableException(ex,"TransactionCallbackthrewundeclaredcheckedexception");}//4.正常执行完成的话,提交事务this.transactionManager.commit(status);returnresult;}}
这些方法具体的实现我们暂且不看,后续进行源码分析时都会详细介绍,之所以将这个代码贴出来是让大家更好的理解TransactionTemplate的工作机制:实际上就是通过一个TransactionCallback封装了业务逻辑,然后TransactionTemplate会在事务的上下文中调用。
在上面的例子中doInTransaction是有返回值的,而实际上有时候并不需要返回值,这种情况下,我们可以使用TransactionCallbackWithoutResult提代TransactionCallback。
transactionTemplate.execute(newTransactionCallbackWithoutResult(){protectedvoiddoInTransactionWithoutResult(TransactionStatusstatus){updateOperation1();updateOperation2();}});
❝实际上我们还可以通过TransactionTemplate指定事务的属性,例如隔离级别、超时时间、传播行为等等
TransactionTemplate是线程安全的,我们可以全局配置一个TransactionTemplate,然后所有的类都共享这个TransactionTemplate。但是,如果某个类需要特殊的事务配置,例如需要定制隔离级别,那么我们就有必要去创建不同的TransactionTemplate。❞
TransactionOperator
❝TransactionOperator适用于响应式编程的情况,这里就不做详细介绍了❞
TransactionManager
实际上TransactionTemplate内部也是使用TransactionManager来完成事务管理的,我们之前也看过它的execute方法的实现了,其实内部就是调用了TransactionManager的方法,实际上就是分为这么几步
鸿蒙官方战略合作共建——HarmonyOS技术社区
开启事务
执行业务逻辑
出现异常进行回滚
正常执行则提交事务
这里我还是直接用官网给出的例子
//定义事务DefaultTransactionDefinitiondef=newDefaultTransactionDefinition();def.setName("SomeTxName");def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//txManager,事务管理器//通过事务管理器开启一个事务TransactionStatusstatus=txManager.getTransaction(def);try{//完成自己的业务逻辑}catch(MyExceptionex){//出现异常,进行回滚txManager.rollback(status);throwex;}//正常执行完成,提交事务txManager.commit(status);
我们在后边的源码分析中其实重点分析的也就是TransactionManager的源码。
申明式事务
在对编程式事务有一定了解之后我们会发现,编程式事务存在下面几个问题:
鸿蒙官方战略合作共建——HarmonyOS技术社区
「我们的业务代码跟事务管理的代码混杂在一起」。
「每个需要事务管理的地方都需要写重复的代码」
如何解决呢?这就要用到申明式事务了,实现申明式事务一般有两种方式
基于XML配置
基于注解
申明式事务事务的实现原理如下(图片来源于官网):
实现原理
「实际上就是结合了APO自动代理跟事务相关API」。通过开启AOP自动代理并向容器中注册了事务需要的通知(Transaction Advisor),在Transaction Advisor调用了事务相关API,其实内部也是调用了TransactionManager的方法。
基于XML配置这种方式就不讲了,笔者近两年时间没用过XML配置,我们主要就看看通过注解方式来实现申明式事务。主要涉及到两个核心注解
鸿蒙官方战略合作共建——HarmonyOS技术社区
@EnableTransactionManagement
@Transactional
@EnableTransactionManagement这个注解主要有两个作用,其一是,开启AOP自动代理,其二是,添加事务需要用到的通知(Transaction Advisor),如果你对AOP有一定了解的话那你应该知道一个Advisor实际上就是一个绑定了切点(Pointcut)的通知(Advice),通过@EnableTransactionManagement这个注解导入的Advisor所绑定的切点就是通过@Transactional来定义的。
申明式事务的例子我这里就省去了,我相信没几个人不会用吧.....
Spring对事务的抽象
Spring事务抽象的关键就是事务策略的概念,事务策略是通过TransactionManager接口定义的。TransactionManager本身只是一个标记接口,它有两个直接子接口
鸿蒙官方战略合作共建——HarmonyOS技术社区
ReactiveTransactionManager,这个接口主要用于在响应式编程模型下,不是我们要讨论的重点
PlatformTransactionManager,命令式编程模型下我们使用这个接口。
❝关于响应式跟命令式编程都可以单独写一篇文章了,本文重点不是讨论这两种编程模型,可以认为平常我们使用的都是命令式编程❞
PlatformTransactionManager
PlatformTransactionManager接口定义
publicinterfacePlatformTransactionManagerextendsTransactionManager{//开启事务TransactionStatusgetTransaction(TransactionDefinitiondefinition)throwsTransactionException;//提交事务voidcommit(TransactionStatusstatus)throwsTransactionException;//回滚事务voidrollback(TransactionStatusstatus)throwsTransactionException;}
PlatformTransactionManager是命令式编程模型下Spring事务机制的中心接口,定义了完成一个事务必须的三个步骤,也就是说定义了事务实现的规范
开启事务
提交事务
回滚事务
通常来说,我们不会直接实现这个接口,而是通过继承AbstractPlatformTransactionManager,这个类是一个抽象类,主要用作事务管理的模板,这个抽象类已经实现了事务的传播行为以及跟事务相关的同步管理。
回头看接口中定义的三个方法,首先是开启事务的方法,从方法签名上来看,其作用就是通过一个TransactionDefinition来获取一个TransactionStatus类型的对象。为了更好的理解Spring中事务的抽象我们有必要了解下这两个接口
TransactionDefinition
接口定义如下:
publicinterfaceTransactionDefinition{//定义了7中事务的传播机制intPROPAGATION_REQUIRED=0;intPROPAGATION_SUPPORTS=1;intPROPAGATION_MANDATORY=2;intPROPAGATION_REQUIRES_NEW=3;intPROPAGATION_NOT_SUPPORTED=4;intPROPAGATION_NEVER=5;intPROPAGATION_NESTED=6;//4种隔离级别,-1代表的是使用数据库默认的隔离级别//比如在MySQL下,使用的就是ISOLATION_REPEATABLE_READ(可重复读)intISOLATION_DEFAULT=-1;intISOLATION_READ_UNCOMMITTED=1;intISOLATION_READ_COMMITTED=2;intISOLATION_REPEATABLE_READ=4;intISOLATION_SERIALIZABLE=8;//事务的超时时间,默认不限制时间intTIMEOUT_DEFAULT=-1;//提供了对上面三个属性的get方法defaultintgetPropagationBehavior(){returnPROPAGATION_REQUIRED;}defaultintgetIsolationLevel(){returnISOLATION_DEFAULT;}defaultintgetTimeout(){returnTIMEOUT_DEFAULT;}//事务是否是只读的,默认不是defaultbooleanisReadOnly(){returnfalse;}//事务的名称@NullabledefaultStringgetName(){returnnull;}//返回一个只读的TransactionDefinition//只对属性提供了getter方法,所有属性都是接口中定义的默认值staticTransactionDefinitionwithDefaults(){returnStaticTransactionDefinition.INSTANCE;}}
从这个接口的名字上我们也能知道,它的主要完成了对事务定义的抽象,这些定义有些是数据库层面本身就有的,例如隔离级别、是否只读、超时时间、名称。也有些是Spring赋予的,例如事务的传播机制。Spring中一共定义了7种事务的传播机制
TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
关于事务的传播在源码分析的文章中我会重点介绍,现在大家留个印象即可。
我们在使用申明式事务的时候,会通过@Transactional这个注解去申明某个方法需要进行事务管理,在@Transactional中可以定义事务的属性,这些属性实际上就会被封装到一个TransactionDefinition中,当然封装的时候肯定不是直接使用的接口,而是这个接口的一个实现类RuleBasedTransactionAttribute。RuleBasedTransactionAttribute,该类的继承关系如下:
RuleBasedTransactionAttribute
DefaultTransactionDefinition,实现了TransactionDefinition,并为其中的定义的属性提供了默认值
//默认的传播机制为required,没有事务新建一个事务//有事务的话加入当前事务privateintpropagationBehavior=PROPAGATION_REQUIRED;//隔离级别跟数据库默认的隔离级别一直privateintisolationLevel=ISOLATION_DEFAULT;//默认为-1,不设置超时时间privateinttimeout=TIMEOUT_DEFAULT;//默认不是只读的privatebooleanreadOnly=false;
TransactionAttribute,扩展了``DefaultTransactionDefinition`,新增了两个事务的属性
//用于指定事务使用的事务管理器的名称StringgetQualifier();//指定在出现哪种异常时才进行回滚booleanrollbackOn(Throwableex);
DefaultTransactionAttribute,继承了DefaultTransactionDefinition,同时实现了TransactionAttribute接口,定义了默认的回滚异常
//抛出RuntimeException/Error才进行回滚publicbooleanrollbackOn(Throwableex){return(exinstanceofRuntimeException||exinstanceofError);}
RuleBasedTransactionAttribute,@Transactional注解的rollbackFor等属性就会被封装到这个类中,允许程序员自己定义回滚的异常,如果没有指定回滚的异常,默认「抛出RuntimeException/Error才进行回滚」
TransactionStatus
这个接口主要用于描述Spring事务的状态,其继承关系如下:
TransactionStatus
TransactionExecution,这个接口也是用于描述事务的状态,TransactionStatus是在其上做的扩展,内部定义了以下几个方法
//判断当前事务是否是一个新的事务//不是一个新事务的话,那么需要加入到已经存在的事务中booleanisNewTransaction();//事务是否被标记成RollbackOnly//如果被标记成了RollbackOnly,意味着事务只能被回滚voidsetRollbackOnly();booleanisRollbackOnly();//是否事务完成,回滚或提交都意味着事务完成了booleanisCompleted();
SavepointManager,定义了管理保存点(Savepoint)的方法,隔离级别为NESTED时就是通过设置回滚点来实现的,内部定义了这么几个方法
//创建保存点ObjectcreateSavepoint()throwsTransactionException;//回滚到指定保存点voidrollbackToSavepoint(Objectsavepoint)throwsTransactionException;//移除回滚点voidreleaseSavepoint(Objectsavepoint)throwsTransactionException;
TransactionStatus,继承了上面这些接口,额外提供了两个方法
//用于判断当前事务是否设置了保存点booleanhasSavepoint();//这个方法复写了父接口Flushable中的方法//主要用于刷新会话//对于Hibernate/jpa而言就是调用了其session/entityManager的flush方法voidflush();
❝小总结:通过上面的分析我们会发现,TransactionDefinition的主要作用是给出一份事务属性的定义,然后事务管理器根据给出的定义来创建事务,TransactionStatus主要是用来描述创建后的事务的状态❞
在对TransactionDefinition跟TransactionStatus有一定了解后,我们再回到PlatformTransactionManager接口本身,PlatformTransactionManager作为事务管理器的基础接口只是定义管理一个事务必须的三个方法:开启事务,提交事务,回滚事务,接口仅仅是定义了规范而已,真正做事的还是要依赖它的实现类,所以我们来看看它的继承关系
PlatformTransactionManager的实现类
PlatformTransactionManager
AbstractPlatformTransactionManager,Spring提供的一个事务管理的基类,提供了事务管理的模板,实现了Spring事务管理的一个标准流程
鸿蒙官方战略合作共建——HarmonyOS技术社区
判断当前是否已经存在一个事务
应用合适的事务传播行为
在必要的时候挂起/恢复事务
提交时检查事务是否被标记成为rollback-only
在回滚时做适当的修改(是执行真实的回滚/还是将事务标记成rollback-only)
触发注册的同步回调
在AbstractPlatformTransactionManager提供了四个常见的子类,其说明如下
关于事务管理器的详细代码分析放到下篇文章,本文对其有个大概了解即可。
Spring中事务的同步机制
Spring中事务相关的同步机制可以分为两类
资源的同步
行为的同步
什么是资源的同步呢?在一个事务中我们往往会一次执行多个SQL(如果是单条的SQL实际上没有必要开启事务),为了保证事务所有的SQL都能够使用一个数据库连接,这个时候我们需要将数据库连接跟事务进行同步,这个时候数据库连接就是跟这个事务同步的一个资源。
那什么又是行为的同步呢?还是以数据库连接为例子,在事务开启之前我们需要先获取一个数据库连接,同样的在事务提交时我们需要将连接关闭(不一定是真正的关闭,如果是连接池只是归还到连接池中),这个时候关闭连接这个行为也需要跟事务进行同步
那么Spring是如何来管理同步的呢?同样的,Spring也提供了一个同步管理器TransactionSynchronizationManager,这是一个抽象类,其中所有的方法都是静态的,并且所有的方法都是围绕它所申明的几个静态常量字段,如下:
//这就是同步的资源,Spring就是使用这个完成了连接的同步privatestaticfinalThreadLocal<Map<Object,Object>>resources=newNamedThreadLocal<>("Transactionalresources");//TransactionSynchronization完成了行为的同步//关于TransactionSynchronization在后文进行分析privatestaticfinalThreadLocal<Set<TransactionSynchronization>>synchronizations=newNamedThreadLocal<>("Transactionsynchronizations");//事务的名称privatestaticfinalThreadLocal<String>currentTransactionName=newNamedThreadLocal<>("Currenttransactionname");//事务是否被标记成只读privatestaticfinalThreadLocal<Boolean>currentTransactionReadOnly=newNamedThreadLocal<>("Currenttransactionread-onlystatus");//事物的隔离级别privatestaticfinalThreadLocal<Integer>currentTransactionIsolationLevel=newNamedThreadLocal<>("Currenttransactionisolationlevel");//是否真实开启了事务privatestaticfinalThreadLocal<Boolean>actualTransactionActive=newNamedThreadLocal<>("Actualtransactionactive");
可以看到所有的同步都是通过ThreadLocal实现的,对于ThreadLocal本文不做详细分析,如果对ThreadLocal还不了解的同学也没有关系,对于本文而言你只需要知道ThreadLocal能将资源跟当前线程绑定即可,例如ThreadLocal<Map<Object, Object>> resources这个属性就代表要将一个map绑定到当前线程,它提供了set跟get方法,分别用于将属性绑定到线程上以及获取线程上绑定的属性。
上面的几个变量中除了synchronizations之外其余的应该都很好理解,synchronizations中绑定的是一个TransactionSynchronization的集合,那么这个TransactionSynchronization有什么用呢?我们来看看它的接口定义
publicinterfaceTransactionSynchronizationextendsFlushable{//事务完成的状态//0提交//1回滚//2异常状态,例如在事务执行时出现异常,然后回滚,回滚时又出现异常//就会被标记成状态2intSTATUS_COMMITTED=0;intSTATUS_ROLLED_BACK=1;intSTATUS_UNKNOWN=2;//我们绑定的这些TransactionSynchronization需要跟事务同步//1.如果事务挂起,我们需要将其挂起//2.如果事务恢复,我们需要将其恢复defaultvoidsuspend(){}defaultvoidresume(){}@Overridedefaultvoidflush(){}//在事务执行过程中,提供的一些回调方法defaultvoidbeforeCommit(booleanreadOnly){}defaultvoidbeforeCompletion(){}defaultvoidafterCommit(){}defaultvoidafterCompletion(intstatus){}}
可以看到这个接口就是定义了一些方法,这些个方法可以在事务达到不同阶段后执行,可以认为定义了事务执行过程的一些回调行为,这就是我之前说的行为的同步。
模拟Spring事务的实现
本文的最后一部分希望大家模拟一下Spring事务的实现,我们利用现有的AOP来实现事务的管理。数据库访问我们直接使用jdbc,在模拟之前我们先明确两点
鸿蒙官方战略合作共建——HarmonyOS技术社区
切点应该如何定义?
通知要实现什么功能?
我们先说第一个问题,因为是我们自己模拟,所以关于切点的定义我们就设置的尽量简单一些,不妨就直接指定某个包下的所有类。对于第二个问题,我们也不做的过于复杂,在方法执行前开启事务,在方法执行后提交事务并关闭连接,所以我们需要定义一个环绕通知。同时,我们也需要将连接跟事务同步,保证事务中的所有SQL共用一个事务是实现事务管理的必要条件。基于此,我们开始编写代码
我们只需要引入Spring相关的依赖跟JDBC相关依赖即可,该项目仅仅是一个Spring环境下的Java项目,没有Web依赖,也不是SpringBoot项目,项目结构如下:
POM文件:
<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.dmz.framework</groupId><artifactId>mybatis</artifactId><version>1.0-SNAPSHOT</version><dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.15</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.6.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>5.2.6.RELEASE</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.5</version></dependency></dependencies></project>
配置类:
//开启AOP跟扫描组件即可@EnableAspectJAutoProxy@ComponentScan("com.dmz.mybatis.tx_demo")publicclassConfig{}
完成事务管理的核心类:
publicclassTransactionUtil{publicstaticfinalThreadLocal<Connection>synchronousConnection=newThreadLocal<Connection>();privateTransactionUtil(){}publicstaticConnectionstartTransaction(){Connectionconnection=synchronousConnection.get();if(connection==null){try{//这里替换成你自己的连接地址即可connection=DriverManager.getConnection("jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8","root","123");synchronousConnection.set(connection);connection.setAutoCommit(false);}catch(SQLExceptione){e.printStackTrace();}}returnconnection;}publicstaticintexecute(Stringsql,Object...args){Connectionconnection=startTransaction();try(PreparedStatementpreparedStatement=connection.prepareStatement(sql)){if(args!=null){for(inti=1;i<args.length+1;i++){preparedStatement.setObject(i,args[i-1]);}}returnpreparedStatement.executeUpdate();}catch(SQLExceptione){e.printStackTrace();}return0;}publicstaticvoidcommit(){try(Connectionconnection=synchronousConnection.get()){connection.commit();synchronousConnection.remove();}catch(SQLExceptione){e.printStackTrace();}}publicstaticvoidrollback(){try(Connectionconnection=synchronousConnection.get()){connection.rollback();synchronousConnection.remove();}catch(SQLExceptione){e.printStackTrace();}}}
实际需要事务管理的类
@ComponentpublicclassUserService{publicvoidsaveUser(){TransactionUtil.execute("INSERTINTO`test`.`user`(`id`,`name`)VALUES(?,?)",100,"dmz");//测试回滚//thrownewRuntimeException();}}
切面:
@Component@AspectpublicclassTxAspect{@Pointcut("execution(public*com.dmz.mybatis.tx_demo.service..*.*(..))")privatevoidpointcut(){}@Around("pointcut()")publicObjectaround(JoinPointjoinPoint)throwsThrowable{//在方法执行前开启事务TransactionUtil.startTransaction();//执行业务逻辑Objectproceed=null;try{ProceedingJoinPointmethod=(ProceedingJoinPoint)joinPoint;proceed=method.proceed();}catch(Throwablethrowable){//出现异常进行回滚TransactionUtil.rollback();returnproceed;}//方法执行完成后提交事务TransactionUtil.commit();returnproceed;}}
用于测试的主函数:
publicclassMain{publicstaticvoidmain(String[]args){AnnotationConfigApplicationContextac=newAnnotationConfigApplicationContext(Config.class);UserServiceuserService=ac.getBean(UserService.class);userService.saveUser();}}
具体的测试过程跟测试结果我就不放了,大家把代码拷贝过去自行测试就好了
“总结Spring中事务的使用、抽象机制及模拟Spring事务实现”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。