在互联网系统中同时运行着成千上百个线程是十分常见的事情,尤其当一个热门出现时,用户几乎同时打开手机、电脑、平板灯设备进行访问,这样就会导致数据库处在一个多事务访问的环境中,从而引发数据丢失或者数据不一致的现象,同时也会导致服务器压力陡增,甚至出现数据库死锁或者瘫痪;在大部分情况下,数据库事务要么同时成功要么同时失败,从而保证数据的准确和一致,但有些场景比如批量事务,就不能但看事务的成功与否,一个事务失败那么该事务内所处理的所有数据都失败并回滚也不合理,诸多场景Spring均提供了很好的支撑

Spring数据库事务管理器的设计

在Spring5之前版本,数据库事务是通过PlatformTransactionManager管理的,Spring5之后,引入了响应式编程,将TransactionManager作为底层接口,首先了解一下PlatformTransactionManager,源码如下

/**
 * 定义了一个平台无关的事务管理器接口,它是{@link TransactionManager}接口的扩展。
 * 提供了获取事务状态、提交事务和回滚事务的能力。
 */
public interface PlatformTransactionManager extends TransactionManager {

    /**
     * 获取一个事务状态对象,这个事务状态对象用于后续的事务控制。
     *
     * @param var1 可选的事务定义,用于定义事务的属性。如果为null,则使用默认的事务设置。
     * @return 一个事务状态对象,用于控制当前事务。
     * @throws TransactionException 如果在获取事务状态时发生错误。
     */
    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;

    /**
     * 提交当前事务。这会导致所有尚未提交的变化被提交到底层资源(例如数据库)。
     *
     * @param var1 当前事务的状态对象,用于标识要提交的事务。
     * @throws TransactionException 如果在提交事务时发生错误。
     */
    void commit(TransactionStatus var1) throws TransactionException;

    /**
     * 回滚当前事务。这会导致所有尚未提交的变化被回滚,撤销所有对数据库的更改。
     *
     * @param var1 当前事务的状态对象,用于标识要回滚的事务。
     * @throws TransactionException 如果在回滚事务时发生错误。
     */
    void rollback(TransactionStatus var1) throws TransactionException;
}

在数据库编程中,JdbcTemplate并不能支持事务,支持事务需要使用TransactionTemplate,它是Spring提供的事务管理模板,这个模板抽象了底层的事务管理器(PlatformTransactionManager),使用户能够以声明式的方式定义事务边界,其源码如下

//
// TransactionTemplate 类提供了一种简化事务管理的模板方法实现。
// 它抽象了底层的事务管理器(PlatformTransactionManager),使用户能够以声明式的方式定义事务边界。
//

package org.springframework.transaction.support;

// 引入相关依赖和接口
import java.lang.reflect.UndeclaredThrowableException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.lang.Nullable;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.util.Assert;

public class TransactionTemplate extends DefaultTransactionDefinition implements TransactionOperations, InitializingBean {
    // 日志记录器
    protected final Log logger = LogFactory.getLog(this.getClass());
    // 事务管理器,可为空
    @Nullable
    private PlatformTransactionManager transactionManager;

    // 默认构造函数
    public TransactionTemplate() {
    }

    // 带事务管理器的构造函数
    public TransactionTemplate(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    // 带事务管理和事务定义的构造函数
    public TransactionTemplate(PlatformTransactionManager transactionManager, TransactionDefinition transactionDefinition) {
        super(transactionDefinition);
        this.transactionManager = transactionManager;
    }

    // 设置事务管理器
    public void setTransactionManager(@Nullable PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    // 获取事务管理器
    @Nullable
    public PlatformTransactionManager getTransactionManager() {
        return this.transactionManager;
    }

    // 初始化检查,确保事务管理器已设置
    public void afterPropertiesSet() {
        if (this.transactionManager == null) {
            throw new IllegalArgumentException("Property 'transactionManager' is required");
        }
    }

    /**
     * 执行事务操作。
     * @param action 事务回调,其中包含实际的事务逻辑。
     * @return 事务执行中的返回结果。
     * @throws TransactionException 事务操作中的异常。
     */
    @Nullable
    public <T> T execute(TransactionCallback<T> action) throws TransactionException {
        Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
        if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
            // 如果事务管理器支持优先使用回调,则使用之
            return ((CallbackPreferringPlatformTransactionManager)this.transactionManager).execute(this, action);
        } else {
            // 否则,按照常规方式执行事务
            TransactionStatus status = this.transactionManager.getTransaction(this);

            Object result;
            try {
                result = action.doInTransaction(status);
            } catch (Error | RuntimeException var5) {
                // 处理运行时异常,回滚事务
                this.rollbackOnException(status, var5);
                throw var5;
            } catch (Throwable var6) {
                // 处理其他异常,回滚事务
                this.rollbackOnException(status, var6);
                throw new UndeclaredThrowableException(var6, "TransactionCallback threw undeclared checked exception");
            }

            // 提交事务
            this.transactionManager.commit(status);
            return (T) result;
        }
    }

    /**
     * 回滚事务。
     * @param status 事务状态。
     * @param ex 引起回滚的异常。
     * @throws TransactionException 回滚操作中的异常。
     */
    private void rollbackOnException(TransactionStatus status, Throwable ex) throws TransactionException {
        Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
        this.logger.debug("Initiating transaction rollback on application exception", ex);

        try {
            this.transactionManager.rollback(status);
        } catch (TransactionSystemException var4) {
            // 处理回滚时的系统异常
            this.logger.error("Application exception overridden by rollback exception", ex);
            var4.initApplicationException(ex);
            throw var4;
        } catch (Error | RuntimeException var5) {
            // 处理回滚时的运行时异常
            this.logger.error("Application exception overridden by rollback exception", ex);
            throw var5;
        }
    }

    // 实现Equals方法,考虑事务管理器的比较
    public boolean equals(@Nullable Object other) {
        return this == other || super.equals(other) && (!(other instanceof TransactionTemplate) || this.getTransactionManager() == ((TransactionTemplate)other).getTransactionManager());
    }
}
  • 事务的创建、提交、回滚均是通过PlatformTransactionManager接口完成的
  • 当事务产生异常时回滚,在默认的实现中所有的异常都会回滚,但Spring也允许通过配置在某些异常发生时是否回滚事务
  • 当无异常时,提交事务

互联网应用主流框架整合之数据库事务管理_MyBatis-Spring


如图所示,Spring有多种事务管理器,在诸多数据库事务管理其中,常用的是DataSourceTransactionManager,它继承抽象事务管理器AbstractPlatformTransactionManager,而AbstractPlatformTransactionManager实现了PlatformTransactionManager接口,更多的关系可在编译器中自行阅读源码即可

配置事务管理器

如果基于MyBatis框架,用的最多的事务管理器是DataSourceTransactionManager(org.springframework.jdbc.datasource.DataSourceTransactionManager;),而如果是基于Hibernate框架,那么用的较多的应该是spring-orm(org.springframework.orm.hibernate5.HibernateTransactionManager;),但大同小异

配置一个简单的事务管理器,需要加入XML的事务命名空间,代码如下

<?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:p="http://www.springframework.org/schema/p"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
      http://www.springframework.org/schema/aop
      http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
      http://www.springframework.org/schema/tx
      http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <!-- 驱动事务管理器工作 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <!-- 数据库连接池 -->
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://192.168.3.115:3306/ssm?useUnicode=true;characterEncoding=utf8" />
        <property name="username" value="root" />
        <property name="password" value="123456" />
        <!--连接池的最大数据库连接数 -->
        <property name="maxTotal" value="255" />
        <!--最大等待连接中的数量 -->
        <property name="maxIdle" value="5" />
        <!--最大等待毫秒数 -->
        <property name="maxWaitMillis" value="10000" />
    </bean>

    <!-- 配置JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 配置数据源事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

</beans>

该配置使用DataSourceTransactionManager定义数据库事务管理器,该管理器依赖数据源,如此配置Spring就知道开发者已经将数据库事务委托给事务管理器(TransactionManager)管理了,在JdbcTemplate源码分析时,可以看到数据库资源的产生和释放如果没有委托给数据库管理器,那么就由JdbcTemplate管理,如上配置完成后,JdbcTemplate的数据库资源和事务就由事务管理器处理了

在Spring中可使用声明式事务或者编程式事务,但因为编程式事务几乎不再使用,因为产生的冗余比较多,代码可读性差;声明式事务是主要方式,它又可以通过XML配置的方式和注解事务的方式实现,这其中为了避免XML泛滥,又是以注解@Transactional为主要方式

以Java配置的方式实现Spring数据库事务

用Java配置的方式实现Spring数据库事务,需要在配置类中实现接口TransactionManagementConfigurerannotationDrivenTransactionManager方法,Spring会把annotationDrivenTransactionManager方法返回的事务管理器作为程序中的事务管理器,如下代码所示

package com.transaction.config;

import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;

/**
 * 事务配置类,负责初始化数据源、JdbcTemplate、事务管理器和SqlSessionFactory等。
 */
@Configuration
@ComponentScan("com.transaction")
// 启用事务管理
@EnableTransactionManagement
@MapperScan(basePackages = "com.transaction", annotationClass = Mapper.class)
public class TransactionConfig implements TransactionManagementConfigurer {
    private DataSource dataSource = null;

    /**
     * 初始化数据源。
     *
     * @return DataSource 返回配置好的数据源实例。
     */
    @Bean(name = "dataSource")
    public DataSource initDataSource() {
        if (dataSource != null) {
            return dataSource;
        }
        Properties props = new Properties();
        // 配置数据库连接属性
        props.setProperty("driverClassName", "com.mysql.jdbc.Driver");
        props.setProperty("url", "jdbc:mysql://localhost:3306/ssm");
        props.setProperty("username", "root");
        props.setProperty("password", "123456");
        // 配置连接池属性
        props.setProperty("maxActive", "200");
        props.setProperty("maxIdle", "20");
        props.setProperty("maxWait", "30000");
        try {
            dataSource = BasicDataSourceFactory.createDataSource(props);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dataSource;
    }

    /**
     * 初始化JdbcTemplate。
     *
     * @param dataSource 数据源
     * @return JdbcTemplate 返回配置好的JdbcTemplate实例。
     */
    @Bean(name = "jdbcTemplate")
    public JdbcTemplate initjdbcTemplate(@Autowired DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    /**
     * 创建基于注解的事务管理器。
     *
     * @return PlatformTransactionManager 返回配置好的事务管理器实例。
     */
    @Override
    @Bean(name = "transactionManager")
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        // 配置事务管理器的数据源
        transactionManager.setDataSource(initDataSource() );
        return transactionManager;
    }

    /**
     * 创建SqlSessionFactory。
     *
     * @param dataSource 数据源
     * @return SqlSessionFactory 返回配置好的SqlSessionFactory实例。
     * @throws Exception 如果配置失败抛出异常。
     */
    @Bean("sqlSessionFactory")
    public SqlSessionFactory createSqlSessionFactoryBean(
            @Autowired DataSource dataSource) throws Exception {
        // 设置配置文件
        String cfgFile = "mybatis-config.xml";
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        // 设置配置文件位置
        Resource configLocation = new ClassPathResource(cfgFile);
        sqlSessionFactoryBean.setConfigLocation(configLocation);
        return sqlSessionFactoryBean.getObject();
    }

}

代码实现了TransactionManagementConfigurer接口所定义的方法annotationDrivenTransactionManager,用DataSourceTransactionManager定义数据库事务管理器实例,然后设置数据源

这个配置类前使用了注解@EnableTransactionManagement,然后在Spring上下文中使用了事务注解@Transactional,Spring就会使用这个数据库事务管理器去管理事务了

编程式事务

编程式事务以代码的方式管理事务,或者说事务将通过代码实现,这里需要一个定义事务的接口TransactionDefinition,使用其默认的实现类DefaultTransactionDefinition来定义事务,如下代码所示

/**
     * 测试注解配置的Spring事务管理。
     * 该方法通过注解配置的方式,展示了一个基本的事务管理示例,包括事务的开启、执行数据库操作、事务的提交或回滚。
     */
    public static void testAnnotation() {
        // 创建AnnotationConfigApplicationContext,并通过指定TransactionConfig类来加载配置
        ApplicationContext ctx = new AnnotationConfigApplicationContext(TransactionConfig.class);
        // 从ApplicationContext中获取JdbcTemplate实例
        JdbcTemplate jdbcTemplate = ctx.getBean(JdbcTemplate.class);

        // 创建默认的事务定义
        TransactionDefinition def = new DefaultTransactionDefinition();
        // 从ApplicationContext中获取PlatformTransactionManager实例
        PlatformTransactionManager transactionManager = ctx.getBean(PlatformTransactionManager.class);
        // 根据事务定义获取事务状态
        TransactionStatus status = transactionManager.getTransaction(def);
        try {
            // 执行SQL插入操作
            jdbcTemplate.update("insert into t_role(role_name, note) values('role_name_transactionManager', 'note_transactionManager')");
            // 提交事务
            transactionManager.commit(status);
        } catch(Exception ex) {
            // 如果有异常发生,则回滚事务
            transactionManager.rollback(status);
        }
    }

从代码中不难看出,所有的事务是由开发者自己控制的,由于事务已交由事务管理器管理,所以JdbcTemplate的数据库资源已经由事务管理器管理,当它执行完insert语句时不会自动提交事务,需要使用事务管理器的commit方法,回滚事务则需要使用事务管理器的rollback方法,这就是编程式事务的使用方式

声明式事务

声明式事务是一种约定型的事务,大部分情况下,约定了当使用数据库时,如果代码中发生了异常,则回滚事务,否则提交事务,从而保证数据库数据的一致性;Spring给了一个约定(AOP开发也给了我们一个约定),如果使用声明式事务,当开发者的业务方法不发生异常或者发生了被允许的异常时,Spring会让事务管理器提交事务,当发生不被允许的异常时,则让事务管理器回滚事务

声明式事务允许自定义事务接口-TransactionDefinition,它可以由XML或者@Transactional配置

@Transactional配置项

首先看一下其源码,如下所示

/**
 * 标示方法或类需要事务管理的注解。应用于类型或方法上,以指定事务的配置属性。
 * 这些属性包括事务的传播行为、隔离级别、超时、只读标志以及触发回滚的异常类型。
 */
@Target({ElementType.TYPE, ElementType.METHOD}) // 指定注解可以应用于类型(类)或方法上
@Retention(RetentionPolicy.RUNTIME) // 指定注解在运行时可被读取
@Inherited // 指定子类可以继承此注解
@Documented // 指定此注解应被包含在文档中
@Reflective // 指示Spring AOT工具处理此注解以优化反射调用
public @interface Transactional {
    /**
     * 事务管理器的名称。可以使用此属性来指定应用哪个事务管理器。如果未指定,则使用默认的事务管理器。
     * 此属性与transactionManager属性作用相同,通过@AliasFor注解实现相互引用。
     * 它是一个SpringIoC容器里的一个Bean id,这个Bean需要实现接口PlatformTransactionManager
     */
    @AliasFor("transactionManager")
    String value() default "";

