6、Spring事务配置上篇

  • Post author:
  • Post category:其他




一、事务简介



1、概述



1、事务在逻辑上一组操作,要么都执行(成功),要么都不执行(失败),主要是针对数据库而言的,比如MySQL、Oracle等。


2、事务是数据库提供的特性,因此可以直接通过操作数据库来操作事务,但是对Java开发来说,更多的是使用现成的Java事务API来管理事务,这些API都非常的底层。Java的API都是通过数据库连接(Connection)来操作数据库的,Connection本质上就是一个项目到数据库的Socket连接,建立连接之后就能向数据库发送各种操作指令;

事务的管理也是基于连接,并且一个事务对应一个数据库连接




2、事务的四个重要特性



1、

原子性(Atomicity)

:一个事务中的所有操作,要么全部成功,要么全部失败,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。


2、

一致性(Consistency)

:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。


3、

隔离性(Isolation)

:数据库允许多个并发事务同时对其数据进行读写和修改,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。


4、

持久性(Durability)

:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。



3、Spring事务的好处



1、Spring提供了一种更加简单并且强大的的方式对事务管理进行了支持,是基于AOP机制实现的。


2、好处:



  • 能与Spring数据访问抽象完美集成

    ,Spring数据访问抽象定义了访问数据库的标准,通过此可以引入其他的数据访问框架。



  • 为不同的访问框架的事务管理提供了一致的抽象编程模型

    ,比如JDBC、Hibernate、MyBatis等,它们都有自己的控制事务的方式,但是Spring数据访问抽象能够与这些框架无缝集成,Spring事务管理又与数据访问抽象完美集成,那么当Spring中引入集成这些框架后,可以统一使用Spring提供的API或者声明方式来管理。



  • 提供了声明式事务管理和编程式事务管理两种方式

    ,大多数使用声明式事务管理。



  • 支持全局事务

    ,(Global Transactions,也就是分布式事务,单个操作涉及多个不同的数据源/数据库)和本地事务(Local Transactions,单个操作设计一个数据源/数据库)的所有功能。



二、Spring事务管理的核心API



1、概述



1、Spring将事务管理的核心抽象为一个事务管理器

TransactionManager

,该接口只有一个简单的接口定义,属于一个标记接口,没有任何方法可以实现,如果实现了该接口,那么就标志着该类属于一个事务管理器。


2、该接口存在两个重要的子接口:



  • org.springframework.transaction.PlatformTransactionManager

    :命令式事务管理的核心接口,



  • org.springframework.transaction.ReactiveTransactionManager

    :响应事务管理的核心接口,这是Spring 5.2新增的功能,用于配合Spring Data MongoDB 2.2和Spring Data R2DBC 1.0依赖。



2、PlatformTransactionManager事务管理器



1、PlatformTransactionManager是命令式事务管理器接口。是一个服务提供方接口,采用的是

Service Provider Interface

服务注册发现机制,即SPI,这是一种JDK自带的服务发现机制,在这里用于实现事务管理器服务的扩展发现。


2、它虽然是一个服务提供方接口,但是spring-tx仍然提供了一些默认实现,常见的实现有:



  • org.springframework.transaction.jta.JtaTransactionManager

    :该事务管理器适用于处理分布式事务,即跨多个资源的事务,以及一般控制应用程序服务器资源(例如JNDI中可用的JDBC数据源)上的事务。



  • org.springframework.jdbc.datasource.DataSourceTransactionManager

    :该事务管理器能够在任何环境中使用任何JDBC驱动程序工作。该事务管理器操作的数据源需要返回独立的连接。 连接可能来自池(数据库连接池),但数据源不得返回线程范围/请求范围的连接等。此事务管理器将根据指定的传播行为将连接与线程绑定事务本身相关联。它假定即使在正在进行的事务期间也可以获得单独的独立连接。使用Spring JDBC或者MyBatis进行数据库访问和操作时使用。


3、

PlatformTransactionManager类中主要包括事务的提交的方法commit,回滚的方法rollback,以及根据传递的事务定义TransactionDefinition获取一个事务状态对象TransactionStatus的方法getTransaction,实际上获取的事务状态对象就可以看作事务对象本身


public interface PlatformTransactionManager extends TransactionManager {
    /**
     * 返回当前活动事务(当前线程栈的事务)或根据指定的传播行为创建一个新事务
     * @param definition 事务定义实例(可为null),描述了一个事务的传播行为、隔离级别、超时时间等属性。
     * @return 表示新事务或当前事务的事务状态对象
     */
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
            throws TransactionException;

    /**
     * 提交给定事务,和提交的事务状态有关,若事务状态被标记为回滚,则执行回滚操作而不是提交
     * @param status 事务状态对象,由getTransaction方法返回
     */
    void commit(TransactionStatus status) throws TransactionException;

