Spring的xml方式声明式事务控制
一、概述
前面我们讲了Spring的AOP配置,但是这种直接配置在开发中使用得较少,一般是事务控制使用得较多,在事务控制中利用的也是AOP的原理,只不过把事务是当作AOP当中的通知,且这个通知由Spring来产生,我们只负责配置,下面就事务的一些知识和xml方式声明式事务控制进行讲解:


二、实验
以转账来进行实验,如果让加钱和减钱各自一个事务的话,那么当出现异常情况时,可能是减钱了,但是另一个账户没有加钱的情况:所以要将加钱和减钱组成一个事务:
java
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi: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-package="com.itheima"/>
<!--加载properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置数据源信息-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--配置SqlSessionFactoryBean,作用将SqlSessionFactory存储到spring容器-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--MapperScannerConfigurer,作用扫描指定的包,产生Mapper对象存储到Spring容器-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.itheima.mapper"></property>
</bean>
<!-- 还要配置事物管理器,在编程式事务中讲过事物管理器具有回滚、获得、提交事务对象的功能,不同框架的事务管理器不同-->
<!-- 底层是mybatis,mybatis底层是jdbc,jdbc需要connection,所以要数据源-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 然后是要配置通知,因为这个通知是Spring提供的,所以需要用到tx标签-->
<tx:advice id="txadvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- 使用advisor配置AOP-->
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* com.itheima.service.impl.*.*(..))"/>
<!-- 配置织入-->
<aop:advisor advice-ref="txadvice" pointcut-ref="txPointcut"/>
</aop:config>
</beans>
先是从下往上配要用Spring的事务的话(也就是通知)就需要使用advisor配置织入,而且配置通知时需要使用tx标签,其中事务中又要配transaction-manager事务管理器,一般要配置事务管理器的话都要实现,它的继承关系如下:

在这里Dao层使用的框架是mybatis,mybatis底层是jdbc,jdbc需要connection,所以要数据源,配置的是DataSourceTransactionManager,且要指定数据源。同时在通知中还要指定method。这样加钱和减钱组成一个事务,出现异常加钱和减钱都不能完成了:
java
package com.itheima.service.impl;
import com.itheima.mapper.AccountMapper;
import com.itheima.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service("accountService")
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
public class AccountServiceImpl2 implements AccountService {
@Autowired
private AccountMapper accountMapper;
@Override
public void transferMoney(String outAccount, String inAccount, Integer money) {
// 如果是编程类事务的话:先开一个事务,然后try catch以下有异常就回滚
accountMapper.decrMoney(outAccount,money);
int i = 1/0;
accountMapper.incrMoney(inAccount,money);
}
public void registAccount(){
}
}
java
package com.itheima;
import com.itheima.config.SpringConfig;
import com.itheima.service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AccountTest {
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
//ApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig.class);
AccountService accountService = app.getBean(AccountService.class);
accountService.transferMoney("tom","lucy",500);
}
}
结果:


钱是没有转成功的。
在这里值得注意的是tx的attributes属性:
-
name:方法名称 *代表通配符 添加操作 addUser、addAccount、addOrders 可统一配置为 add *
-
isolation:事务的隔离级别,用于解决事务并发问题
-
timeout:超时时间,默认值为-1,单位是s
-
read-only:是否为只读模式,查询操作建议设置为只读
-
propagation:事务的传播行为,用于解决业务方法调用业务方法时出现的事务嵌套问题
要是没有设置这些属性。会应用数据库默认的,但是必须加上<tx:method name="*"/>,在项目中常常是这样设置(各大功能一个,留一个保底的):
java
<tx:method name="add*"/>
<tx:method name="update*"/>
<tx:method name="delete*"/>
<tx:method name="select*"/>
<tx:method name="*"/>