    /**
     * 与value()相同,提供另一个名称来指定事务管理器。
     */
    @AliasFor("value")
    String transactionManager() default "";

    /**
     * 为事务分配的标签数组。可用于对事务进行分类或筛选。
     */
    String[] label() default {};

    /**
     * 指定事务的传播行为。默认为Propagation.REQUIRED。
     */
    Propagation propagation() default Propagation.REQUIRED;

    /**
     * 指定事务的隔离级别。默认为Isolation.DEFAULT。
     */
    Isolation isolation() default Isolation.DEFAULT;

    /**
     * 指定事务的超时时间,单位为秒。-1表示使用默认超时。可以通过timeoutString()以字符串形式指定。
     */
    int timeout() default -1;

    /**
     * 以字符串形式指定事务的超时时间。提供此属性是为了兼容性,一般建议使用timeout()。
     */
    String timeoutString() default "";

    /**
     * 指定事务是否为只读模式。默认为false。
     */
    boolean readOnly() default false;

    /**
     * 指定哪些异常类型应当触发事务回滚。为空时表示只对RuntimeException和Error触发回滚。
     */
    Class<? extends Throwable>[] rollbackFor() default {};

    /**
     * 以类名字符串形式指定哪些异常类型应当触发事务回滚。提供此属性是为了兼容性,一般建议使用rollbackFor()。
     */
    String[] rollbackForClassName() default {};

    /**
     * 指定哪些异常类型不应触发事务回滚。为空时表示对所有异常都触发回滚。
     */
    Class<? extends Throwable>[] noRollbackFor() default {};

    /**
     * 以类名字符串形式指定哪些异常类型不应触发事务回滚。提供此属性是为了兼容性,一般建议使用noRollbackFor()。
     */
    String[] noRollbackForClassName() default {};
}

每一个配置项的意义,看注释即可,注意,在使用声明式事务时,需要配置注解驱动,要在XML配置中配置如下内容,便可以使用注解@Transactional

<!-- 驱动事务管理器工作 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

使用XML配置事务管理器

使用XML配置事务管理器的方法很多,但不常用,更多的时候应该采用注解式的事务,首先要配置一个事务拦截器TransactionInterceptor,此处可以类比一下AOP,代码如下

<!-- 定义TransactionInterceptor bean,用于事务拦截器 -->
<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
    <!-- 配置事务管理器 -->
    <property name="transactionManager" ref="transactionManager"/>
    <!-- 配置事务属性,键为方法名模式,值为事务传播行为和隔离级别 -->
    <property name="transactionAttributes">
        <props>
            <!-- 定义各个操作的事务属性 key代表业务方法的正则式匹配,其内容可以配置各类事务定义参数-->
            <prop key="insert">PROPAGATION_REQUIRED, ISOLATION_READ_UNCOMMITTED</prop>
            <prop key="save">PROPAGATION_REQUIRED, ISOLATION_READ_UNCOMMITTED</prop>
            <prop key="add">PROPAGATION_REQUIRED, ISOLATION_READ_UNCOMMITTED</prop>
            <prop key="select">PROPAGATION_REQUIRED, readOnly</prop>
            <prop key="get">PROPAGATION_REQUIRED, readOnly</prop>
            <prop key="find">PROPAGATION_REQUIRED, readOnly</prop>
            <prop key="del">PROPAGATION_REQUIRED, ISOLATION_READ_UNCOMMITTED</prop>
            <prop key="remove">PROPAGATION_REQUIRED, ISOLATION_READ_UNCOMMITTED</prop>
            <prop key="update">PROPAGATION_REQUIRED, ISOLATION_READ_UNCOMMITTED</prop>
        </props>
    </property>
</bean>

配置transactionAttributes的内容是关键点,SpringIoC启动时会解析这些内容,放到事务定义接口TransactionDefinition中,在运行时回根据正则式的匹配度决定采取哪种策略,显然使用了拦截器的编程技术,也说明声明式事务的底层原理是SpringAOP技术

上述配置项只告诉Spring事务策略,并没有告知Spring拦截哪些类,需要近一步的配置,如下所示

<!-- 创建一个BeanNameAutoProxyCreator Bean,用于自动代理指定的Bean -->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <!-- 指定需要进行代理的Bean名称,这里使用通配符*ServiceImpl,表示所有以ServiceImpl结尾的Bean都将被代理 -->
    <property name="beanNames">
        <list>
            <value>*ServiceImpl</value>
        </list>
    </property>
    <!-- 指定应用到代理Bean上的拦截器,这里使用transactionInterceptor,即事务拦截器 -->
    <property name="interceptorNames">
        <list>
            <value>transactionInterceptor</value>
        </list>
    </property>
</bean>

实际意义看注释即可

事务定义器

先看一下事务定义器TransactionDefinition的源码,如下所示

package org.springframework.transaction;

import org.springframework.lang.Nullable;

/**
 * 事务定义接口,提供了事务配置的属性。
 */
public interface TransactionDefinition {
    // 事务传播行为常量
    int PROPAGATION_REQUIRED = 0; // 必须存在事务,如果当前没有则新建一个
    int PROPAGATION_SUPPORTS = 1; // 支持当前事务,如果当前没有则不需要
    int PROPAGATION_MANDATORY = 2; // 必须存在当前事务,否则抛出异常
    int PROPAGATION_REQUIRES_NEW = 3; // 总是新建一个事务,无论当前是否存在事务
    int PROPAGATION_NOT_SUPPORTED = 4; // 不使用事务,如果当前存在事务则挂起
    int PROPAGATION_NEVER = 5; // 不使用事务,如果当前存在事务则抛出异常
    int PROPAGATION_NESTED = 6; // 在当前事务内新建一个嵌套事务

    // 事务隔离级别常量
    int ISOLATION_DEFAULT = -1; // 使用底层数据库的默认隔离级别
    int ISOLATION_READ_UNCOMMITTED = 1; // 最低隔离级别,允许读取未提交的数据
    int ISOLATION_READ_COMMITTED = 2; // 读取已提交的数据,防止脏读
    int ISOLATION_REPEATABLE_READ = 4; // 防止脏读和不可重复读
    int ISOLATION_SERIALIZABLE = 8; // 最高隔离级别,防止脏读、不可重复读和 phantom read(幻读)

    // 事务超时常量,默认值
    int TIMEOUT_DEFAULT = -1; // 使用底层事务系统的默认超时设置

    /**
     * 获取事务传播行为。
     * 
     * @return 事务传播行为的枚举值,默认为 PROPAGATION_REQUIRED。
     */
    default int getPropagationBehavior() {
        return 0;
    }

    /**
     * 获取事务隔离级别。
     * 
     * @return 事务隔离级别的枚举值,默认为 ISOLATION_DEFAULT。
     */
    default int getIsolationLevel() {
        return -1;
    }

    /**
     * 获取事务超时时间(秒)。
     * 
     * @return 事务超时时间(秒),-1 表示使用默认超时设置。
     */
    default int getTimeout() {
        return -1;
    }

    /**
     * 判断事务是否为只读事务。
     * 
     * @return 如果事务设置为只读,则返回 true,否则返回 false,默认为 false。
     */
    default boolean isReadOnly() {
        return false;
    }

    /**
     * 获取事务名称。
     * 
     * @return 事务的名称,可能为 null。
     */
    @Nullable
    default String getName() {
        return null;
    }

    /**
     * 提供一个具有默认设置的事务定义实例。
     * 
     * @return 返回一个包含默认事务设置的 TransactionDefinition 实例。
     */
    static TransactionDefinition withDefaults() {
        return StaticTransactionDefinition.INSTANCE;
    }
}

除了异常的定义,其他关于事务的定义都可以在这里完成,对于事务的回滚内容,它会以RollbackRuleAttributeNoRollbackRuleAttribute两个类保存,这样在事务拦截器中就可以根据我们配置的内容处理事务了

声明式事务的约定流程

约定十分重要,在SpringIoC容器初始化时,Spring会读入注解@Transactional或者XML的事务配置,并且保存到一个事务定义类里面(TransactionDefinition接口的子类)备用,注解@Transactional可以使用在方法或者类上,当运行时候Spring会拦截注解标注的方法或者类的所有方法,和AOP一样,它就会把开发者的代码植入AOP的流程,然后给出它的约定

Spring通过事务管理器(PlatformTransactionManager的子类)创建事务,同时把事务定义中的隔离级别、超时时间等属性根据配置内容在事务上进行设置,根据传播行为,配置采取一种特定的策略,这事Spring根据配置完成的内容,开发者只需要配置无需编码,然后执行开发者提供的业务代码,Spring是通过发射方式调度开发者的业务方法,反射的结果可能是正常返回或者异常返回,Spring的约定是只要发生异常,并符合事务定义类回滚条件的就会将数据库事务回滚,否则提交事务,这也是Spring完成的

在整个开发过程中,只需要编写业务代码并对事务属性进行配置就可以了,并不需要使用代码干预,降低了工作量代码逻辑也更为清晰更有利于维护

互联网应用主流框架整合之数据库事务管理_Transactional_02

package com.transaction.service.impl;

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;

import com.transaction.dao.RoleDao;
import com.transaction.pojo.Role;
import com.transaction.service.RoleService;

/**
 * RoleService的实现类,提供角色相关的业务逻辑处理。
 */
@Service
public class RoleServiceImpl implements RoleService {

    // 自动注入RoleDao,用于执行数据库操作
    @Autowired
    private RoleDao roleDao = null;

    /**
     * 插入一个新的角色到数据库。
     * @param role 角色对象,包含角色的信息。
     * @return 插入成功返回插入的行数,否则返回0。
     */
    @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.DEFAULT, timeout=3)
    public int insertRole(Role role) {
        // 通过RoleDao将角色对象插入数据库
        return roleDao.insertRole(role);
    }

}

这段代码使用注解@Transactional插入数据,这里没有对数据库资源的控制,只有一个注解,它配置了propagation=Propagation.REQUIRED, isolation=Isolation.DEFAULT, timeout=3这意味着当别的事务方法调度它时,如果存在事务就沿用下来,如果不存在就开启新的事务,并且隔离级别采用默认级别,超时时间为3秒;其他的开发人员只需要知道当roleDao的insert方法抛出异常,注解@Transactional的工作Spring就会回滚事务,如果成功就提交事务

注解@Transactional的工作原理是SpringAOP技术,其底层的实现原理是动态代理,也即是只有代理对象调用才能启动Spring事务

数据库相关知识

数据库事务ACID特性

数据库事务正确执行的4个基础要素:原子性(Atomicity),一致性(Consistency),隔离性(Isolation),持久性(Durability)

