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 小时前
Spring Boot 牵手Spring AI,玩转DeepSeek大模型
后端
0xDevNull2 小时前
Java反射机制深度解析:从原理到实战
java·开发语言·后端
华洛3 小时前
我用AI做了一个48秒的真人精品漫剧,不难也不贵
前端·javascript·后端
华科易迅3 小时前
MybatisPlus增删改查操作
android·java·数据库
AugustRed3 小时前
基于现有的 Controller 接口 API 暴露 MCP
spring·mcp
WZTTMoon3 小时前
Spring Boot 中Servlet、Filter、Listener 四种注册方式全解析
spring boot·后端·servlet
standovon3 小时前
Spring Boot整合Redisson的两种方式
java·spring boot·后端
Cosolar4 小时前
LlamaIndex RAG 本地部署+API服务,快速搭建一个知识库检索助手
后端·openai·ai编程
IAUTOMOBILE4 小时前
Python 流程控制与函数定义:从调试现场到工程实践
java·前端·python
hutengyi4 小时前
PostgreSQL版本选择
java