Spring的xml方式声明式事务控制

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。

相关推荐
进击的荆棘2 小时前
优选算法——模拟
java·开发语言·算法·模拟
撩妹小狗2 小时前
ROS文件解读(package .xml--CMakeLists.txt)
xml·机器人·自动驾驶·ros
徐子童2 小时前
ArrayList和LinkedList的区别
java·开发语言·数据结构·高频面试题
fengxin_rou2 小时前
redis主从和集群一致性、哨兵机制详解
java·开发语言·数据库·redis·缓存
Olafur_zbj2 小时前
【AI】LLM上下文拼接
java·开发语言·spring·llm·context
对酒当歌丶人生几何2 小时前
Spring异步体系与事务一致性实战指南
java·spring·eventlistener
这也能行2 小时前
Tomcat
java·tomcat
小杍随笔2 小时前
【Rust中所有符号的作用及使用场景详解】
java·算法·rust