  • 原子性(A):原子性保证了事务中的所有操作要么全部完成,要么全部不完成。如果事务中的任何一步操作失败,整个事务就会回滚到事务开始前的状态,就像该事务从未发生过一样。这种特性确保了数据库系统的完整性,避免了部分完成的操作导致的数据不一致。
  • 一致性©:一致性是指事务执行前后,数据库都必须处于一致性状态。这意味着事务的所有操作都遵循业务规则和约束,例如,如果一个事务试图从账户A转账到账户B,那么在事务完成后,两个账户的总额应该保持不变,并且所有账户余额都应是合法的。
  • 隔离性(I):隔离性确保了并发执行的事务之间不会互相影响。数据库系统提供了不同的隔离级别来控制事务之间的可见性,以防止并发操作产生的问题,如脏读、不可重复读和幻读。常见的隔离级别包括读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。
  • 脏读:一个事务读取到了另一个事务尚未提交的数据,如果那个事务最终回滚,那么脏读的数据就是错误的。
  • 不可重复读:在一个事务中,两次读取同一数据可能会得到不同的结果,因为其他事务在这两次读之间修改了数据。
  • 幻读:一个事务在多次读取相同查询结果集时,由于其他事务插入了新的行,使得结果集中出现了在先前读取时不存在的行。
  • 持久性(D):持久性意味着一旦事务提交,其对数据库的更改就会永久保存,即使系统崩溃或出现其他故障,这些更改也不会丢失。数据库系统通常会通过日志记录和检查点机制来实现这一特性,确保在系统恢复后,已提交的事务仍然有效

丢失更新

丢失更新(Lost Update)是数据库并发控制中可能出现的一种问题,它发生在多个事务尝试同时更新同一数据项时,导致一个事务的更新结果被另一个事务覆盖,从而“丢失”了前一个事务所做的更新。具体来说,丢失更新可以分为两种情况:

  • 不可见更新丢失(Unseen Update Loss): 当两个事务并发执行时,第一个事务读取并修改了一个数据项,但还未提交。在此期间,第二个事务也读取了这个数据项(此时看到的是修改前的旧值),然后进行了自己的修改并提交。随后,第一个事务也提交了它的修改。由于第二个事务的提交晚于第一个事务的读取操作,第一个事务的更新就被第二个事务无意中覆盖(丢失)了。
  • 不可重复读取丢失(Non-repeatable Read Loss): 这种情况与上述类似,但关注点在于一个事务内。假设一个事务内两次读取同一数据,第一次读取后,另一个并发事务修改并提交了该数据。当第一个事务第二次读取该数据时,发现数据已经改变,之前的读取结果无法重复获取,这也可以视为一种形式的丢失更新,尽管这里的重点是读取的不一致性而非数据更新的实际丢失。

在互联网中存在抢购、秒杀等高并发场景,数据库在一个多事务并发的环境中运行,多个事务的并发会产生一系列问题,丢失更新就是其中之一,丢失更新问题主要是由于缺乏适当的并发控制机制导致的,为了避免事务并发导致出现一致性问题,数据库标准规范中定义了事务之间的隔离级别,在不同程度上减少了出现丢失更新的可能性

此外,锁机制。也是为了解决这个问题,数据库系统通常采用以下策略之一或结合使用:

  • 乐观锁:在更新数据前检查数据是否被其他事务修改过,通常是通过版本号或时间戳来实现。
  • 悲观锁:在事务开始时就锁定将要访问的数据,直到事务结束才释放锁,这样可以阻止其他事务并发修改。
  • 更高的事务隔离级别:如可重复读(Repeatable Read)或串行化(Serializable),虽然这可能会影响并发性能。
  • 多版本并发控制(MVCC, Multiversion Concurrency Control):为事务提供数据的多个版本,每个事务看到的是它开始时的数据快照,从而避免了直接的冲突

隔离级别

SQL 标准定义了四种事务隔离级别,它们是根据事务之间数据可见性规则的不同而划分的。每种隔离级别旨在解决特定的并发问题,以平衡数据一致性与系统性能。以下是这四种隔离级别的详细解释:

  • 读未提交(Read Uncommitted):在这个级别,一个事务可以读取其他事务尚未提交的数据,这可能导致脏读、不可重复读和幻读。这是最低的隔离级别,因此并发性能最高,但数据一致性最弱。
  • 读已提交(Read Committed):在这种隔离级别下,一个事务只能看到其他事务已经提交的修改。这避免了脏读,但仍然可能存在不可重复读的问题。即在同一个事务中,两次执行相同的查询可能会返回不同的结果,因为其他事务可能在这两次查询之间插入、更新或删除了数据。
  • 可重复读(Repeatable Read):在可重复读的隔离级别中,一个事务在整个事务期间可以看到事务开始时的数据视图,即使其他事务在此期间对数据进行了修改。这消除了不可重复读的问题,但可能仍然存在幻读。即在同一个事务中,多次执行同样的范围查询可能会返回不同的行,因为其他事务可能插入了满足查询条件的新行。
  • 串行化(Serializable):这是最高的隔离级别,它通过完全序列化事务的执行来消除脏读、不可重复读和幻读。在串行化级别,事务如同依次执行,每个事务都在其他事务完成之后开始。虽然这提供了最高的数据一致性,但并发性能最低,因为事务必须等待其他事务完成才能继续。

不同的数据库系统可能会有不同的实现方式,但基本理念是相同的。选择哪种隔离级别取决于应用的需求,需要在数据一致性与系统性能之间做出权衡。在实际应用中,通常会选择足够高以保证数据完整性的隔离级别,同时尽可能降低对性能的影响。

选择隔离级别和传播行为

如何选择隔离级别

在互联网应用中,不但要考虑数据库数据的一致性,而且要考虑系统的性能,一般情况下可以认为从读未提交(Read Uncommitted)到串行化(Serializable),数据库的性能是直线下降的,设置级别越高,比如串行化(Serializable),会严重压制并发的能力,从而引发大量的线程挂起,直到获得锁才能进一步操作,但这样回复时又需要大量的等待时间,因此在购物类的应用中,通过隔离级别控制数据一致性的方式就被排除掉了,但这样读未提交(Read Uncommitted)风险又过大,因此在大部分场景下,会选择读已提交(Read Committed)设置事务,但这并未解决数据一致性问题

/**
 * 枚举类型定义了事务的隔离级别。
 * 这些隔离级别决定了在同一个事务中多次读取数据时的行为,以及多个事务之间如何交互来访问数据。
 * 每个枚举值对应一个特定的隔离级别,用整数值表示。
 */
public enum Isolation {
    // 默认隔离级别,由数据库自己选择合适的隔离级别
    DEFAULT(-1),
    // 读未提交,允许一个事务读取另一个未提交事务的数据,是最弱的隔离级别
    READ_UNCOMMITTED(1),
    // 读已提交,保证一个事务只能读取另一个已提交事务的数据
    READ_COMMITTED(2),
    // 可重复读,保证一个事务在相同条件下多次读取的数据是一致的,防止脏读和不可重复读
    REPEATABLE_READ(4),
    // 串行化,最严格的隔离级别,确保一个事务在另一个事务完成之前不能读取或修改数据
    SERIALIZABLE(8);

    // 存储每个隔离级别的整数值
    private final int value;

    /**
     * 构造函数为每个隔离级别设置对应的整数值。
     *
     * @param value 隔离级别的整数表示
     */
    private Isolation(int value) {
        this.value = value;
    }

    /**
     * 返回当前隔离级别的整数表示。
     *
     * @return 隔离级别的整数值
     */
    public int value() {
        return this.value;
    }
}
/**
 * RoleService的实现类,提供角色相关的业务逻辑处理。
 */
@Service
public class RoleServiceImpl implements RoleService {

    // 自动注入RoleDao,用于执行数据库操作
    @Autowired
    private RoleDao roleDao = null;

    /**
     * 插入一个新的角色到数据库。
     * @param role 角色对象,包含角色的信息。
     * @return 插入成功返回插入的行数,否则返回0。
     * @@Transactional 注解指定了该方法需要事务支持,具体配置为:
     * - propagation=Propagation.REQUIRED 表示如果外部有事务,则加入外部事务,如果没有则创建新的事务
     * - isolation=Isolation.DEFAULT 使用读已提交,保证一个事务只能读取另一个已提交事务的数据.
     * - timeout=3 设定了事务的超时时间为3秒
     */
    @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED, timeout=3)
    public int insertRole(Role role) {
        // 通过RoleDao将角色对象插入数据库
        return roleDao.insertRole(role);
    }

}

它的选择完全取决于实际的场景,如果并发很小甚至可以忽略,也完全可以选择串行化来保证数据的一致性,总之隔离级别需要根据并发大小和性能进行选择

在源码中我们看到了隔离级别有个默认值,并且注解@Transactional的隔离级别默认值也是Isolation.DEFAULT,其含义是采用数据库默认的隔离级别,但这个默认级别是根据数据库的选择变化而变化的,不同的数据库隔离级别的支持和默认值不同,比如MySQL可以自持4种隔离级别,而默认值是可重复读(Repeatable Read),Oracle只支持两种隔离级别读已提交(Read Committed)和串行化(Serializable),默认值为读已提交(Read Committed)

如果选择传播行为

在数据库编程中,特别是使用像Java Spring框架这样的高级框架时,事务的传播性(Propagation)是指当一个事务方法被另一个事务方法调用时,如何处理这两个事务的关系。Spring框架定义了七种不同的事务传播行为,每种行为都规定了在调用具有事务的方法时,应该如何开始、继续或结束事务。以下是这七种传播行为的详细解释:

  • PROPAGATION_REQUIRED:这是默认的行为。如果当前存在事务,那么方法将在这个事务内运行;如果当前不存在事务,则会创建一个新的事务。
  • PROPAGATION_SUPPORTS:如果当前存在事务,那么方法将在该事务内运行,但没有事务也可以。如果不存在事务,方法不会创建新的事务。
  • PROPAGATION_MANDATORY:类似PROPAGATION_REQUIRED,但要求必须存在一个事务。如果当前没有事务,调用将抛出异常。
  • PROPAGATION_REQUIRES_NEW:不管当前是否存在事务,都会创建一个新的事务,并且在新事务中执行方法。如果存在当前事务,当前事务会被挂起。
  • PROPAGATION_NOT_SUPPORTED:方法不应该在事务中运行。如果存在事务,事务会在方法执行前被暂停,在方法执行后恢复。
  • PROPAGATION_NEVER:方法不应该在事务中运行。如果存在事务,调用将抛出异常。
  • PROPAGATION_NESTED:如果当前存在事务,则在一个嵌套事务内执行。如果当前没有事务,则行为类似于PROPAGATION_REQUIRED。嵌套事务通常使用保存点来实现,允许在事务内部进行部分回滚。

这些传播行为允许开发者根据业务需求灵活地控制事务的边界和行为,确保数据的一致性和完整性。在处理复杂的事务逻辑时,正确选择事务的传播行为至关重要,通过这些传播行为,开发者可以精确地控制事务的边界,以满足复杂业务场景的需求。例如,PROPAGATION_REQUIRES_NEW常用于需要独立原子操作的场景,即使调用者已经在一个事务中,也能保证被调用方法的原子性。而PROPAGATION_SUPPORTS则用于那些可以但不需要事务支持的操作

/**
 * 传播行为枚举,定义了事务的传播特性。
 */
public enum Propagation {
    /**
     * 必须参与事务。如果当前没有事务,则尝试创建一个新的事务;
     * 如果当前存在事务,则加入当前事务。
     */
    REQUIRED(0),
    /**
     * 支持事务,但不一定参与。如果当前存在事务,则加入当前事务;
     * 如果当前没有事务,则以非事务方式执行。
     */
    SUPPORTS(1),
    /**
     * 必须参与事务。如果当前没有事务,则抛出异常。
     */
    MANDATORY(2),
    /**
     * 总是开启一个新的事务。如果当前存在事务,则挂起当前事务,
     * 开启一个新的事务执行。
     */
    REQUIRES_NEW(3),
    /**
     * 不支持事务,如果当前存在事务,则抛出异常。
     */
    NOT_SUPPORTED(4),
    /**
     * 永远不开启事务,如果当前存在事务,则挂起当前事务。
     */
    NEVER(5),
    /**
     * 在当前事务内开启一个新的嵌套事务。如果当前没有事务,则抛出异常。
     */
    NESTED(6);