    /**
     * 回滚给定事务
     * @param status 事务状态对象,由getTransaction方法返回
     */
    void rollback(TransactionStatus status) throws TransactionException;
}



3、TransactionStatus事务状态



1、TransactionStatus是一个事务的状态接口,提供了一些控制事务执行和查询事务状态的方法。实际上TransactionStatus对象就代表一个事务对象。


2、常用方法:
方法名 说明


boolean hasSavepoint()


返回此事务是否在内部带有保存点,这是实现嵌套事务回滚特性的关键


boolean isNewTransaction()


返回当前事务是否为新事务


void setRollbackOnly()


设置仅事务回滚,这指示事务管理器,事务的唯一可能的结果可能是回滚。该方法可以用在某些不能抛出异常的地方,作为引发异常的替代方法,因为异常会触发回滚。


boolean isRollbackOnly()


返回事务是否标记为仅回滚


boolean isCompleted()


返回事务是否已完成,即是否已提交或回滚


Object createSavepoint()


创建新的保存点,可以通过rollbackToSavepoint回滚到特定的保存点


void rollbackToSavepoint(Object savepoint)


回滚到给定的保存点


void releaseSavepoint(Object savepoint)


显式释放给定的保存点,大多数事务管理器将在事务完成时自动释放保存点
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {

    boolean hasSavepoint();
    
    void flush();
    
    boolean isNewTransaction();

    void setRollbackOnly();

    boolean isRollbackOnly();

    boolean isCompleted();

    Object createSavepoint() throws TransactionException;

    void rollbackToSavepoint(Object savepoint) throws TransactionException;
    
    void releaseSavepoint(Object savepoint) throws TransactionException;
}



4、TransactionDefinition事务定义



1、TransactionDefinition是一个定义了符合Spring标准的事务属性的接口,内部具有获取各种Spring事务属性的方法,并且定义了以下几种属性常量:



  • PROPAGATION

    :事务的传播行为。 通常,事务范围内的所有代码都在该事务中运行。但是,如果事务上下文已存在,则可以指定传播行为。例如,代码可以在现有事务(常见情况下)中继续运行,也可以挂起现有事务并创建新的事务。



  • ISOLATION

    :事务的隔离级别。 用来表示事务与事务之间的工作隔离的程度。例如:某事务能否查到来自其他事务的未提交的写入。



  • TIMEOUT

    :超时时间。 事务在超时之前运行多长时间,超时将自动回滚;

    Spring事务超时时间 = 事务开始时到最后一个Statement创建时时间 + 最后一个Statement的执行完的时间





  • Read-only status

    :只读状态。 当代码中只有读取没有修改数据时,可以使用只读事务。
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;


    // ==================================事务超时时间属性==================================
    int TIMEOUT_DEFAULT = -1;


    /**
     * 返回传播行为,必须返回此接口规定的传播行为常量中的一个,
     * 默认值为:PROPAGATION_REQUIRED
     */
    default int getPropagationBehavior() {
        return PROPAGATION_REQUIRED;
    }

    /**
     * 返回隔离级别,必须返回此接口规定的隔离级别常量中的一个,这些常量与java.sql.Connection中的常量匹配
     * 专门设计用于PROPAGATION_REQUIRED 或 PROPAGATION_REQUIRES_NEW,因为它仅适用于新启动的事务。
     * 默认值为:ISOLATION_DEFAULT。请注意,不支持自定义隔离级别的事务管理器在返回给定ISOLATION_DEFAULT
     *         以外的任何其他级别时将引发异常。
     */
    default int getIsolationLevel() {
        return ISOLATION_DEFAULT;
    }

    /**
     * 返回事务超时时间。必须返回秒级别的时间,或TIMEOUT_DEFAULT(-1)
     * 专门设计用于PROPAGATION_REQUIRED 或 PROPAGATION_REQUIRES_NEW,因为它仅适用于新启动的事务。
     * 默认值为:ISOLATION_DEFAULT(-1)
     */
    default int getTimeout() {
        return TIMEOUT_DEFAULT;
    }

    /**
     * 返回是否作为只读事务进行优化。
     * 如果仅作为只读事务,那么返回true,默认返回false
     */
    default boolean isReadOnly() {
        return false;
    }

    /**
     * 返回此事务的名称。可以是null。将用作要在事务监视器中显示的事务名称。
     * 在Spring声明式事务中,名称默认是:所属类的完全限定的类名.方法名
     */
    @Nullable
    default String getName() {
        return null;
    }

    /**
     * 返回具有默认值的不可修改的事务定义。
     * 如果是出于自定义目的,应该使用可修改的DefaultTransactionDefinition代替。
     */
    static TransactionDefinition withDefaults() {
        return StaticTransactionDefinition.INSTANCE;
    }
}



