Spring的xml方式声明式事务控制一、概述前面我们讲了Spring的AOP配置但是这种直接配置在开发中使用得较少一般是事务控制使用得较多在事务控制中利用的也是AOP的原理只不过把事务是当作AOP当中的通知且这个通知由Spring来产生我们只负责配置下面就事务的一些知识和xml方式声明式事务控制进行讲解二、实验以转账来进行实验如果让加钱和减钱各自一个事务的话那么当出现异常情况时可能是减钱了但是另一个账户没有加钱的情况所以要将加钱和减钱组成一个事务?xml version1.0encodingUTF-8?beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxmlns:aophttp://www.springframework.org/schema/aopxmlns:txhttp://www.springframework.org/schema/txxsi:schemaLocation http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd !--组件扫描--context:component-scan base-packagecom.itheima/!--加载properties文件--context:property-placeholder locationclasspath:jdbc.properties/!--配置数据源信息--bean iddataSourceclasscom.alibaba.druid.pool.DruidDataSourceproperty namedriverClassNamevalue${jdbc.driver}/propertyproperty nameurlvalue${jdbc.url}/propertyproperty nameusernamevalue${jdbc.username}/propertyproperty namepasswordvalue${jdbc.password}/property/bean!--配置SqlSessionFactoryBean作用将SqlSessionFactory存储到spring容器--beanclassorg.mybatis.spring.SqlSessionFactoryBeanproperty namedataSourcerefdataSource/property/bean!--MapperScannerConfigurer,作用扫描指定的包产生Mapper对象存储到Spring容器--beanclassorg.mybatis.spring.mapper.MapperScannerConfigurerproperty namebasePackagevaluecom.itheima.mapper/property/bean!--还要配置事物管理器在编程式事务中讲过事物管理器具有回滚、获得、提交事务对象的功能不同框架的事务管理器不同--!--底层是mybatismybatis底层是jdbc,jdbc需要connection所以要数据源--beanclassorg.springframework.jdbc.datasource.DataSourceTransactionManageridtransactionManagerproperty namedataSourcerefdataSource//bean!--然后是要配置通知因为这个通知是Spring提供的所以需要用到tx标签--tx:advice idtxadvicetransaction-managertransactionManagertx:attributestx:method name*//tx:attributes/tx:advice!--使用advisor配置AOP--aop:configaop:pointcut idtxPointcutexpressionexecution(* com.itheima.service.impl.*.*(..))/!--配置织入--aop:advisor advice-reftxadvicepointcut-reftxPointcut//aop:config/beans先是从下往上配要用Spring的事务的话也就是通知就需要使用advisor配置织入而且配置通知时需要使用tx标签其中事务中又要配transaction-manager事务管理器一般要配置事务管理器的话都要实现它的继承关系如下在这里Dao层使用的框架是mybatismybatis底层是jdbc,jdbc需要connection所以要数据源配置的是DataSourceTransactionManager且要指定数据源。同时在通知中还要指定method。这样加钱和减钱组成一个事务出现异常加钱和减钱都不能完成了packagecom.itheima.service.impl;importcom.itheima.mapper.AccountMapper;importcom.itheima.service.AccountService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importorg.springframework.transaction.annotation.Isolation;importorg.springframework.transaction.annotation.Propagation;importorg.springframework.transaction.annotation.Transactional;Service(accountService)Transactional(isolationIsolation.READ_COMMITTED,propagationPropagation.REQUIRED)publicclassAccountServiceImpl2implementsAccountService{AutowiredprivateAccountMapperaccountMapper;OverridepublicvoidtransferMoney(StringoutAccount,StringinAccount,Integermoney){// 如果是编程类事务的话先开一个事务然后try catch以下有异常就回滚accountMapper.decrMoney(outAccount,money);inti1/0;accountMapper.incrMoney(inAccount,money);}publicvoidregistAccount(){}}packagecom.itheima;importcom.itheima.config.SpringConfig;importcom.itheima.service.AccountService;importorg.springframework.context.ApplicationContext;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;importorg.springframework.context.support.ClassPathXmlApplicationContext;publicclassAccountTest{publicstaticvoidmain(String[]args){ApplicationContextappnewClassPathXmlApplicationContext(applicationContext.xml);//ApplicationContext app new AnnotationConfigApplicationContext(SpringConfig.class);AccountServiceaccountServiceapp.getBean(AccountService.class);accountService.transferMoney(tom,lucy,500);}}结果钱是没有转成功的。在这里值得注意的是tx的attributes属性name方法名称 *代表通配符 添加操作 addUser、addAccount、addOrders 可统一配置为 add *isolation事务的隔离级别用于解决事务并发问题timeout超时时间默认值为-1单位是sread-only是否为只读模式查询操作建议设置为只读propagation事务的传播行为用于解决业务方法调用业务方法时出现的事务嵌套问题要是没有设置这些属性。会应用数据库默认的但是必须加上tx:method name“*”/在项目中常常是这样设置各大功能一个留一个保底的tx:method nameadd*/tx:method nameupdate*/tx:method namedelete*/tx:method nameselect*/tx:method name*/一般是REQUIRED和SUPPORTS使用得多需要记住。三、原理解析 tx:advice是通过xml配置的当然是找它的handler了http\://www.springframework.org/schema/txorg.springframework.transaction.config.TxNamespaceHandler进入TxNamespaceHandler//// Source code recreated from a .class file by IntelliJ IDEA// (powered by FernFlower decompiler)//packageorg.springframework.transaction.config;importorg.springframework.beans.factory.xml.NamespaceHandlerSupport;importorg.w3c.dom.Element;publicclassTxNamespaceHandlerextendsNamespaceHandlerSupport{staticfinalStringTRANSACTION_MANAGER_ATTRIBUTEtransaction-manager;staticfinalStringDEFAULT_TRANSACTION_MANAGER_BEAN_NAMEtransactionManager;publicTxNamespaceHandler(){}staticStringgetTransactionManagerName(Elementelement){returnelement.hasAttribute(transaction-manager)?element.getAttribute(transaction-manager):transactionManager;}publicvoidinit(){this.registerBeanDefinitionParser(advice,newTxAdviceBeanDefinitionParser());this.registerBeanDefinitionParser(annotation-driven,newAnnotationDrivenBeanDefinitionParser());this.registerBeanDefinitionParser(jta-transaction-manager,newJtaTransactionManagerBeanDefinitionParser());}}进入TxAdviceBeanDefinitionParser()既然是Parser肯定有Parser方法不在本类就在父类和爷爷类这个是在爷爷类publicabstractclassAbstractBeanDefinitionParserimplementsBeanDefinitionParser{publicstaticfinalStringID_ATTRIBUTEid;publicstaticfinalStringNAME_ATTRIBUTEname;publicAbstractBeanDefinitionParser(){}NullablepublicfinalBeanDefinitionparse(Elementelement,ParserContextparserContext){AbstractBeanDefinitiondefinitionthis.parseInternal(element,parserContext);if(definition!null!parserContext.isNested()){try{Stringidthis.resolveId(element,definition,parserContext);if(!StringUtils.hasText(id)){parserContext.getReaderContext().error(Id is required for element parserContext.getDelegate().getLocalName(element) when used as a top-level tag,element);}String[]aliasesnull;if(this.shouldParseNameAsAliases()){Stringnameelement.getAttribute(name);if(StringUtils.hasLength(name)){aliasesStringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));}}BeanDefinitionHolderholdernewBeanDefinitionHolder(definition,id,aliases);this.registerBeanDefinition(holder,parserContext.getRegistry());if(this.shouldFireEvents()){BeanComponentDefinitioncomponentDefinitionnewBeanComponentDefinition(holder);this.postProcessComponentDefinition(componentDefinition);parserContext.registerComponent(componentDefinition);}}catch(BeanDefinitionStoreExceptionex){Stringmsgex.getMessage();parserContext.getReaderContext().error(msg!null?msg:ex.toString(),element);returnnull;}}returndefinition;}让端点到达tx:advice然后往下看还有一段BeanDefinitionHolderholdernewBeanDefinitionHolder(definition,id,aliases);this.registerBeanDefinition(holder,parserContext.getRegistry());这是注册BeanDefinition看对应的holder可以看到这是名为txadvice值为TransactionInterceptor的类。那么我们就分析TransactionInterceptorpublicclassTransactionInterceptorextendsTransactionAspectSupportimplementsMethodInterceptor,Serializable{publicTransactionInterceptor(){}发现它实现了MethodInterceptor接口依据之前学过的Advisor配置AOP织入可以知道这相当于配置环绕通知是通过它的invoke方法来配置的NullablepublicObjectinvoke(MethodInvocationinvocation)throwsThrowable{Class?targetClassinvocation.getThis()!null?AopUtils.getTargetClass(invocation.getThis()):null;Methodvar10001invocation.getMethod();invocation.getClass();returnthis.invokeWithinTransaction(var10001,targetClass,invocation::proceed);}targetClass进入invokeWithinTransaction}}else{TransactionInfotxInfothis.createTransactionIfNecessary(ptm,txAttr,joinpointIdentification);ObjectretVal;try{retValinvocation.proceedWithInvocation();}catch(Throwableex){该有的都返回了createTransactionIfNecessary就是开事务了看到createTransactionIfNecessary进入statustm.getTransaction(txAttr);去看tm是谁protectedTransactionInfocreateTransactionIfNecessary(NullablePlatformTransactionManagertm,NullableTransactionAttributetxAttr,finalStringjoinpointIdentification){tm是PlatformTransactionManager平台事务管理器同时我们也看到了createTransactionIfNecessary中的参数ptm那么它是什么呢PlatformTransactionManagerptmthis.asPlatformTransactionManager(tm);tm又是TransactionManagertmthis.determineTransactionManager(txAttr);到此为止后面是最终是通过解析xml文件获取的。再来看status tm.getTransaction(txAttr);进入getTransaction()publicfinalTransactionStatusgetTransaction(NullableTransactionDefinitiondefinition)throwsTransactionException{TransactionStatus事务管理器状态编程式事务的一种PlatformTransactionManager也是其中一种同时又看到这一句returnthis.startTransaction(def,transaction,debugEnabled,suspendedResources);这是开启事务就执行完了createTransactionIfNecessary所以是在执行createTransactionIfNecessary时内部已经开事务了往createTransactionIfNecessary下又可以看到this.commitTransactionAfterReturning(txInfo);就是提交事务了。而开启事务和提交事务又是在invoke方法执行的所以而这个invoke方法又是属于TransactionInterceptor实现了 MethodInterceptor所以我们在配置的时候就通过Advisor来配置TransactionInterceptor实际上声明式事务本质上就是AOP。