    // 枚举值对应的整数值
    private final int value;

    /**
     * 构造函数,为每个传播行为设置对应的整数值。
     * 
     * @param value 传播行为的整数值。
     */
    private Propagation(int value) {
        this.value = value;
    }

    /**
     * 获取传播行为对应的整数值。
     * 
     * @return 传播行为的整数值。
     */
    public int value() {
        return this.value;
    }
}

最常用的是REQUIRED,它也是默认的传播行为,即如果不存在事务就启用事务,如果存在就沿用下来

MyBatis-Spring中使用事务

互联网应用主流框架整合之数据库事务管理_MyBatis-Spring_03


互联网应用主流框架整合之数据库事务管理_Transactional_04

package com.transaction.config;

import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;

/**
 * 事务配置类,负责初始化数据源、JdbcTemplate、事务管理器和SqlSessionFactory等。
 */
@Configuration
@ComponentScan("com.transaction")
// 启用事务管理
@EnableTransactionManagement
@MapperScan(basePackages = "com.transaction", annotationClass = Mapper.class)
public class JavaConfig implements TransactionManagementConfigurer {
    private DataSource dataSource = null;

    /**
     * 初始化数据源。
     *
     * @return DataSource 返回配置好的数据源实例。
     */
    @Bean(name = "dataSource")
    public DataSource initDataSource() {
        if (dataSource != null) {
            return dataSource;
        }
        Properties props = new Properties();
        // 配置数据库连接属性
        props.setProperty("driverClassName", "com.mysql.cj.jdbc.Driver");
        props.setProperty("url", "jdbc:mysql://192.168.3.115:3306/ssm?useUnicode=true;characterEncoding=utf8");
        props.setProperty("username", "root");
        props.setProperty("password", "Ms123!@#");
        // 配置连接池属性
        props.setProperty("maxActive", "200");
        props.setProperty("maxIdle", "20");
        props.setProperty("maxWait", "30000");
        try {
            dataSource = BasicDataSourceFactory.createDataSource(props);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dataSource;
    }

    /**
     * 初始化JdbcTemplate。
     *
     * @param dataSource 数据源
     * @return JdbcTemplate 返回配置好的JdbcTemplate实例。
     */
    @Bean(name = "jdbcTemplate")
    public JdbcTemplate initjdbcTemplate(@Autowired DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    /**
     * 创建基于注解的事务管理器。
     *
     * @return PlatformTransactionManager 返回配置好的事务管理器实例。
     */
    @Override
    @Bean(name = "transactionManager")
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        // 配置事务管理器的数据源
        transactionManager.setDataSource(initDataSource() );
        return transactionManager;
    }

    /**
     * 创建SqlSessionFactory。
     *
     * @param dataSource 数据源
     * @return SqlSessionFactory 返回配置好的SqlSessionFactory实例。
     * @throws Exception 如果配置失败抛出异常。
     */
    @Bean("sqlSessionFactory")
    public SqlSessionFactory createSqlSessionFactoryBean(@Autowired DataSource dataSource) throws Exception {
        // 设置配置文件
        String cfgFile = "mybatis-config.xml";
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        // 设置配置文件位置
        Resource configLocation = new ClassPathResource(cfgFile);
        sqlSessionFactoryBean.setConfigLocation(configLocation);
        return sqlSessionFactoryBean.getObject();
    }

}
/**
 * Role类用于表示角色信息
 */
package com.transaction.pojo;

import org.apache.ibatis.type.Alias;

@Alias("role")
public class Role {

    private Long id; // 角色的唯一标识
    private String roleName; // 角色名称
    private String note; // 角色的备注信息

    /**
     * 获取角色的唯一标识
     *
     * @return 角色的唯一标识
     */
    public Long getId() {
        return id;
    }

    /**
     * 设置角色的唯一标识
     *
     * @param id 角色的唯一标识
     */
    public void setId(Long id) {
        this.id = id;
    }

    /**
     * 获取角色名称
     *
     * @return 角色名称
     */
    public String getRoleName() {
        return roleName;
    }

    /**
     * 设置角色名称
     *
     * @param roleName 角色名称
     */
    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    /**
     * 获取角色的备注信息
     *
     * @return 角色的备注信息
     */
    public String getNote() {
        return note;
    }

    /**
     * 设置角色的备注信息
     *
     * @param note 角色的备注信息
     */
    public void setNote(String note) {
        this.note = note;
    }
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 定义Mapper的命名空间 -->
<mapper namespace="com.transaction.dao.RoleDao">

    <!-- 插入角色信息,使用自动生成的键值设置到角色实体的id属性上 -->
    <insert id="insertRole" useGeneratedKeys="true" keyProperty="id">
        insert into t_role(role_name, note) values (#{roleName}, #{note})
    </insert>

    <!-- 根据id删除角色信息 -->
    <delete id="deleteRole" parameterType="long">
        delete from t_role where id=#{id}
    </delete>

    <!-- 根据id查询角色信息,返回role实体 -->
    <select id="getRole" parameterType="long" resultType="role">
        select id, role_name as roleName, note from t_role where id = #{id}
    </select>

    <!-- 更新角色信息,参数为role实体 -->
    <update id="updateRole" parameterType="role">
        update t_role set role_name = #{roleName},note = #{note} where id = #{id}
    </update>
</mapper>
package com.transaction.dao;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import com.transaction.pojo.Role;

/**
 * RoleDao接口定义了角色数据访问层的方法
 * @Mapper注解告知MyBatis该接口是一个映射器接口
 */
@Mapper
public interface RoleDao  {

    /**
     * 插入一个新的角色到数据库
     * @param role 角色对象,包含角色的信息
     * @return 插入成功返回影响的行数,通常为1
     */
    public int insertRole(Role role);

    /**
     * 根据角色ID获取角色信息
     * @param id 角色的唯一标识符
     * @return 返回匹配的角色对象,如果找不到返回null
     */
    public Role getRole(@Param("id") Long id);

    /**
     * 更新数据库中的角色信息
     * @param role 包含更新后角色信息的Role对象
     * @return 返回影响的行数,如果角色不存在,可能返回0
     */
    public int updateRole(Role role);

    /**
     * 根据角色ID删除角色信息
     * @param id 角色的唯一标识符
     * @return 返回影响的行数,通常为1;如果角色不存在,可能返回0
     */
    public int deleteRole(@Param("id") Long id);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!-- 这个配置使全局的映射器启用或禁用缓存 -->
        <setting name="cacheEnabled" value="true" />
        <!-- 允许 JDBC 支持生成的键。需要适当的驱动。如果设置为true,则这个设置强制生成的键被使用,尽管一些驱动拒绝兼容但仍然有效(比如 Derby) -->
        <setting name="useGeneratedKeys" value="true" />
        <!-- 配置默认的执行器。
        SIMPLE 执行器没有什么特别之处。
        REUSE 执行器重用预处理语句。
        BATCH 执行器重用语句和批量更新  -->
        <setting name="defaultExecutorType" value="REUSE" />
        <!-- 全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 设置超时时间,它决定驱动等待一个数据库响应的时间  -->
        <setting name="defaultStatementTimeout" value="25000"/>
    </settings>
    <!-- 别名配置 -->
    <typeAliases>
        <typeAlias alias="role" type="com.transaction.pojo.Role" />
    </typeAliases>

    <!-- 指定映射器路径 -->
    <mappers>
        <mapper resource="RoleMapper.xml" />
    </mappers>
</configuration>
/**
 * RoleListService接口定义了角色列表的业务操作。
 */
package com.transaction.service;

import java.util.List;

import com.transaction.pojo.Role;

/**
 * RoleListService提供角色列表的插入操作。
 */
public interface RoleListService {

    /**
     * 插入角色列表到数据库。
     *
     * @param roleList 要插入的角色列表,类型为List<Role>。
     * @return 插入成功返回插入的记录数,失败返回0。
     */
    public int insertRoleList(List<Role> roleList);
}
package com.transaction.service.impl;

import java.util.List;

import org.apache.log4j.Logger;
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;

import com.transaction.pojo.Role;
import com.transaction.service.RoleListService;
import com.transaction.service.RoleService;

/**
 * 角色列表服务实现类
 * 提供角色列表的事务性操作
 */
@Service
public class RoleListServiceImpl implements RoleListService {

    // 日志记录器
    private Logger log = Logger.getLogger(RoleListServiceImpl.class);

    // 自动注入角色服务
    @Autowired
    private RoleService roleService = null;

    /**
     * 插入角色列表
     * @param roleList 要插入的角色列表
     * @return 插入的角色数量
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
    public int insertRoleList(List<Role> roleList) {
        int count = 0; // 记录成功插入的角色数量
        for (Role role : roleList) {
            try {
                // 尝试插入每个角色,累计成功插入的数量
                count += roleService.insertRole(role);
            } catch (Exception ex) {
                // 捕获并记录异常信息,不中断循环
                log.info(ex);
            }
        }
        return count; // 返回成功插入的角色数量
    }

}
package com.transaction.service;

import com.transaction.pojo.Role;

/**
 * RoleService接口定义了角色服务的相关操作。
 */
public interface RoleService {

    /**
     * 插入一个新角色到数据库。
     *
     * @param role 待插入的角色对象,包含角色的详细信息。
     * @return 返回插入操作的数据库影响行数,通常为1表示插入成功。
     */
    public int insertRole(Role role);
}
package com.transaction.service.impl;

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;

import com.transaction.dao.RoleDao;
import com.transaction.pojo.Role;
import com.transaction.service.RoleService;

/**
 * RoleService的实现类,提供角色相关的业务逻辑处理。
 */
@Service
public class RoleServiceImpl implements RoleService {

    // 自动注入RoleDao,用于执行数据库操作
    @Autowired
    private RoleDao roleDao = null;

    /**
     * 插入一个新的角色到数据库。
     * @param role 角色对象,包含角色的信息。
     * @return 插入成功返回插入的行数,否则返回0。
     * @@Transactional 注解指定了该方法需要事务支持,具体配置为:
     * - propagation=Propagation.REQUIRED 表示如果外部有事务,则加入外部事务,如果没有则创建新的事务。
     * - isolation=Isolation.DEFAULT 使用数据库默认的隔离级别。
     * - timeout=3 设定了事务的超时时间为3秒。
     */
    @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.DEFAULT, timeout=3)
    public int insertRole(Role role) {
        // 通过RoleDao将角色对象插入数据库
        return roleDao.insertRole(role);
    }

}
/**
     * 测试数据库操作的隔离性。
     * 该方法创建一个Spring应用上下文,从JavaConfig类加载配置,然后获取RoleListService的实例。
     * 之后,创建一个包含10个角色对象的列表,并尝试将这个列表插入到数据库中,最后打印出影响的行数。
     * 该方法不接受参数且没有返回值。
     */
    public static void testIsolation() {
        // 创建Spring应用上下文并从JavaConfig类加载配置
        ApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);

        // 从上下文中获取RoleListService的实例
        RoleListService roleListService = ctx.getBean(RoleListService.class);
        System.out.println(roleListService.getClass().getName());

        List<Role> roleList = new ArrayList<>();

        // 循环创建10个角色对象并添加到列表中
        for (int i = 1; i <= 10; i++) {
            Role role = new Role();
            role.setRoleName("role_name_" + i);
            role.setNote("note-" + i);
            roleList.add(role);
        }

        // 调用service方法,尝试插入角色列表到数据库
        int affectRows = roleListService.insertRoleList(roleList);
        System.out.println(affectRows);
    }

执行结果如下:

"C:\Program Files\Java\jdk-17.0.1\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2023.2.3\lib\idea_rt.jar=61483:C:\Program Files\JetBrains\IntelliJ IDEA 2023.2.3\bin" -Dfile.encoding=UTF-8 -classpath D:\Programs\Java\SpringTransaction\target\classes;C:\Users\Administrator\.m2\repository\org\springframework\spring-core\6.1.8\spring-core-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-jcl\6.1.8\spring-jcl-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-orm\6.1.8\spring-orm-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-tx\6.1.8\spring-tx-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-beans\6.1.8\spring-beans-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-context\6.1.8\spring-context-6.1.8.jar;C:\Users\Administrator\.m2\repository\io\micrometer\micrometer-observation\1.12.6\micrometer-observation-1.12.6.jar;C:\Users\Administrator\.m2\repository\io\micrometer\micrometer-commons\1.12.6\micrometer-commons-1.12.6.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-context-support\6.1.8\spring-context-support-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-expression\6.1.8\spring-expression-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-aop\6.1.8\spring-aop-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-jdbc\6.1.8\spring-jdbc-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\apache\commons\commons-dbcp2\2.12.0\commons-dbcp2-2.12.0.jar;C:\Users\Administrator\.m2\repository\org\apache\commons\commons-pool2\2.12.0\commons-pool2-2.12.0.jar;C:\Users\Administrator\.m2\repository\commons-logging\commons-logging\1.3.0\commons-logging-1.3.0.jar;C:\Users\Administrator\.m2\repository\jakarta\transaction\jakarta.transaction-api\1.3.3\jakarta.transaction-api-1.3.3.jar;C:\Users\Administrator\.m2\repository\org\mybatis\mybatis\3.5.16\mybatis-3.5.16.jar;C:\Users\Administrator\.m2\repository\org\mybatis\mybatis-spring\3.0.3\mybatis-spring-3.0.3.jar;C:\Users\Administrator\.m2\repository\org\aspectj\aspectjweaver\1.9.22.1\aspectjweaver-1.9.22.1.jar;C:\Users\Administrator\.m2\repository\org\aspectj\aspectjrt\1.9.22.1\aspectjrt-1.9.22.1.jar;C:\Users\Administrator\.m2\repository\com\mysql\mysql-connector-j\8.3.0\mysql-connector-j-8.3.0.jar;C:\Users\Administrator\.m2\repository\com\google\protobuf\protobuf-java\3.25.1\protobuf-java-3.25.1.jar;C:\Users\Administrator\.m2\repository\org\slf4j\slf4j-reload4j\2.0.13\slf4j-reload4j-2.0.13.jar;C:\Users\Administrator\.m2\repository\org\slf4j\slf4j-api\2.0.13\slf4j-api-2.0.13.jar;C:\Users\Administrator\.m2\repository\ch\qos\reload4j\reload4j\1.2.22\reload4j-1.2.22.jar;C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-core\2.23.1\log4j-core-2.23.1.jar;C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-api\2.23.1\log4j-api-2.23.1.jar com.transaction.main.Main
DEBUG 2024-05-28 18:14:33,673 org.apache.ibatis.logging.LogFactory: Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
DEBUG 2024-05-28 18:14:33,686 org.mybatis.logging.Logger: Creating MapperFactoryBean with name 'roleDao' and 'com.transaction.dao.RoleDao' mapperInterface
DEBUG 2024-05-28 18:14:33,690 org.mybatis.logging.Logger: Enabling autowire by type for MapperFactoryBean with name 'roleDao'.
DEBUG 2024-05-28 18:14:34,353 org.mybatis.logging.Logger: Parsed configuration file: 'class path resource [mybatis-config.xml]'
DEBUG 2024-05-28 18:14:34,355 org.mybatis.logging.Logger: Property 'mapperLocations' was not specified.
jdk.proxy2.$Proxy31
DEBUG 2024-05-28 18:14:35,340 org.mybatis.logging.Logger: Creating a new SqlSession
DEBUG 2024-05-28 18:14:35,345 org.mybatis.logging.Logger: Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474]
DEBUG 2024-05-28 18:14:35,353 org.mybatis.logging.Logger: JDBC Connection [1187225933, URL=jdbc:mysql://192.168.3.115:3306/ssm?useUnicode=true;characterEncoding=utf8, MySQL Connector/J] will be managed by Spring
DEBUG 2024-05-28 18:14:35,363 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: insert into t_role(role_name, note) values (?, ?)
DEBUG 2024-05-28 18:14:35,410 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_1(String), note-1(String)
DEBUG 2024-05-28 18:14:35,420 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 18:14:35,448 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474]
DEBUG 2024-05-28 18:14:35,449 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474] from current transaction
DEBUG 2024-05-28 18:14:35,451 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_2(String), note-2(String)
DEBUG 2024-05-28 18:14:35,470 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 18:14:35,486 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474]
DEBUG 2024-05-28 18:14:35,487 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474] from current transaction
DEBUG 2024-05-28 18:14:35,488 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_3(String), note-3(String)
DEBUG 2024-05-28 18:14:35,498 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 18:14:35,499 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474]
DEBUG 2024-05-28 18:14:35,499 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474] from current transaction
DEBUG 2024-05-28 18:14:35,500 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_4(String), note-4(String)
DEBUG 2024-05-28 18:14:35,508 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 18:14:35,509 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474]
DEBUG 2024-05-28 18:14:35,509 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474] from current transaction
DEBUG 2024-05-28 18:14:35,511 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: null, note-5(String)
DEBUG 2024-05-28 18:14:35,526 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 18:14:35,527 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474]
DEBUG 2024-05-28 18:14:35,527 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474] from current transaction
DEBUG 2024-05-28 18:14:35,528 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_6(String), note-6(String)
DEBUG 2024-05-28 18:14:35,547 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 18:14:35,547 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474]
DEBUG 2024-05-28 18:14:35,552 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474] from current transaction
DEBUG 2024-05-28 18:14:35,553 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_7(String), note-7(String)
DEBUG 2024-05-28 18:14:35,610 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 18:14:35,611 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474]
DEBUG 2024-05-28 18:14:35,612 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474] from current transaction
DEBUG 2024-05-28 18:14:35,612 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_8(String), note-8(String)
DEBUG 2024-05-28 18:14:35,760 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 18:14:35,761 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474]
DEBUG 2024-05-28 18:14:35,762 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474] from current transaction
DEBUG 2024-05-28 18:14:35,763 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_9(String), note-9(String)
DEBUG 2024-05-28 18:14:35,802 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 18:14:35,802 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474]
DEBUG 2024-05-28 18:14:35,803 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474] from current transaction
DEBUG 2024-05-28 18:14:35,803 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_10(String), note-10(String)
DEBUG 2024-05-28 18:14:35,816 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 18:14:35,817 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474]
DEBUG 2024-05-28 18:14:35,818 org.mybatis.logging.Logger: Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474]
DEBUG 2024-05-28 18:14:35,821 org.mybatis.logging.Logger: Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474]
DEBUG 2024-05-28 18:14:35,822 org.mybatis.logging.Logger: Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474]
10
Process finished with exit code 0