三、编程式事务



1、概述



1、编程式事务就是在业务代码中手动调用事务管理相关的方法进行事务管理,这样的好处是事务控制的粒度非常细,可以精确到代码行级别;但是代码耦合度高。


2、Spring框架提供了两种编程式事务管理方式:


  • 使用上层的TransactionTemplate或者TransactionalOperator。


  • 直接使用TransactionManager的实现。


3、对上述两种方式的:


  • Spring项目组推荐编程式事务管理使用

    TransactionTemplate

    ,对于响应式代码则推荐使用

    TransactionalOperator

    ,这两个类是一个模板类,提供了统一的API。


  • 第二种方式较为复杂,需要使用PlatformTransactionManager、TransactionDefinition和TransactionStatus这三个对象的各自的实现类来进行事务管理。



2、TransactionTemplate方式配置



1、TransactionTemplate相当于事务模版,它使用回调方法机制,简化、封装了原始事务方法的调用,不需要显示地进行开始事务、提交事务等方法的手动调用,业务代码只关注业务逻辑即可。


2、要想使用事务模板类,必须要传递一个事务管理器类,TransactionTemplate底层仍然是依靠事务管理器来实现事务管理的。


3、

可以在TransactionTemplate上指定事务属性,如传播行为,隔离级别,超时等。默认情况下,TransactionTemplate实例具有默认的事务属性,来自于它的父类DefaultTransactionDefinition




3、

核心方法execute(TransactionCallback<T> action:




  • 该方法传递一个TransactionCallback对象作为参数,TransactionCallback是一个函数式接口,可以使用匿名对象,也可以使用lambda表达式,方法doInTransaction中包含需要在事务上下文中运行的业务代码,并且支持返回值





  • 该方法会自动开启、提交或回滚事务





  • 默认情况下,如果抛出非受检异常(RuntimeException)或Error异常则会回滚事务,抛出其他受检异常或方法执行成功则会提交事务





  • 使用该方法时不允许直接抛出受检异常,而是必须处理!对于受检异常,可以使用doInTransaction方法传递的参数TransactionStatus(当前事务对象),可以调用它的setRollbackOnly方法手动设置该事务的唯一结果就回滚,用来代替抛出异常




4、配置依赖说明:


  • 引入spring-jdbc依赖,直接使用Spring提供的JdbcTemplate来访问数据库,不再引入其他数据库框架。


  • spring-jdbc中包含了spring-tx的依赖是用于支持Spring事务管理,当然也可以单独引入。
<!--spring 核心组件所需依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
</dependency>

<!--Spring 测试-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!--spring-jdbc,内部包含spring-tx的依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.7.RELEASE</version>
</dependency>

<!--mysql数据库驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.16</version>
</dependency>

<!--druid数据源-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.3</version>
</dependency>

<!-- 单元测试 -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>


1、新建配置文件(druid.properties)用于存储数据库连接信息
url=jdbc:mysql://localhost:3306/learning
className=com.mysql.cj.jdbc.Driver
userName=root
password=98072428


2、配置类
/**
 * @Date: 2023/2/12
 * MySQL数据库访问配置文件
 */
@Configuration
@ComponentScan("com.itan.transactional.*")
@PropertySource(value = "classpath:druid.properties", encoding = "UTF-8")
public class DataSourceConfig {
    @Value("${url}")
    private String url;

    @Value("${className}")
    private String className;

    @Value("${userName}")
    private String userName;

    @Value("${password}")
    private String password;

    /**
     * 配置Druid数据源
     */
    @Bean
    public DruidDataSource druidDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(url);
        dataSource.setDriverClassName(className);
        dataSource.setUsername(userName);
        dataSource.setPassword(password);
        return dataSource;
    }

    /**
     * 配置JdbcTemplate,直接使用spring-jdbc来操作数据库,不使用其他外部数据库框架
     */
    @Bean
    public JdbcTemplate jdbcTemplate() {
        // 传入一个数据源
        return new JdbcTemplate(druidDataSource());
    }

    /**
     * 配置DataSourceTransactionManager,用于管理某一个数据库的事务
     */
    @Bean
    public DataSourceTransactionManager transactionManager() {
        // 传入一个数据源
        return new DataSourceTransactionManager(druidDataSource());
    }
    
    /**
     * 配置TransactionTemplate,用于方便编程式的事务操作
     */
    @Bean
    public TransactionTemplate transactionTemplate() {
        // 传入一个TransactionManager
        return new TransactionTemplate(transactionManager());
    }
}


3、正常测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = DataSourceConfig.class)
public class Test1 {
    @Resource
    private JdbcTemplate jdbcTemplate;

    @Resource
    private TransactionTemplate transactionTemplate;

