☰
Spring对事务的支持
2026/11/5 3:56:03 网站建设 项目流程

一、什么是事务

事务是一组数据库操作,这些操作要么全部成功,要么全部失败并回滚。

在一个业务流程中,通常需要多条DML(insert、delete、update)语句共同联合才能完成。这些语句必须同时成功或者同时失败。

二、事务的四大特性

  1. 原子性:事务是最小不可分割单元,全部成功,或者全部回滚。
  2. 一致性:事务前后数据状态合法(转账总金额不变)。
  3. 隔离性:多个事务并发执行,互相隔离。
  4. 持久性:事务提交成功,数据库永久落库,宕机也不丢失。

三、事务的四个处理过程

  1. 开启事务
  2. 执行核心业务代码
  3. 提交事务(核心业务过程中没有出现异常才能提交事务)
  4. 回滚事务(核心业务处理过程中出现异常就回滚事务)

四、事务并发带来的问题

  1. 脏读:读到别的事务未提交的数据,例如:
  2. 不可重复读:同一个事务内,两次读同一个数据,中间被别的事务修改提交,两次结果不一样。
  3. 幻读:同一个事务,两次查询,别的事务新增/删除数据,行数变了。例如:

五、事务的隔离级别(解决并发问题)

六、事务属性

Propagation propagation() default Propagation.REQUIRED;

--用来设置事务的传播特性

Isolation isolation() default Isolation.DEFAULT;

--设置事务的隔离级别

int timeout() default -1;

--设置事务的超时时间

boolean readOnly() default false;

--设置只读事务

Class<? extends Throwable>[] rollbackFor() default {};

--指定哪些异常时,回滚

Class<? extends Throwable>[] noRollbackFor() default {};

--指定某个异常时不回滚

6.1 事务传播行为

什么是事务的传播行为?

在某个类中,有两个方法,a方法和b方法,连个方法上都有事务,在a方法中调用b方法,事务是如何传递的?是合并到一个事务?还是开启一个新的事务?这就是事务的传播行为。

  1. REQUIRED(默认)有事务,就加入当前事务;没有,新建事务。共用同一个事务,一起提交、一起回滚。👉 绝大多数业务使用

  2. SUPPORTS有事务就加入;没有事务,就非事务运行。适合查询、只读方法。

  3. MANDATORY必须在已有事务里执行;外面没有事务 → 直接抛异常。强制上层开启事务。

  4. REQUIRES_NEW一定新建独立事务;外层如果有事务,先把外层事务挂起。两个事务完全独立,互不影响。、

  5. NOT_SUPPORTED以非事务方式执行;外层有事务,就把外层事务挂起。不参与事务。

  6. NEVER必须无事务运行;外层存在事务 → 抛出异常。禁止在事务内调用。

  7. NESTED 嵌套事务外层有事务:开启嵌套子事务,基于数据库savepoint保存点; 内层子事务回滚,只回滚到保存点,不影响外层; 外层事务回滚,嵌套子事务跟着一起回滚。 外层没有事务:行为等价于 REQUIRED。

在代码中设置事务的传播行为,代码如下:

@Transactional(propagation = Propagation.REQUIRED)

七、Spirng实现事务的两种方式

  1. 编程式事务(这里不讲,用的少)
  2. 声明式事务(重点)
    1. 基于注解的方式(重点)
    2. 基于XML的方式(目前不做讲解)

基于注解的方式,测试传播行为

代码如下:

目录结构如下:

两个类MyService1和MyService2类:

@Service public class MyService1 { @Autowired private AccountMapper accountMapper; @Autowired private MyService2 myService2; @Transactional(propagation = Propagation.REQUIRED) public void save(Account account) { accountMapper.insert(account); try { Account act = new Account(); act.setActNo("act-004"); act.setBalance(new BigDecimal(200)); myService2.save(act); } catch (Exception e) { } } }
@Service public class MyService2 { @Autowired private AccountMapper accountMapper; @Transactional(propagation = Propagation.REQUIRED) public void save(Account account) { accountMapper.insert(account); //模拟异常 // System.out.println(10/0); } }

SpringMybatisConfig代码如下

@Configuration //组件扫描 @ComponentScan("com.cjs.bank") @EnableAspectJAutoProxy(proxyTargetClass = true) //引入外部属性配置文件 @PropertySource("classpath:application.properties") //Mapper扫描器 @MapperScan(basePackages = {"com.cjs.bank.mapper"}) //开启事务注解支持 @EnableTransactionManagement public class SpringMybatisConfig { //配置数据源 @Bean public DataSource dataSource(@Value("${mybatis.data.source.driver}") String driverClassName, @Value("${mybatis.data.source.url}") String url, @Value("${mybatis.data.source.user}") String name, @Value("${mybatis.data.source.password}") String password ) { HikariDataSource dataSource = new HikariDataSource(); dataSource.setDriverClassName(driverClassName); dataSource.setJdbcUrl(url); dataSource.setUsername(name); dataSource.setPassword(password); return dataSource; } //注意:方法参数dataSource Spring IoC容器会自动注入 //配置SqlSessinFactioryBean @Bean public SqlSessionFactoryBean sqlSessionFactoryBean(@Value("${mybatis.config.location}") String configLocatin,DataSource dataSource, @Value("${mybatis.type.aliases.package}") String typeAliasesPackage ) { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); // Resource resource = new ClassPathResource(configLocatin); sqlSessionFactoryBean.setConfigLocation(new ClassPathResource(configLocatin)); sqlSessionFactoryBean.setDataSource(dataSource); sqlSessionFactoryBean.setTypeAliasesPackage(typeAliasesPackage); //指定Mapper XML文件位置 try { sqlSessionFactoryBean.setMapperLocations( new PathMatchingResourcePatternResolver().getResources("classpath*:com/cjs/bank/mapper/*.xml")); } catch (java.io.IOException e) { throw new RuntimeException("加载Mapper XML失败", e); } return sqlSessionFactoryBean; //返回IoC容器管理 } //配置事务管理器,还需要开启事务注解,这样@Transactional才可以使用,因此还需要配置一个开关 @Bean public DataSourceTransactionManager transactinManage(DataSource dataSource) { DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(dataSource); return dataSourceTransactionManager; } }

Account代码如下:

@Data public class Account { private Long id; private String actNo; private BigDecimal balance; }

AccountMapper代码如下:

public interface AccountMapper { /** * 根据账号查询账号信息 * @param actNo 账号 * @return 账号信息 */ Account selectByActNo(String actNo); /** * 根据账号的余额 * @param account 账号对象 * @return 1:表示成功,其他值表示失败 */ int updateBelanceByActNo(Account account); /** * 保存账号信息 * @param account * @return */ int insert(Account account); }

AccountMapper.XML关键代码如下:

<?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 namespace="com.cjs.bank.mapper.AccountMapper"> <insert id="insert"> insert into t_act(act_no,balance) values(#{actNo},#{balance}) </insert> </mapper>

单元测试代码如下:

@Test public void test2(){ ApplicationContext act = new AnnotationConfigApplicationContext(SpringMybatisConfig.class); MyService1 myService1 = act.getBean(MyService1.class); Account account = new Account(); account.setActNo("act-003"); account.setBalance(new BigDecimal(200)); myService1.save(account); }

数据库图片:

程序分析:

以上代码主要是测试事务的传播行为,有的代码多余,是其他公共,不影响此处的操作。数据库图片为结构图。

操作步骤:修改在MyService1和MyService2中的关键代码

@Transactional(propagation = Propagation.REQUIRED)

会有不同的结果。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询