执行日志中,有如下四行,表明Spring已经加入了事务控制,insertRoleList会调用inserRole,而insertRole的传播行为为REQUIRED,

DEBUG 2024-05-28 18:14:35,345 org.mybatis.logging.Logger: Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474]
DEBUG 2024-05-28 18:14:35,818 org.mybatis.logging.Logger: Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474]
DEBUG 2024-05-28 18:14:35,821 org.mybatis.logging.Logger: Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474]
DEBUG 2024-05-28 18:14:35,822 org.mybatis.logging.Logger: Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c518474]

互联网应用主流框架整合之数据库事务管理_数据库事务_05

/**
     * 测试数据隔离的方法。
     * 该方法通过创建一个Spring应用上下文来获取RoleListService的实例,然后使用该实例插入角色列表数据到数据库。
     * 其中,第五个角色的角色名会被设置为null,以模拟和测试数据库操作中的异常处理。
     *
     * @return void 该方法没有返回值。
     */
    public static void testIsolationException() {
        // 创建Spring应用上下文并从JavaConfig类加载配置
        ApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
        // 从上下文中获取RoleListService的实例
        RoleListService roleListService = ctx.getBean(RoleListService.class);
        System.out.println(roleListService.getClass().getName());

        List<Role> roleList = new ArrayList<>();
        // 循环创建10个角色对象,其中第5个角色的角色名为null
        for (int i = 1; i <= 10; i++) {
            Role role = new Role();
            if (i == 5) {
                // 故意设置一个null的角色名,以测试数据库操作的异常处理
                role.setRoleName(null);
            } else {
                role.setRoleName("role_name_" + i);
            }
            role.setNote("note-" + i);
            roleList.add(role);
        }
        // 调用service方法,尝试插入角色列表到数据库
        int affectRows = roleListService.insertRoleList(roleList);
        System.out.println(affectRows);
    }

执行结果如下:

"C:\Program Files\Java\jdk-17.0.1\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2023.2.3\lib\idea_rt.jar=63230:C:\Program Files\JetBrains\IntelliJ IDEA 2023.2.3\bin" -Dfile.encoding=UTF-8 -classpath D:\Programs\Java\SpringTransaction\target\classes;C:\Users\Administrator\.m2\repository\org\springframework\spring-core\6.1.8\spring-core-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-jcl\6.1.8\spring-jcl-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-orm\6.1.8\spring-orm-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-tx\6.1.8\spring-tx-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-beans\6.1.8\spring-beans-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-context\6.1.8\spring-context-6.1.8.jar;C:\Users\Administrator\.m2\repository\io\micrometer\micrometer-observation\1.12.6\micrometer-observation-1.12.6.jar;C:\Users\Administrator\.m2\repository\io\micrometer\micrometer-commons\1.12.6\micrometer-commons-1.12.6.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-context-support\6.1.8\spring-context-support-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-expression\6.1.8\spring-expression-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-aop\6.1.8\spring-aop-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-jdbc\6.1.8\spring-jdbc-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\apache\commons\commons-dbcp2\2.12.0\commons-dbcp2-2.12.0.jar;C:\Users\Administrator\.m2\repository\org\apache\commons\commons-pool2\2.12.0\commons-pool2-2.12.0.jar;C:\Users\Administrator\.m2\repository\commons-logging\commons-logging\1.3.0\commons-logging-1.3.0.jar;C:\Users\Administrator\.m2\repository\jakarta\transaction\jakarta.transaction-api\1.3.3\jakarta.transaction-api-1.3.3.jar;C:\Users\Administrator\.m2\repository\org\mybatis\mybatis\3.5.16\mybatis-3.5.16.jar;C:\Users\Administrator\.m2\repository\org\mybatis\mybatis-spring\3.0.3\mybatis-spring-3.0.3.jar;C:\Users\Administrator\.m2\repository\org\aspectj\aspectjweaver\1.9.22.1\aspectjweaver-1.9.22.1.jar;C:\Users\Administrator\.m2\repository\org\aspectj\aspectjrt\1.9.22.1\aspectjrt-1.9.22.1.jar;C:\Users\Administrator\.m2\repository\com\mysql\mysql-connector-j\8.3.0\mysql-connector-j-8.3.0.jar;C:\Users\Administrator\.m2\repository\com\google\protobuf\protobuf-java\3.25.1\protobuf-java-3.25.1.jar;C:\Users\Administrator\.m2\repository\org\slf4j\slf4j-reload4j\2.0.13\slf4j-reload4j-2.0.13.jar;C:\Users\Administrator\.m2\repository\org\slf4j\slf4j-api\2.0.13\slf4j-api-2.0.13.jar;C:\Users\Administrator\.m2\repository\ch\qos\reload4j\reload4j\1.2.22\reload4j-1.2.22.jar;C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-core\2.23.1\log4j-core-2.23.1.jar;C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-api\2.23.1\log4j-api-2.23.1.jar com.transaction.main.Main
DEBUG 2024-05-28 18:34:55,375 org.apache.ibatis.logging.LogFactory: Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
DEBUG 2024-05-28 18:34:55,393 org.mybatis.logging.Logger: Creating MapperFactoryBean with name 'roleDao' and 'com.transaction.dao.RoleDao' mapperInterface
DEBUG 2024-05-28 18:34:55,400 org.mybatis.logging.Logger: Enabling autowire by type for MapperFactoryBean with name 'roleDao'.
DEBUG 2024-05-28 18:34:56,361 org.mybatis.logging.Logger: Parsed configuration file: 'class path resource [mybatis-config.xml]'
DEBUG 2024-05-28 18:34:56,364 org.mybatis.logging.Logger: Property 'mapperLocations' was not specified.
jdk.proxy2.$Proxy31
DEBUG 2024-05-28 18:34:57,612 org.mybatis.logging.Logger: Creating a new SqlSession
DEBUG 2024-05-28 18:34:57,620 org.mybatis.logging.Logger: Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@764cba]
DEBUG 2024-05-28 18:34:57,630 org.mybatis.logging.Logger: JDBC Connection [1636487850, URL=jdbc:mysql://192.168.3.115:3306/ssm?useUnicode=true;characterEncoding=utf8, MySQL Connector/J] will be managed by Spring
DEBUG 2024-05-28 18:34:57,644 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: insert into t_role(role_name, note) values (?, ?)
DEBUG 2024-05-28 18:34:57,704 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_1(String), note-1(String)
DEBUG 2024-05-28 18:34:57,720 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 18:34:57,774 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@764cba]
DEBUG 2024-05-28 18:34:57,775 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@764cba] from current transaction
DEBUG 2024-05-28 18:34:57,776 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_2(String), note-2(String)
DEBUG 2024-05-28 18:34:57,789 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 18:34:57,790 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@764cba]
DEBUG 2024-05-28 18:34:57,790 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@764cba] from current transaction
DEBUG 2024-05-28 18:34:57,791 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_3(String), note-3(String)
DEBUG 2024-05-28 18:34:57,801 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 18:34:57,801 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@764cba]
DEBUG 2024-05-28 18:34:57,802 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@764cba] from current transaction
DEBUG 2024-05-28 18:34:57,803 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_4(String), note-4(String)
DEBUG 2024-05-28 18:34:57,812 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 18:34:57,813 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@764cba]
DEBUG 2024-05-28 18:34:57,813 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@764cba] from current transaction
DEBUG 2024-05-28 18:34:57,815 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: null, note-5(String)
DEBUG 2024-05-28 18:34:57,847 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@764cba]
 INFO 2024-05-28 18:34:58,137 com.transaction.service.impl.RoleListServiceImpl: org.springframework.dao.DataIntegrityViolationException: 