    @Test
    public void test01() {
        // 使用lambda表达式方式
        Integer res1 = transactionTemplate.execute(status -> {
            return insert("张三", 20);
        });
        System.out.println("使用lambda表达式返回结果:" + res1);

        // 使用匿名内部类方式
        Integer res2 = transactionTemplate.execute(new TransactionCallback<Integer>() {
            @Override
            public Integer doInTransaction(TransactionStatus status) {
                return insert("李四", 25);
            }
        });
        System.out.println("使用匿名内部类方式返回结果:" + res2);
    }

    private int insert(String name, int age) {
        // 插入的sql
        String sql = "insert into users (name, age) values (?,?)";
        // 调用jdbcTemplate的update方法,插入数据
        return jdbcTemplate.update(sql, name, age);
    }
}
/**
 * 运行结果:
 * 使用lambda表达式返回结果:1
 * 使用匿名内部类方式返回结果:1
 */


4、异常测试


  • 对于非受检异常(RuntimeException)或Error异常则会回滚事务。


  • 对于受检异常可以通过setRollbackOnly设置事务唯一结果为回滚进行处理。
/**
 * 对于非受检异常(RuntimeException)或Error异常则会自动回滚事务
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = DataSourceConfig.class)
public class Test1 {
    @Resource
    private JdbcTemplate jdbcTemplate;

    @Resource
    private TransactionTemplate transactionTemplate;

    @Test
    public void test02() {
        // 使用lambda表达式方式
        Integer res1 = transactionTemplate.execute(status -> {
            return insert2("王五", 20);
        });
        System.out.println("使用lambda表达式返回结果:" + res1);
    }

    private int insert2(String name, int age) {
        // 插入的sql
        String sql = "insert into users (name, age) values (?,?)";
        // 调用jdbcTemplate的update方法,插入数据
        int result = jdbcTemplate.update(sql, name, age);
        // 制造非受检异常:java.lang.ArithmeticException
        int i = 1 / 0;
        return result;
    }
}
/**
 * 对于受检异常可以通过setRollbackOnly设置事务唯一结果为回滚进行处理
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = DataSourceConfig.class)
public class Test1 {
    @Resource
    private JdbcTemplate jdbcTemplate;

    @Resource
    private TransactionTemplate transactionTemplate;

    @Test
    public void test03() {
        // 使用lambda表达式方式
        Integer res1 = transactionTemplate.execute(status -> {
            try {
                return insert3("王五", 20);
            } catch (IOException e) {
                // 调用setRollbackOnly方法回滚事务
                status.setRollbackOnly();
                e.printStackTrace();
                return null;
            }
        });
        System.out.println("使用lambda表达式返回结果:" + res1);
    }

    private int insert3(String name, int age) throws IOException {
        // 插入的sql
        String sql = "insert into users (name, age) values (?,?)";
        // 调用jdbcTemplate的update方法,插入数据
        int result = jdbcTemplate.update(sql, name, age);
        // 手动抛出一个受检异常
        throw new IOException();
    }
}



3、TransactionManager方式配置



1、

使用TransactionManager进行事务管理,需要PlatformTransactionManager、TransactionDefinition和TransactionStatus这三个对象搭配使用。



2、

通过PlatformTransactionManager的getTransaction方法根据传入的事务定义TransactionDefinition对象获取一个事务状态对象TransactionStatus,并且事务的开启、提交、回滚都需要手动控制,Spring不会自动控制


/**
 * @Date: 2023/2/12
 * 编程式事务:transactionManager方式实现
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = DataSourceConfig.class)
public class Test2 {
    @Resource
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private DataSourceTransactionManager transactionManager;

    @Test
    public void test01() {
        // 事务定义对象,可以设置事务的属性
        DefaultTransactionDefinition definition = null;
        // 事务状态对象
        TransactionStatus status = null;
        try {
            // 创建事务定义
            definition = new DefaultTransactionDefinition();
            // 编程方式时才能显式设置事务名称
            definition.setName("userAdd");
            // 设置事务传播行为
            definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
            // 通过getTransaction方法获取一个事务,此时已经开启了事务
            status = transactionManager.getTransaction(definition);
            // 调用业务方法
            int res = insert1("王五", 30);
            System.out.println("受影响行数:" + res);
            // 提交事务
            transactionManager.commit(status);
        } catch (Exception e) {
            e.printStackTrace();
            // 回滚事务
            transactionManager.rollback(status);
        }
    }

    private int insert1(String name, int age) {
        // 插入的sql
        String sql = "insert into users (name, age) values (?,?)";
        // 调用jdbcTemplate的update方法,插入数据
        int result = jdbcTemplate.update(sql, name, age);
        // 制造非受检异常:java.lang.ArithmeticException
        //int i = 1 / 0;
        return result;
    }
}
/**
 * 运行结果:
 * 受影响行数:1
 */