一般是REQUIRED和SUPPORTS使用得多需要记住。
三、原理解析
tx:advice是通过xml配置的当然是找它的handler了:
java
http\://www.springframework.org/schema/tx=org.springframework.transaction.config.TxNamespaceHandler
进入TxNamespaceHandler:
java
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.transaction.config;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.w3c.dom.Element;
public class TxNamespaceHandler extends NamespaceHandlerSupport {
static final String TRANSACTION_MANAGER_ATTRIBUTE = "transaction-manager";
static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager";
public TxNamespaceHandler() {
}
static String getTransactionManagerName(Element element) {
return element.hasAttribute("transaction-manager") ? element.getAttribute("transaction-manager") : "transactionManager";
}
public void init() {
this.registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());
this.registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
this.registerBeanDefinitionParser("jta-transaction-manager", new JtaTransactionManagerBeanDefinitionParser());
}
}
进入TxAdviceBeanDefinitionParser(),既然是Parser肯定有Parser方法,不在本类就在父类和爷爷类,这个是在爷爷类:
java
public abstract class AbstractBeanDefinitionParser implements BeanDefinitionParser {
public static final String ID_ATTRIBUTE = "id";
public static final String NAME_ATTRIBUTE = "name";
public AbstractBeanDefinitionParser() {
}
@Nullable
public final BeanDefinition parse(Element element, ParserContext parserContext) {
AbstractBeanDefinition definition = this.parseInternal(element, parserContext);
if (definition != null && !parserContext.isNested()) {
try {
String id = this.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[] aliases = null;
if (this.shouldParseNameAsAliases()) {
String name = element.getAttribute("name");
if (StringUtils.hasLength(name)) {
aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
}
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
this.registerBeanDefinition(holder, parserContext.getRegistry());
if (this.shouldFireEvents()) {
BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
this.postProcessComponentDefinition(componentDefinition);
parserContext.registerComponent(componentDefinition);
}
} catch (BeanDefinitionStoreException ex) {
String msg = ex.getMessage();
parserContext.getReaderContext().error(msg != null ? msg : ex.toString(), element);
return null;
}
}
return definition;
}
让端点到达tx:advice:

然后往下看还有一段:
java
BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
this.registerBeanDefinition(holder, parserContext.getRegistry());
这是注册BeanDefinition,看对应的holder:

可以看到这是名为txadvice,值为TransactionInterceptor的类。
那么我们就分析TransactionInterceptor:
java
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
public TransactionInterceptor() {
}
发现它实现了MethodInterceptor接口,依据之前学过的Advisor配置AOP织入可以知道这相当于配置环绕通知,是通过它的invoke方法来配置的:
java
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
Class<?> targetClass = invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null;
Method var10001 = invocation.getMethod();
invocation.getClass();
return this.invokeWithinTransaction(var10001, targetClass, invocation::proceed);
}
targetClass:

进入invokeWithinTransaction:
java
}
} else {
TransactionInfo txInfo = this.createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
retVal = invocation.proceedWithInvocation();
} catch (Throwable ex) {

该有的都返回了,createTransactionIfNecessary就是开事务了,看到createTransactionIfNecessary,进入:
java
status = tm.getTransaction(txAttr);
去看tm是谁:
java
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm, @Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
tm是PlatformTransactionManager,平台事务管理器
同时我们也看到了createTransactionIfNecessary中的参数ptm,那么它是什么呢?
java
PlatformTransactionManager ptm = this.asPlatformTransactionManager(tm);
tm又是:
java
TransactionManager tm = this.determineTransactionManager(txAttr);
到此为止,后面是最终是通过解析xml文件获取的。
再来看status = tm.getTransaction(txAttr);,进入getTransaction():
java
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
TransactionStatus事务管理器状态,编程式事务的一种,(PlatformTransactionManager也是其中一种)
同时又看到这一句:
java
return this.startTransaction(def, transaction, debugEnabled, suspendedResources);
这是开启事务,就执行完了createTransactionIfNecessary,所以是在执行createTransactionIfNecessary时内部已经开事务了,
往createTransactionIfNecessary下又可以看到:
java
this.commitTransactionAfterReturning(txInfo);
就是提交事务了。
而开启事务和提交事务又是在invoke方法执行的,所以而这个invoke方法又是属于TransactionInterceptor(实现了 MethodInterceptor),所以我们在配置的时候就通过Advisor来配置TransactionInterceptor,实际上声明式事务本质上就是AOP。