### Error updating database.  Cause: java.sql.SQLIntegrityConstraintViolationException: Column 'role_name' cannot be null
### The error may exist in RoleMapper.xml
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### Cause: java.sql.SQLIntegrityConstraintViolationException: Column 'role_name' cannot be null
; Column 'role_name' cannot be null
DEBUG 2024-05-28 18:34:58,137 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@764cba] from current transaction
DEBUG 2024-05-28 18:34:58,138 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_6(String), note-6(String)
DEBUG 2024-05-28 18:34:58,150 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 18:34:58,151 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@764cba]
DEBUG 2024-05-28 18:34:58,151 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@764cba] from current transaction
DEBUG 2024-05-28 18:34:58,151 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_7(String), note-7(String)
DEBUG 2024-05-28 18:34:58,165 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 18:34:58,166 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@764cba]
DEBUG 2024-05-28 18:34:58,166 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@764cba] from current transaction
DEBUG 2024-05-28 18:34:58,167 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_8(String), note-8(String)
DEBUG 2024-05-28 18:34:58,176 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 18:34:58,177 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@764cba]
DEBUG 2024-05-28 18:34:58,177 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@764cba] from current transaction
DEBUG 2024-05-28 18:34:58,179 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_9(String), note-9(String)
DEBUG 2024-05-28 18:34:58,188 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 18:34:58,188 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@764cba]
DEBUG 2024-05-28 18:34:58,189 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@764cba] from current transaction
DEBUG 2024-05-28 18:34:58,189 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_10(String), note-10(String)
DEBUG 2024-05-28 18:34:58,198 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 18:34:58,198 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@764cba]
DEBUG 2024-05-28 18:34:58,199 org.mybatis.logging.Logger: Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@764cba]
DEBUG 2024-05-28 18:34:58,200 org.mybatis.logging.Logger: Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@764cba]
Exception in thread "main" org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:938)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:754)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:676)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:426)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223)
	at jdk.proxy2/jdk.proxy2.$Proxy31.insertRoleList(Unknown Source)
	at com.transaction.main.Main.testIsolationException(Main.java:88)
	at com.transaction.main.Main.main(Main.java:27)

Process finished with exit code 1

这里边有个细节insertRoleList调用insertRole方法,而insertRole方法是包在try...catch...语句块里的,也就是说异常被捕获了,insertRoleList是拿不到异常的,但Spring还是回滚了事务,再看一下PlatformTransactionManager 接口的getTransaction方法

/**
 * 平台事务管理器接口,定义了事务管理的基本操作。继承自TransactionManager接口。
 */
public interface PlatformTransactionManager extends TransactionManager {
    
    /**
     * 获取一个事务状态对象。这允许在一个事务中执行多个操作。
     * 
     * @param definition 事务定义,可为null。定义了事务的属性,例如隔离级别、读写模式等。
     *                   如果为null,则使用默认的事务配置。
     * @return TransactionStatus对象,代表了一个事务的状态。
     * @throws TransactionException 如果获取事务状态过程中出现错误,则抛出事务异常。
     */
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;

    /**
     * 提交当前事务。这将对所有在事务中进行的操作进行提交。
     * 
     * @param status 代表当前事务状态的TransactionStatus对象。
     * @throws TransactionException 如果提交过程中出现错误,则抛出事务异常。
     */
    void commit(TransactionStatus status) throws TransactionException;

    /**
     * 回滚当前事务。这将撤销所有在事务中进行的操作。
     * 
     * @param status 代表当前事务状态的TransactionStatus对象。
     * @throws TransactionException 如果回滚过程中出现错误,则抛出事务异常。
     */
    void rollback(TransactionStatus status) throws TransactionException;
}

可以看到getTransaction返回的是一个TransactionStatus类型的结果,它是一个事务状态,当执行insertRole方法时候,Spring会记录对应的事务状态,于是就看到了ransaction rolled back because it has been marked as rollback-only

有些场景不希望看到类似的insertRole方法的事务导致insertRoleList事务回滚,可以将insertRole方法的传播行为改为NESTED

@Transactional(propagation=Propagation.NESTED, isolation=Isolation.DEFAULT, timeout=3)
    public int insertRole(Role role) {
        // 通过RoleDao将角色对象插入数据库
        return roleDao.insertRole(role);
    }

再次执行即可

"C:\Program Files\Java\jdk-17.0.1\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2023.2.3\lib\idea_rt.jar=50190:C:\Program Files\JetBrains\IntelliJ IDEA 2023.2.3\bin" -Dfile.encoding=UTF-8 -classpath D:\Programs\Java\SpringTransaction\target\classes;C:\Users\Administrator\.m2\repository\org\springframework\spring-core\6.1.8\spring-core-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-jcl\6.1.8\spring-jcl-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-orm\6.1.8\spring-orm-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-tx\6.1.8\spring-tx-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-beans\6.1.8\spring-beans-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-context\6.1.8\spring-context-6.1.8.jar;C:\Users\Administrator\.m2\repository\io\micrometer\micrometer-observation\1.12.6\micrometer-observation-1.12.6.jar;C:\Users\Administrator\.m2\repository\io\micrometer\micrometer-commons\1.12.6\micrometer-commons-1.12.6.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-context-support\6.1.8\spring-context-support-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-expression\6.1.8\spring-expression-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-aop\6.1.8\spring-aop-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-jdbc\6.1.8\spring-jdbc-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\apache\commons\commons-dbcp2\2.12.0\commons-dbcp2-2.12.0.jar;C:\Users\Administrator\.m2\repository\org\apache\commons\commons-pool2\2.12.0\commons-pool2-2.12.0.jar;C:\Users\Administrator\.m2\repository\commons-logging\commons-logging\1.3.0\commons-logging-1.3.0.jar;C:\Users\Administrator\.m2\repository\jakarta\transaction\jakarta.transaction-api\1.3.3\jakarta.transaction-api-1.3.3.jar;C:\Users\Administrator\.m2\repository\org\mybatis\mybatis\3.5.16\mybatis-3.5.16.jar;C:\Users\Administrator\.m2\repository\org\mybatis\mybatis-spring\3.0.3\mybatis-spring-3.0.3.jar;C:\Users\Administrator\.m2\repository\org\aspectj\aspectjweaver\1.9.22.1\aspectjweaver-1.9.22.1.jar;C:\Users\Administrator\.m2\repository\org\aspectj\aspectjrt\1.9.22.1\aspectjrt-1.9.22.1.jar;C:\Users\Administrator\.m2\repository\com\mysql\mysql-connector-j\8.3.0\mysql-connector-j-8.3.0.jar;C:\Users\Administrator\.m2\repository\com\google\protobuf\protobuf-java\3.25.1\protobuf-java-3.25.1.jar;C:\Users\Administrator\.m2\repository\org\slf4j\slf4j-reload4j\2.0.13\slf4j-reload4j-2.0.13.jar;C:\Users\Administrator\.m2\repository\org\slf4j\slf4j-api\2.0.13\slf4j-api-2.0.13.jar;C:\Users\Administrator\.m2\repository\ch\qos\reload4j\reload4j\1.2.22\reload4j-1.2.22.jar;C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-core\2.23.1\log4j-core-2.23.1.jar;C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-api\2.23.1\log4j-api-2.23.1.jar com.transaction.main.Main
DEBUG 2024-05-28 19:27:40,772 org.apache.ibatis.logging.LogFactory: Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
DEBUG 2024-05-28 19:27:40,786 org.mybatis.logging.Logger: Creating MapperFactoryBean with name 'roleDao' and 'com.transaction.dao.RoleDao' mapperInterface
DEBUG 2024-05-28 19:27:40,792 org.mybatis.logging.Logger: Enabling autowire by type for MapperFactoryBean with name 'roleDao'.
DEBUG 2024-05-28 19:27:41,962 org.mybatis.logging.Logger: Parsed configuration file: 'class path resource [mybatis-config.xml]'
DEBUG 2024-05-28 19:27:41,964 org.mybatis.logging.Logger: Property 'mapperLocations' was not specified.
jdk.proxy2.$Proxy31
DEBUG 2024-05-28 19:27:43,078 org.mybatis.logging.Logger: Creating a new SqlSession
DEBUG 2024-05-28 19:27:43,084 org.mybatis.logging.Logger: Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61a1ea2c]
DEBUG 2024-05-28 19:27:43,093 org.mybatis.logging.Logger: JDBC Connection [956429999, URL=jdbc:mysql://192.168.3.115:3306/ssm?useUnicode=true;characterEncoding=utf8, MySQL Connector/J] will be managed by Spring
DEBUG 2024-05-28 19:27:43,106 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: insert into t_role(role_name, note) values (?, ?)
DEBUG 2024-05-28 19:27:43,161 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_1(String), note-1(String)
DEBUG 2024-05-28 19:27:43,177 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 19:27:43,267 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61a1ea2c]
DEBUG 2024-05-28 19:27:43,280 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61a1ea2c] from current transaction
DEBUG 2024-05-28 19:27:43,282 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_2(String), note-2(String)
DEBUG 2024-05-28 19:27:43,294 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 19:27:43,295 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61a1ea2c]
DEBUG 2024-05-28 19:27:43,305 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61a1ea2c] from current transaction
DEBUG 2024-05-28 19:27:43,306 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_3(String), note-3(String)
DEBUG 2024-05-28 19:27:43,316 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 19:27:43,317 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61a1ea2c]
DEBUG 2024-05-28 19:27:43,330 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61a1ea2c] from current transaction
DEBUG 2024-05-28 19:27:43,331 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_4(String), note-4(String)
DEBUG 2024-05-28 19:27:43,341 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 19:27:43,342 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61a1ea2c]
DEBUG 2024-05-28 19:27:43,352 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61a1ea2c] from current transaction
DEBUG 2024-05-28 19:27:43,353 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: null, note-5(String)
DEBUG 2024-05-28 19:27:43,380 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61a1ea2c]
 INFO 2024-05-28 19:27:43,633 com.transaction.service.impl.RoleListServiceImpl: org.springframework.dao.DataIntegrityViolationException: 
### Error updating database.  Cause: java.sql.SQLIntegrityConstraintViolationException: Column 'role_name' cannot be null
### The error may exist in RoleMapper.xml
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### Cause: java.sql.SQLIntegrityConstraintViolationException: Column 'role_name' cannot be null
; Column 'role_name' cannot be null
DEBUG 2024-05-28 19:27:43,637 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61a1ea2c] from current transaction
DEBUG 2024-05-28 19:27:43,637 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_6(String), note-6(String)
DEBUG 2024-05-28 19:27:43,645 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 19:27:43,646 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61a1ea2c]
DEBUG 2024-05-28 19:27:43,654 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61a1ea2c] from current transaction
DEBUG 2024-05-28 19:27:43,655 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_7(String), note-7(String)
DEBUG 2024-05-28 19:27:43,663 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 19:27:43,663 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61a1ea2c]
DEBUG 2024-05-28 19:27:43,675 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61a1ea2c] from current transaction
DEBUG 2024-05-28 19:27:43,676 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_8(String), note-8(String)
DEBUG 2024-05-28 19:27:43,685 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 19:27:43,685 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61a1ea2c]
DEBUG 2024-05-28 19:27:43,694 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61a1ea2c] from current transaction
DEBUG 2024-05-28 19:27:43,695 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_9(String), note-9(String)
DEBUG 2024-05-28 19:27:43,702 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 19:27:43,702 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61a1ea2c]
DEBUG 2024-05-28 19:27:43,714 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61a1ea2c] from current transaction
DEBUG 2024-05-28 19:27:43,715 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_10(String), note-10(String)
DEBUG 2024-05-28 19:27:43,722 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 19:27:43,723 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61a1ea2c]
DEBUG 2024-05-28 19:27:43,727 org.mybatis.logging.Logger: Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61a1ea2c]
DEBUG 2024-05-28 19:27:43,729 org.mybatis.logging.Logger: Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61a1ea2c]
DEBUG 2024-05-28 19:27:43,730 org.mybatis.logging.Logger: Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61a1ea2c]
9