四、基于XML的声明式事务



1、声明式事务概述



1、声明式事务就是通过配置的方式(XML或注解)告诉Spring,哪些方法需要Spring来管理事务。


2、

声明式事务是基于Spring AOP实现的,对于使用AOP声明式配置的Bean,将会生成一个AopProxy代理对象,当调用事务方法时,实际上是通过代理对象去调用的,方法前后进行拦截(TransactionInterceptor),然后通过TransactionManager在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务


在这里插入图片描述



3、Spring声明式事务优点:不需要通过编程的方式管理事务,业务逻辑代码中不掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。


4、Spring声明式事务缺点:



  • AOP的机制导致了声明式事务只能进行方法级别的事务管理,而编程式事务则可以精确到代码行级别





  • AOP的机制导致了同一个AOP类中的事务方法相互调用时,被调用方法的事务配置不会生效,原因是Spring AOP的代理机制最终还是通过原始目标对象本身去调用目标方法的,这样被调用的方法就会因为是原始对象调用的,而不被拦截




  • 无论是基于JDK动态代理还是CGLIB代理,由于本身的缺陷,它们代理的方法增强都具有限制。对于JDK的代理,目标类必须实现符合规则的接口,并且只能代理实现的接口的方法;对于CGLIB的代理,目标类不能是

    final

    修饰的,并且被代理的方法也不能是

    private/final/static

    的。



2、配置依赖



1、依赖配置和上面保持一致,由于需要使用到切入点表达式,因此还需要引入aspectjweaver的依赖。
<!--用于解析AspectJ的切入点表达式语法-->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
</dependency>



3、业务类

@Data
@NoArgsConstructor
public class User {
    private Integer id;

    private String name;

    private Integer age;

    private Date createTime;

    private Date updateTime;

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
}
package com.itan.transactional.xml.service;

import com.itan.transactional.entity.User;
import java.util.List;

/**
 * @Date: 2023/2/12
 * UserService接口类
 */
public interface UserService {
    int insertUser(User user);

    List<User> getAllUsers();
}
package com.itan.transactional.xml.service.impl;

import com.itan.transactional.entity.User;
import com.itan.transactional.xml.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.transaction.support.DefaultTransactionStatus;
import java.util.List;

/**
 * @Date: 2023/2/12
 * UserService实现类
 */
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public int insertUser(User user) {
        // 获取当前事务
        DefaultTransactionStatus transactionStatus = (DefaultTransactionStatus) TransactionAspectSupport.currentTransactionStatus();
        // 获取事务属性,如果开启了事务,那么会返回事务;如果没有事务,那么会抛出异常!
        System.out.println("insertUser method Transaction isReadOnly: " + transactionStatus.isReadOnly());
        // 插入的sql
        String sql = "insert into users (name, age) values (?,?)";
        // 调用jdbcTemplate的update方法,插入数据
        return jdbcTemplate.update(sql, user.getName(), user.getAge());
    }

    @Override
    public List<User> getAllUsers() {
        // 获取当前事务
        DefaultTransactionStatus transactionStatus = (DefaultTransactionStatus) TransactionAspectSupport.currentTransactionStatus();
        // 获取事务属性,如果开启了事务,那么会返回事务;如果没有事务,那么会抛出异常!
        System.out.println("getAllUsers method Transaction isReadOnly: " + transactionStatus.isReadOnly());
        // 查询的sql
        String sql = "select * from users";
        // 调用jdbcTemplate的update方法,插入数据
        List<User> list = jdbcTemplate.query(sql, BeanPropertyRowMapper.newInstance(User.class), null);
        return list;
    }
}



4、事务配置文件及事务标签



1、

基于XML的声明式事务是依靠tx命名空间标签和aop命名空间标签

完成的,因此需要引入两个命名空间;还需要使用到与事务相关的标签

<tx:advice>、<tx:attributes>

等组合。


2、

<tx:advice>

标签:



  • 事务通知标签,可以定义许多方法的事务语义(包括:传播行为、隔离级别、回滚规则等)

    ;该标签可以配置多个,可以使用不同的事务管理器。


  • ==id属性:==为advice bean的标识。


  • ==transaction-manager属性:==关联一个事务管理器,默认查找id/name为“transactionManager”的事务管理器,因此如果配置的事务管理器bean的id/name就是transactionManager,那么该属性可以省略。


3、

<tx:attributes>

标签:是一个标签集,内部可以配置多个

<tx:method>

标签。


4、

<tx:method>

标签:



  • 声明式事务的核心配置标签

    ,该标签用于配置某个或某些方法的事务属性,这些方法是从

    切入点表达式aop:pointcut匹配到的

    ,具有以下可配置属性:
名称 必填 默认值 说明


name


Y

表示与该的事务属性关联的方法名称,可以使用通配符*表示所有


propagation


N

REQUIRED

指定事务的传播行为,默认值对应PROPAGATION_REQUIRED


isolation


N

DEFAULT

指定事务的隔离级别,默认值对应ISOLATION_DEFAULT


timeout


N

-1

事务超时时间,单位秒,默认值为-1(永不超时)


read-only


N

false

事务只读状态


rollback-for


N

指定将触发回滚的异常,多个使用”,”分隔;不指定则使用默认策略


no-rollback-for


N

指定不会触发回滚的异常,多个使用”,”分隔;不指定则使用默认策略


5、

默认回滚策略:执行时如果抛出RuntimeException(运行时异常,非受检异常)和Error及其它们的子类异常时,将会回滚。如果抛出其他类型的异常,比如受检异常,那么不会回滚而是提交事务。

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


    <!-- 加载数据库信息配置文件 -->
    <context:property-placeholder location="classpath:druid.properties" file-encoding="UTF-8" />
    
    <!-- 开启Spring IoC组件注解包扫描,方便操作 -->
    <context:component-scan base-package="com.itan.transactional.xml" />

    <!-- druid数据源配置 -->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${url}" />
        <property name="driverClassName" value="${className}" />
        <property name="username" value="${userName}" />
        <property name="password" value="${password}" />
    </bean>

    <!-- 配置JdbcTemplate,使用JdbcTemplate操作数据库 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!-- 配置一个druidDataSource数据源 -->
        <constructor-arg name="dataSource" ref="druidDataSource" />
    </bean>

    <!-- DataSourceTransactionManager事务管理器,用来管理数据库事务 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 配置一个druidDataSource数据源 -->
        <constructor-arg name="dataSource" ref="druidDataSource" />
    </bean>

    <!-- 配置一个事务的AOP通知,通过transaction-manager属性关联一个事务管理器
    默认查找id/name为transactionManager的事务管理器,因此如果事务管理器beanName就是transactionManager,那么该属性可以省略 -->
    <!-- 该标签可以配置多个,可以使用不同的事务管理器 -->
    <!-- 此标签可以定义定义许多方法的事务语义(其中事务语义包括传播行为、隔离级别、回滚规则等)-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--一个标签集,内部可以配置多个<tx:method/>标签 -->
        <tx:attributes>
            <!--配置某些方法的事务属性,这些方法是从切入点表达式aop:pointcut匹配到的-->
            <!-- 该标签可配置的属性如下
                name: 表示匹配的方法名,可以使用通配符*
                read-only:是否是只读事务。默认false,不是。
                isolation:指定事务的隔离级别。默认值为DEFAULT,即对应ISOLATION_DEFAULT
                propagation:指定事务的传播行为。默认值为REQUIRED,即对应PROPAGATION_REQUIRED
                timeout:指定超时时间。默认值为:-1。永不超时。
                rollback-for:指定将触发回滚的异常,多个使用","分隔。不指定则使用默认策略。
                no-rollback-for:指定不会触发回滚的异常,多个使用","分隔。不指定则使用默认策略。
            -->
            <!-- 以"get"开始的所有方法都配置只读属性,可以使用通配符* -->
            <tx:method name="get*" read-only="true" timeout="30" />
            <!-- 其他剩余方法使用默认事务属性配置 -->
            <tx:method name="*" timeout="30" />
        </tx:attributes>
    </tx:advice>

    <!-- AOP配置,确保上述事务通知正常运行 -->
    <aop:config>
        <!-- 配置aop切入点表达式,该表达式用于确定哪些方法需要进行事务管理 -->
        <aop:pointcut id="userServicePointCut" expression="execution(* com.itan.transactional.xml.service.impl.UserServiceImpl.*(..))"/>
        <!-- 配置aop通知器 使用advice-ref引用上面定义的事务通知(通过指向它的id) 通过pointcut-ref引入上面定义的aop切入点表达式(通过指向它的id)
        这样,通过pointcut-ref引入的切入点表达式匹配的方法就可以应用于通过advice-ref引入的事务通知机制以及各种属性
        -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="userServicePointCut" />
    </aop:config>

</beans>



5、测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:druid-config.xml")
public class Test3 {

    @Autowired
    private UserService userService;

    @Test
    public void test01() {
        int res = userService.insertUser(new User("王五", 20));
        System.out.println("成功修改数据行数:" + res);
        List<User> users = userService.getAllUsers();
        System.out.println("获取所有用户信息:" + users);
    }

}
/**
 * 运行结果:
 * insertUser method Transaction isReadOnly: false
 * 成功修改数据行数:1
 * getAllUsers method Transaction isReadOnly: true
 * 获取所有用户信息:[
 *                  User(id=18, name=张三, age=18, createTime=null, updateTime=null),
 *                  User(id=20, name=王五, age=20, createTime=null, updateTime=null)
 *               ]
 */