Process finished with exit code 0

日志中可以看到DEBUG 2024-05-28 19:27:43,305 org.mybatis.logging.Logger: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61a1ea2c] from current transaction insertRole方法是从当前事务中获取独立的子事务去执行,即便有异常还是会提交事务,这里Spring采用了数据库的savepoint技术,但这个技术不是所有数据库都支持,当传播行为被设置为NESTED时,Spring会先探测当前数据库是否支持savepoint,如果不支持,它就会像REQUIRES_NEW一样创建新事务去运行代码,从而达到内部方法发生异常时并不回滚当前事务的目的

@Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.DEFAULT, timeout=3)
    public int insertRole(Role role) {
        // 通过RoleDao将角色对象插入数据库
        return roleDao.insertRole(role);
    }

再次执行测试代码

"C:\Program Files\Java\jdk-17.0.1\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2023.2.3\lib\idea_rt.jar=50659:C:\Program Files\JetBrains\IntelliJ IDEA 2023.2.3\bin" -Dfile.encoding=UTF-8 -classpath D:\Programs\Java\SpringTransaction\target\classes;C:\Users\Administrator\.m2\repository\org\springframework\spring-core\6.1.8\spring-core-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-jcl\6.1.8\spring-jcl-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-orm\6.1.8\spring-orm-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-tx\6.1.8\spring-tx-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-beans\6.1.8\spring-beans-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-context\6.1.8\spring-context-6.1.8.jar;C:\Users\Administrator\.m2\repository\io\micrometer\micrometer-observation\1.12.6\micrometer-observation-1.12.6.jar;C:\Users\Administrator\.m2\repository\io\micrometer\micrometer-commons\1.12.6\micrometer-commons-1.12.6.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-context-support\6.1.8\spring-context-support-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-expression\6.1.8\spring-expression-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-aop\6.1.8\spring-aop-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-jdbc\6.1.8\spring-jdbc-6.1.8.jar;C:\Users\Administrator\.m2\repository\org\apache\commons\commons-dbcp2\2.12.0\commons-dbcp2-2.12.0.jar;C:\Users\Administrator\.m2\repository\org\apache\commons\commons-pool2\2.12.0\commons-pool2-2.12.0.jar;C:\Users\Administrator\.m2\repository\commons-logging\commons-logging\1.3.0\commons-logging-1.3.0.jar;C:\Users\Administrator\.m2\repository\jakarta\transaction\jakarta.transaction-api\1.3.3\jakarta.transaction-api-1.3.3.jar;C:\Users\Administrator\.m2\repository\org\mybatis\mybatis\3.5.16\mybatis-3.5.16.jar;C:\Users\Administrator\.m2\repository\org\mybatis\mybatis-spring\3.0.3\mybatis-spring-3.0.3.jar;C:\Users\Administrator\.m2\repository\org\aspectj\aspectjweaver\1.9.22.1\aspectjweaver-1.9.22.1.jar;C:\Users\Administrator\.m2\repository\org\aspectj\aspectjrt\1.9.22.1\aspectjrt-1.9.22.1.jar;C:\Users\Administrator\.m2\repository\com\mysql\mysql-connector-j\8.3.0\mysql-connector-j-8.3.0.jar;C:\Users\Administrator\.m2\repository\com\google\protobuf\protobuf-java\3.25.1\protobuf-java-3.25.1.jar;C:\Users\Administrator\.m2\repository\org\slf4j\slf4j-reload4j\2.0.13\slf4j-reload4j-2.0.13.jar;C:\Users\Administrator\.m2\repository\org\slf4j\slf4j-api\2.0.13\slf4j-api-2.0.13.jar;C:\Users\Administrator\.m2\repository\ch\qos\reload4j\reload4j\1.2.22\reload4j-1.2.22.jar;C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-core\2.23.1\log4j-core-2.23.1.jar;C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-api\2.23.1\log4j-api-2.23.1.jar com.transaction.main.Main
DEBUG 2024-05-28 19:33:21,791 org.apache.ibatis.logging.LogFactory: Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
DEBUG 2024-05-28 19:33:21,805 org.mybatis.logging.Logger: Creating MapperFactoryBean with name 'roleDao' and 'com.transaction.dao.RoleDao' mapperInterface
DEBUG 2024-05-28 19:33:21,813 org.mybatis.logging.Logger: Enabling autowire by type for MapperFactoryBean with name 'roleDao'.
DEBUG 2024-05-28 19:33:22,549 org.mybatis.logging.Logger: Parsed configuration file: 'class path resource [mybatis-config.xml]'
DEBUG 2024-05-28 19:33:22,552 org.mybatis.logging.Logger: Property 'mapperLocations' was not specified.
jdk.proxy2.$Proxy31
DEBUG 2024-05-28 19:33:23,635 org.mybatis.logging.Logger: Creating a new SqlSession
DEBUG 2024-05-28 19:33:23,642 org.mybatis.logging.Logger: Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6d025d1d]
DEBUG 2024-05-28 19:33:23,651 org.mybatis.logging.Logger: JDBC Connection [1041905665, URL=jdbc:mysql://192.168.3.115:3306/ssm?useUnicode=true;characterEncoding=utf8, MySQL Connector/J] will be managed by Spring
DEBUG 2024-05-28 19:33:23,664 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: insert into t_role(role_name, note) values (?, ?)
DEBUG 2024-05-28 19:33:23,745 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_1(String), note-1(String)
DEBUG 2024-05-28 19:33:23,771 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 19:33:23,816 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6d025d1d]
DEBUG 2024-05-28 19:33:23,818 org.mybatis.logging.Logger: Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6d025d1d]
DEBUG 2024-05-28 19:33:23,820 org.mybatis.logging.Logger: Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6d025d1d]
DEBUG 2024-05-28 19:33:23,821 org.mybatis.logging.Logger: Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6d025d1d]
DEBUG 2024-05-28 19:33:23,850 org.mybatis.logging.Logger: Creating a new SqlSession
DEBUG 2024-05-28 19:33:23,850 org.mybatis.logging.Logger: Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2b34e38c]
DEBUG 2024-05-28 19:33:23,851 org.mybatis.logging.Logger: JDBC Connection [2144496344, URL=jdbc:mysql://192.168.3.115:3306/ssm?useUnicode=true;characterEncoding=utf8, MySQL Connector/J] will be managed by Spring
DEBUG 2024-05-28 19:33:23,851 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: insert into t_role(role_name, note) values (?, ?)
DEBUG 2024-05-28 19:33:23,852 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_2(String), note-2(String)
DEBUG 2024-05-28 19:33:23,860 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 19:33:23,862 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2b34e38c]
DEBUG 2024-05-28 19:33:23,863 org.mybatis.logging.Logger: Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2b34e38c]
DEBUG 2024-05-28 19:33:23,863 org.mybatis.logging.Logger: Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2b34e38c]
DEBUG 2024-05-28 19:33:23,864 org.mybatis.logging.Logger: Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2b34e38c]
DEBUG 2024-05-28 19:33:23,883 org.mybatis.logging.Logger: Creating a new SqlSession
DEBUG 2024-05-28 19:33:23,883 org.mybatis.logging.Logger: Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@10823d72]
DEBUG 2024-05-28 19:33:23,884 org.mybatis.logging.Logger: JDBC Connection [1183701566, URL=jdbc:mysql://192.168.3.115:3306/ssm?useUnicode=true;characterEncoding=utf8, MySQL Connector/J] will be managed by Spring
DEBUG 2024-05-28 19:33:23,884 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: insert into t_role(role_name, note) values (?, ?)
DEBUG 2024-05-28 19:33:23,885 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_3(String), note-3(String)
DEBUG 2024-05-28 19:33:23,898 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 19:33:23,899 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@10823d72]
DEBUG 2024-05-28 19:33:23,900 org.mybatis.logging.Logger: Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@10823d72]
DEBUG 2024-05-28 19:33:23,900 org.mybatis.logging.Logger: Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@10823d72]
DEBUG 2024-05-28 19:33:23,900 org.mybatis.logging.Logger: Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@10823d72]
DEBUG 2024-05-28 19:33:23,923 org.mybatis.logging.Logger: Creating a new SqlSession
DEBUG 2024-05-28 19:33:23,923 org.mybatis.logging.Logger: Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@78010562]
DEBUG 2024-05-28 19:33:23,924 org.mybatis.logging.Logger: JDBC Connection [950729555, URL=jdbc:mysql://192.168.3.115:3306/ssm?useUnicode=true;characterEncoding=utf8, MySQL Connector/J] will be managed by Spring
DEBUG 2024-05-28 19:33:23,924 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: insert into t_role(role_name, note) values (?, ?)
DEBUG 2024-05-28 19:33:23,925 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_4(String), note-4(String)
DEBUG 2024-05-28 19:33:23,935 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 19:33:23,936 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@78010562]
DEBUG 2024-05-28 19:33:23,936 org.mybatis.logging.Logger: Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@78010562]
DEBUG 2024-05-28 19:33:23,936 org.mybatis.logging.Logger: Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@78010562]
DEBUG 2024-05-28 19:33:23,937 org.mybatis.logging.Logger: Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@78010562]
DEBUG 2024-05-28 19:33:23,955 org.mybatis.logging.Logger: Creating a new SqlSession
DEBUG 2024-05-28 19:33:23,955 org.mybatis.logging.Logger: Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1c9e07c6]
DEBUG 2024-05-28 19:33:23,956 org.mybatis.logging.Logger: JDBC Connection [722513129, URL=jdbc:mysql://192.168.3.115:3306/ssm?useUnicode=true;characterEncoding=utf8, MySQL Connector/J] will be managed by Spring
DEBUG 2024-05-28 19:33:23,956 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: insert into t_role(role_name, note) values (?, ?)
DEBUG 2024-05-28 19:33:23,958 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: null, note-5(String)
DEBUG 2024-05-28 19:33:23,983 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1c9e07c6]
DEBUG 2024-05-28 19:33:24,300 org.mybatis.logging.Logger: Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1c9e07c6]
DEBUG 2024-05-28 19:33:24,300 org.mybatis.logging.Logger: Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1c9e07c6]
 INFO 2024-05-28 19:33:24,311 com.transaction.service.impl.RoleListServiceImpl: org.springframework.dao.DataIntegrityViolationException: 
### Error updating database.  Cause: java.sql.SQLIntegrityConstraintViolationException: Column 'role_name' cannot be null
### The error may exist in RoleMapper.xml
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: insert into t_role(role_name, note) values (?, ?)
### Cause: java.sql.SQLIntegrityConstraintViolationException: Column 'role_name' cannot be null
; Column 'role_name' cannot be null
DEBUG 2024-05-28 19:33:24,321 org.mybatis.logging.Logger: Creating a new SqlSession
DEBUG 2024-05-28 19:33:24,322 org.mybatis.logging.Logger: Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6dcc40f5]
DEBUG 2024-05-28 19:33:24,322 org.mybatis.logging.Logger: JDBC Connection [1887991591, URL=jdbc:mysql://192.168.3.115:3306/ssm?useUnicode=true;characterEncoding=utf8, MySQL Connector/J] will be managed by Spring
DEBUG 2024-05-28 19:33:24,322 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: insert into t_role(role_name, note) values (?, ?)
DEBUG 2024-05-28 19:33:24,323 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_6(String), note-6(String)
DEBUG 2024-05-28 19:33:24,340 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 19:33:24,341 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6dcc40f5]
DEBUG 2024-05-28 19:33:24,341 org.mybatis.logging.Logger: Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6dcc40f5]
DEBUG 2024-05-28 19:33:24,342 org.mybatis.logging.Logger: Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6dcc40f5]
DEBUG 2024-05-28 19:33:24,342 org.mybatis.logging.Logger: Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6dcc40f5]
DEBUG 2024-05-28 19:33:24,364 org.mybatis.logging.Logger: Creating a new SqlSession
DEBUG 2024-05-28 19:33:24,365 org.mybatis.logging.Logger: Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@328d044f]
DEBUG 2024-05-28 19:33:24,365 org.mybatis.logging.Logger: JDBC Connection [74735260, URL=jdbc:mysql://192.168.3.115:3306/ssm?useUnicode=true;characterEncoding=utf8, MySQL Connector/J] will be managed by Spring
DEBUG 2024-05-28 19:33:24,366 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: insert into t_role(role_name, note) values (?, ?)
DEBUG 2024-05-28 19:33:24,367 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_7(String), note-7(String)
DEBUG 2024-05-28 19:33:24,377 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 19:33:24,378 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@328d044f]
DEBUG 2024-05-28 19:33:24,378 org.mybatis.logging.Logger: Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@328d044f]
DEBUG 2024-05-28 19:33:24,378 org.mybatis.logging.Logger: Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@328d044f]
DEBUG 2024-05-28 19:33:24,378 org.mybatis.logging.Logger: Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@328d044f]
DEBUG 2024-05-28 19:33:24,396 org.mybatis.logging.Logger: Creating a new SqlSession
DEBUG 2024-05-28 19:33:24,396 org.mybatis.logging.Logger: Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@fc807c1]
DEBUG 2024-05-28 19:33:24,396 org.mybatis.logging.Logger: JDBC Connection [1506648430, URL=jdbc:mysql://192.168.3.115:3306/ssm?useUnicode=true;characterEncoding=utf8, MySQL Connector/J] will be managed by Spring
DEBUG 2024-05-28 19:33:24,397 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: insert into t_role(role_name, note) values (?, ?)
DEBUG 2024-05-28 19:33:24,398 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_8(String), note-8(String)
DEBUG 2024-05-28 19:33:24,407 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 19:33:24,408 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@fc807c1]
DEBUG 2024-05-28 19:33:24,408 org.mybatis.logging.Logger: Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@fc807c1]
DEBUG 2024-05-28 19:33:24,409 org.mybatis.logging.Logger: Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@fc807c1]
DEBUG 2024-05-28 19:33:24,409 org.mybatis.logging.Logger: Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@fc807c1]
DEBUG 2024-05-28 19:33:24,430 org.mybatis.logging.Logger: Creating a new SqlSession
DEBUG 2024-05-28 19:33:24,431 org.mybatis.logging.Logger: Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6b350309]
DEBUG 2024-05-28 19:33:24,431 org.mybatis.logging.Logger: JDBC Connection [92862012, URL=jdbc:mysql://192.168.3.115:3306/ssm?useUnicode=true;characterEncoding=utf8, MySQL Connector/J] will be managed by Spring
DEBUG 2024-05-28 19:33:24,431 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: insert into t_role(role_name, note) values (?, ?)
DEBUG 2024-05-28 19:33:24,432 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_9(String), note-9(String)
DEBUG 2024-05-28 19:33:24,447 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 19:33:24,448 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6b350309]
DEBUG 2024-05-28 19:33:24,448 org.mybatis.logging.Logger: Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6b350309]
DEBUG 2024-05-28 19:33:24,448 org.mybatis.logging.Logger: Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6b350309]
DEBUG 2024-05-28 19:33:24,448 org.mybatis.logging.Logger: Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6b350309]
DEBUG 2024-05-28 19:33:24,463 org.mybatis.logging.Logger: Creating a new SqlSession
DEBUG 2024-05-28 19:33:24,463 org.mybatis.logging.Logger: Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@74174a23]
DEBUG 2024-05-28 19:33:24,463 org.mybatis.logging.Logger: JDBC Connection [230991505, URL=jdbc:mysql://192.168.3.115:3306/ssm?useUnicode=true;characterEncoding=utf8, MySQL Connector/J] will be managed by Spring
DEBUG 2024-05-28 19:33:24,463 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: insert into t_role(role_name, note) values (?, ?)
DEBUG 2024-05-28 19:33:24,464 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_10(String), note-10(String)
DEBUG 2024-05-28 19:33:24,474 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2024-05-28 19:33:24,474 org.mybatis.logging.Logger: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@74174a23]
DEBUG 2024-05-28 19:33:24,474 org.mybatis.logging.Logger: Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@74174a23]
DEBUG 2024-05-28 19:33:24,475 org.mybatis.logging.Logger: Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@74174a23]
DEBUG 2024-05-28 19:33:24,475 org.mybatis.logging.Logger: Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@74174a23]
9

Process finished with exit code 0

日志中看到DEBUG 2024-05-28 19:33:24,396 org.mybatis.logging.Logger: Creating a new SqlSession,当执行insertRole方法时,Spring会为每一次insertRole方法的调用都分配一个新的SqlSession去执行

REQUIRES_NEW和NESTED都没有回滚,但也有很大的区别,当传播行为是REQUIRES_NEW时,会创建新的数据库连接去执行子方法,这时才会根据子方法的事务配置来设置这条新的连接属性,比如隔离级别、超时时间等;Spring在传播行为不是REQUIRES_NEW时不会创建新的连接,也不会重新设置事务的属性,而是沿用当前事务(insertRoleList方法)的事务配置,子方法insertRole的事务配置、隔离级别
、超时时间等都失效

当使用独立的连接运行子方法时,可以重新设置独立的隔离级别、锁、会话等,此时要用REQUIRES_NEW,如果需要沿用当前事务的隔离级别、锁、会话等就要用NESTED

注解@Transactional

有时候配置的注解@Transactional会失效,注解@Transactional的底层实现是SpringAOP技术,而SpringAOP技术使用的是动态代理,这就意味着对于静态方法(static)和非public方法,@Transactional是生效的,还有一个更为隐秘的问题,在使用过程中机器容易出现疏忽的地方就是产生自调用

package com.transaction.service.impl;

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;

import com.transaction.dao.RoleDao;
import com.transaction.pojo.Role;
import com.transaction.service.RoleService;

import java.util.List;

/**
 * RoleService的实现类,提供角色相关的业务逻辑处理。
 */
@Service
public class RoleServiceImpl implements RoleService {

    // 自动注入RoleDao,用于执行数据库操作
    @Autowired
    private RoleDao roleDao = null;

    /**
     * 插入一个新的角色到数据库。
     * @param role 角色对象,包含角色的信息。
     * @return 插入成功返回插入的行数,否则返回0。
     * @@Transactional 注解指定了该方法需要事务支持,具体配置为:
     * - propagation=Propagation.REQUIRED 表示如果外部有事务,则加入外部事务,如果没有则创建新的事务。
     * - isolation=Isolation.DEFAULT 使用数据库默认的隔离级别。
     * - timeout=3 设定了事务的超时时间为3秒。
     */
    @Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.DEFAULT, timeout=3)
    public int insertRole(Role role) {
        // 通过RoleDao将角色对象插入数据库
        return roleDao.insertRole(role);
    }

    /**
     * 插入角色列表到数据库。
     * 该方法会遍历角色列表,尝试对每个角色进行插入操作。
     * 如果对某个角色的插入操作失败,不会影响其他角色的插入尝试。
     *
     * @param roleList 要插入的角色列表,不应为空。
     * @return 成功插入的角色数量。
     */
    @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED, timeout=3)
    public int insertRoleList(List<Role> roleList){
        int count = 0; // 用于记录成功插入的角色数量。
        for (Role role : roleList) {
            try{
            	// 调用自身类的方法,产生自调用问题
                insertRole(role); // 尝试插入单个角色。
                count++; // 如果插入成功,计数器加一。
            }catch (Exception e){
                e.printStackTrace(); // 捕获并打印异常,但不中断其他角色的插入尝试。
            }
        }
        return count; // 返回成功插入的角色数量。
    }

}

在insertRoleList方法中,调用了自身类的insertRole方法,而insertRole声明式REQUIRES_NEW的传播行为,也就是每次调用都会产生新的事务运行才对,但实际执行并非如此,角色在插入的时候会使用同一事务,也就是说在insertRole上标注的注解@Transactional失效了,出现这个问题的原因在于注解@Transactional的实现原理是AOP,而AOP的实现原理是动态代理,自己调用自己,换句话说并不存在代理对象的调用,这样就不会产生AOP去设置注解@Transactional配置的参数,从而出现自调用注解失效的问题

package com.transaction.service.impl;

import com.transaction.dao.RoleDao;
import com.transaction.pojo.Role;
import com.transaction.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * RoleService的实现类,提供角色相关的业务逻辑处理。
 */
@Service
public class RoleServiceImplII implements RoleService, ApplicationContextAware {

    /**
     * 应用上下文实例,用于访问应用上下文中的所有bean和服务。
     * 这个字段是私有的,意味着它只能在类的内部被访问。
     */
    private ApplicationContext applicationContext;

    /**
     * 设置应用上下文。
     * 该方法用于注入一个应用上下文对象,以便在后续的逻辑中使用。
     *
     * @param applicationContext 应用上下文对象,提供了访问应用上下文中的bean和其他资源的能力。
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext; // 将传入的应用上下文对象保存至成员变量中以备后用
    }

    // 自动注入RoleDao,用于执行数据库操作
    @Autowired
    private RoleDao roleDao = null;

    /**
     * 插入一个新的角色到数据库。
     * @param role 角色对象,包含角色的信息。
     * @return 插入成功返回插入的行数,否则返回0。
     * @@Transactional 注解指定了该方法需要事务支持,具体配置为:
     * - propagation=Propagation.REQUIRED 表示如果外部有事务,则加入外部事务,如果没有则创建新的事务。
     * - isolation=Isolation.DEFAULT 使用数据库默认的隔离级别。
     * - timeout=3 设定了事务的超时时间为3秒。
     */
    @Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.DEFAULT, timeout=3)
    public int insertRole(Role role) {
        // 通过RoleDao将角色对象插入数据库
        return roleDao.insertRole(role);
    }

    /**
     * 插入角色列表到数据库。
     * 该方法会遍历角色列表,尝试对每个角色进行插入操作。
     * 如果对某个角色的插入操作失败,不会影响其他角色的插入尝试。
     *
     * @param roleList 要插入的角色列表,不应为空。
     * @return 成功插入的角色数量。
     */
    @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED, timeout=3)
    public int insertRoleList(List<Role> roleList){
        int count = 0; // 用于记录成功插入的角色数量。
        RoleService roleService = applicationContext.getBean(RoleService.class);
        for (Role role : roleList) {
            try{
                roleService.insertRole(role); // 尝试插入单个角色。
                count++; // 如果插入成功,计数器加一。
            }catch (Exception e){
                e.printStackTrace(); // 捕获并打印异常,但不中断其他角色的插入尝试。
            }
        }
        return count; // 返回成功插入的角色数量。
    }

}

如代码所示,除了之前用两个接口的实现类相互调用,避免自调用,从而避免注解@Transactional失效意外,还可以这样改,直接从容器中获取RoleService的代理对象,而非直接调用,如此Spring才启用了AOP技术,设置注解@Transactional配置才会正常生效

典型案例

package com.transaction.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

import com.transaction.pojo.Role;
import com.transaction.service.RoleListService;
import com.transaction.service.RoleService;

/**
 * 角色控制器类,负责处理角色相关的请求。
 */
@Controller
public class RoleController {
    @Autowired
    private RoleService roleService; // 注入角色服务

    @Autowired
    private RoleListService roleListService; // 注入角色列表服务

    /**
     * 一个示例方法,演示了如何使用角色服务进行角色的插入操作。
     * 这个方法不返回任何内容。
     * 注意:此方法内调用的服务方法均有事务支持,但该方法本身不声明事务。
     */
    public void errerUseServices() {
        // 创建并插入第一个角色
        Role role1 = new Role();
        role1.setRoleName("role_name_1");
        role1.setNote("role_note_1");
        // 调用带事务的方法插入角色
        roleService.insertRole(role1);

        // 创建并插入第二个角色
        Role role2 = new Role();
        role2.setRoleName("role_name_2");
        role2.setNote("role_note_2");
        // 同样调用带事务的方法插入角色
        roleService.insertRole(role2);
    }
}

两次插入分别属于不同事务,无法保证同时成功或同时失败,在使用带有事务的Service方法时,应该只有一个入口,然后使用传播行为来定义事务策略,如此多次调用会造成不能同时提交或回滚的数据一致性问题

在实际的数据库事务的开发中应非常小心处理异常,避免出现想要的回滚或提交没出现,不想要的反而出现了,非常浪费数据库资源

在实际的数据库事务的开发中,与事务无关的代码就不要启用事务,非常浪费数据库资源