五、基于注解的声明式事务



1、概述



1、声明式事务还支持注解方式配置。并且更加简单。在XML中,使用

<tx:annotation-driven>

标签开启事务注解的支持,可以不必配置

<tx:advice>、<aop:config>

标签。


2、

<tx:annotation-driven>常用属性:




  • transaction-manager属性

    :指定驱动事务的事务管理器的beanName,默认值为transactionManager;如果需要的事务管理器的beanName不是transactionManager才需要显式指定。



  • proxy-target-class属性

    :用于指定使用@Transactional注解标志的类创建的事务代理类型,默认为false,使用JDK代理;如果设置为true,使用CGLIB代理。



  • order属性

    :使用@Transactional注解的bean,当多个通知在特定连接点执行时,控制事务通知器的执行顺序。



  • mode属性

    :该属性用于指示应该采用Spring AOP来对异步方法进行动态代理,还是采用AspectJ来进行静态织入;默认为proxy,即Spring AOP代理,如果设置为aspectj,那么还需要spring-aspects.jar(其内部包含了aspectjweaver依赖)



2、@Transactional注解



1、它是Spring提供的事务注解,事务的语义(传播行为、隔离级别、回滚规则)都可以在注解属性中定义。


2、@Transactional注解可以标注在类、接口、方法上;标注在类上时,类中的所有方法都尝试应用同一个@Transactional注解;如果同时标注在类上和方法上,方法上的注解配置优先级高于类上的注解配置。


3、

由于基于Spring AOP,因此同一个代理对象的事务方法相互调用,则被调用的方法的事务注解配置不会生效




4、

如果将@Transactional注解添加在protected、private修饰的方法上,虽然代码不会有任何的报错,但是实际上注解是不会生效的(XML配置中可以生效)




5、属性说明:
属性名 说明

value

指定使用的事务管理器的beanName,默认为空

transactionManager

指定使用的事务管理器的beanName,默认为空,同value属性

propagation

事务的传播行为,默认Propagation.REQUIRED,即PROPAGATION_REQUIRED

isolation

事务的隔离级别,默认Isolation.DEFAULT,即ISOLATION_DEFAULT

timeout

事务的超时时间,默认TransactionDefinition.TIMEOUT_DEFAULT,即-1

readOnly

只读事务标识,默认false,即普通读写事务

rollbackFor

引发回滚的可选异常类Class数组,必须是Throwable下面的异常类型

rollbackForClassName

引发回滚的可选异常类名称数组,必须是Throwable下面的异常类型

noRollbackFor

不得导致回滚的异常类Class数组,必须是Throwable下面的异常类型

noRollbackForClassName

不得导致回滚的异常类名称数组,必须是Throwable下面的异常类型



3、事务配置文件

<?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: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.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">


    <!-- 加载数据库信息配置文件 -->
    <context:property-placeholder location="classpath:druid.properties" file-encoding="UTF-8" />

    <!-- 开启Spring IoC组件注解包扫描,方便操作 -->
    <context:component-scan base-package="com.itan.transactional.anno" />

    <!-- druid数据源配置 -->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${url}" />
        <property name="driverClassName" value="${className}" />
        <property name="username" value="${userName}" />
        <property name="password" value="${password}" />
    </bean>

    <!-- 配置JdbcTemplate,使用JdbcTemplate操作数据库 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!-- 配置一个druidDataSource数据源 -->
        <constructor-arg name="dataSource" ref="druidDataSource" />
    </bean>

    <!-- DataSourceTransactionManager事务管理器,用来管理数据库事务 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 配置一个druidDataSource数据源 -->
        <constructor-arg name="dataSource" ref="druidDataSource" />
    </bean>

    <!--启用基于注解的事务行为配置,通过transaction-manager属性关联一个事务管理器 -->
    <!--默认值为transactionManager,因此如果事务管理器beanName就是transactionManager,那么该属性可以省略-->
    <tx:annotation-driven transaction-manager="transactionManager" />
</beans>



4、业务配置类

package com.itan.transactional.anno.service;

import com.itan.transactional.entity.User;
import java.util.List;

/**
 * @Date: 2023/2/12
 * UserService接口类
 */
public interface UserService {
    int insertUser(User user);

    List<User> getAllUsers();
}
package com.itan.transactional.anno.service.impl;

import com.itan.transactional.entity.User;
import com.itan.transactional.xml.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.transaction.support.DefaultTransactionStatus;
import java.util.List;

/**
 * @Date: 2023/2/12
 * UserService实现类
 */
@Service
@Transactional(timeout = 30)
public class UserServiceImpl implements UserService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public int insertUser(User user) {
        // 获取当前事务
        DefaultTransactionStatus transactionStatus = (DefaultTransactionStatus) TransactionAspectSupport.currentTransactionStatus();
        // 获取事务属性,如果开启了事务,那么会返回事务;如果没有事务,那么会抛出异常!
        System.out.println("insertUser method Transaction isReadOnly: " + transactionStatus.isReadOnly());
        // 插入的sql
        String sql = "insert into users (name, age) values (?,?)";
        // 调用jdbcTemplate的update方法,插入数据
        return jdbcTemplate.update(sql, user.getName(), user.getAge());
    }

    @Override
    @Transactional(readOnly = true, timeout = 30)
    public List<User> getAllUsers() {
        // 获取当前事务
        DefaultTransactionStatus transactionStatus = (DefaultTransactionStatus) TransactionAspectSupport.currentTransactionStatus();
        // 获取事务属性,如果开启了事务,那么会返回事务;如果没有事务,那么会抛出异常!
        System.out.println("getAllUsers method Transaction isReadOnly: " + transactionStatus.isReadOnly());
        // 查询的sql
        String sql = "select id,name,age from users";
        // 调用jdbcTemplate的update方法,插入数据
        List<User> list = jdbcTemplate.query(sql, BeanPropertyRowMapper.newInstance(User.class), null);
        return list;
    }
}



5、测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:druid-anno-config.xml")
public class Test4 {

    @Autowired
    private UserService userService;

    @Test
    public void test01() {
        int res = userService.insertUser(new User("赵六", 29));
        System.out.println("成功修改数据行数:" + res);
        List<User> users = userService.getAllUsers();
        System.out.println("获取所有用户信息:" + users);
    }

}
/**
 * 运行结果:
 * insertUser method Transaction isReadOnly: false
 * 成功修改数据行数:1
 * getAllUsers method Transaction isReadOnly: true
 * 获取所有用户信息:[
 *                  User(id=18, name=张三, age=18, createTime=null, updateTime=null),
 *                  User(id=20, name=王五, age=20, createTime=null, updateTime=null),
 *                  User(id=21, name=赵六, age=29, createTime=null, updateTime=null)
 *               ]
 */



6、纯注解方式的声明式事务



1、上面通过@Transactional注解+XML搭配实现声明式事务,配置还是比较复杂,可以完全使用纯注解和Java Config配置。


2、使用

@EnableTransactionManagement

注解替代

<tx:annotation-driven>

标签来开启事务注解驱动。
package com.itan.transactional.anno.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * @Date: 2023/2/12
 * 数据库访问配置文件
 */
@Configuration
@ComponentScan("com.itan.transactional.anno.*")
@PropertySource(value = "classpath:druid.properties", encoding = "UTF-8")
@EnableTransactionManagement
public class DataSourceAnnoConfig {
    @Value("${url}")
    private String url;

    @Value("${className}")
    private String className;

    @Value("${userName}")
    private String userName;

    @Value("${password}")
    private String password;

    /**
     * 配置Druid数据源
     */
    @Bean
    public DruidDataSource druidDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(url);
        dataSource.setDriverClassName(className);
        dataSource.setUsername(userName);
        dataSource.setPassword(password);
        return dataSource;
    }

    /**
     * 配置JdbcTemplate
     */
    @Bean
    public JdbcTemplate jdbcTemplate() {
        // 传入一个数据源
        return new JdbcTemplate(druidDataSource());
    }

    /**
     * 配置DataSourceTransactionManager,用于管理某一个数据库的事务
     */
    @Bean
    public DataSourceTransactionManager transactionManager() {
        // 传入一个数据源
        return new DataSourceTransactionManager(druidDataSource());
    }
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = DataSourceAnnoConfig.class)
public class Test5 {

    @Autowired
    private UserService userService;

    @Test
    public void test01() {
        int res = userService.insertUser(new User("陈七", 29));
        System.out.println("成功修改数据行数:" + res);
        List<User> users = userService.getAllUsers();
        System.out.println("获取所有用户信息:" + users);
    }

}
/**
 * 运行结果:
 * insertUser method Transaction isReadOnly: false
 * 成功修改数据行数:1
 * getAllUsers method Transaction isReadOnly: true
 * 获取所有用户信息:[
 *                  User(id=18, name=张三, age=18, createTime=null, updateTime=null),
 *                  User(id=20, name=王五, age=20, createTime=null, updateTime=null),
 *                  User(id=21, name=赵六, age=29, createTime=null, updateTime=null),
 *                  User(id=22, name=陈七, age=29, createTime=null, updateTime=null)
 *               ]
 */