Spring 事务失效的六种情况

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,高可用系列 2核4GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 数据库事务是指作为单个逻辑工作单元执行的一系列操作,这些操作要么一起成功,要么一起失败,是一个不可分割的工作单元。

最近有小伙伴告诉松哥说面试中被问到这个问题了,不知道该怎么回答,这能忍?捋一篇文章和小伙伴们分享下吧。
既然捋成文章,就连同 Spring 事务一起梳理下吧。

  1. 什么是事务

数据库事务是指作为单个逻辑工作单元执行的一系列操作,这些操作要么一起成功,要么一起失败,是一个不可分割的工作单元。
在我们日常工作中,涉及到事务的场景非常多,一个 service 中往往需要调用不同的 dao 层方法,这些方法要么同时成功要么同时失败,我们需要在 service 层确保这一点。
说到事务最典型的案例就是转账了:

张三要给李四转账 500 块钱,这里涉及到两个操作,从张三的账户上减去 500 块钱,给李四的账户添加 500 块钱,这两个操作要么同时成功要么同时失败,如何确保他们同时成功或者同时失败呢?答案就是事务。

事务有四大特性(ACID):

原子性(Atomicity): 一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。
一致性(Consistency): 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。
隔离性(Isolation): 数据库允许多个并发事务同时对其数据进行读写和修改,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括未提交读(Read Uncommitted)、提交读(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。
持久性(Durability): 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

这就是事务的四大特性。

  1. Spring 中的事务

2.1 两种用法
Spring 作为 Java 开发中的基础设施,对于事务也提供了很好的支持,总体上来说,Spring 支持两种类型的事务,声明式事务和编程式事务。
编程式事务类似于 Jdbc 事务的写法,需要将事务的代码嵌入到业务逻辑中,这样代码的耦合度较高,而声明式事务通过 AOP 的思想能够有效的将事务和业务逻辑代码解耦,因此在实际开发中,声明式事务得到了广泛的应用,而编程式事务则较少使用,考虑到文章内容的完整,本文对两种事务方式都会介绍。
2.2 三大基础设施
Spring 中对事务的支持提供了三大基础设施,我们先来了解下。

PlatformTransactionManager
TransactionDefinition
TransactionStatus

这三个核心类是 Spring 处理事务的核心类。
2.2.1 PlatformTransactionManager
PlatformTransactionManager 是事务处理的核心,它有诸多的实现类,如下:

PlatformTransactionManager 的定义如下:
public interface PlatformTransactionManager {

TransactionStatus getTransaction(@Nullable TransactionDefinition definition);
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;

}
复制代码
可以看到 PlatformTransactionManager 中定义了基本的事务操作方法,这些事务操作方法都是平台无关的,具体的实现都是由不同的子类来实现的。
这就像 JDBC 一样,SUN 公司制定标准,其他数据库厂商提供具体的实现。这么做的好处就是我们 Java 程序员只需要掌握好这套标准即可,不用去管接口的具体实现。以 PlatformTransactionManager 为例,它有众多实现,如果你使用的是 JDBC 那么可以将 DataSourceTransactionManager 作为事务管理器;如果你使用的是 Hibernate,那么可以将 HibernateTransactionManager 作为事务管理器;如果你使用的是 JPA,那么可以将 JpaTransactionManager 作为事务管理器。DataSourceTransactionManager、HibernateTransactionManager 以及 JpaTransactionManager 都是 PlatformTransactionManager 的具体实现,但是我们并不需要掌握这些具体实现类的用法,我们只需要掌握好 PlatformTransactionManager 的用法即可。
PlatformTransactionManager 中主要有如下三个方法:
1.getTransaction()
getTransaction() 是根据传入的 TransactionDefinition 获取一个事务对象,TransactionDefinition 中定义了一些事务的基本规则,例如传播性、隔离级别等。
2.commit()
commit() 方法用来提交事务。
3.rollback()
rollback() 方法用来回滚事务。
2.2.2 TransactionDefinition
TransactionDefinition 用来描述事务的具体规则,也称作事务的属性。事务有哪些属性呢?看下图:

可以看到,主要是五种属性:

隔离性
传播性
回滚规则
超时时间
是否只读

这五种属性接下来松哥会和大家详细介绍。
TransactionDefinition 类中的方法如下:

可以看到一共有五个方法:

getIsolationLevel(),获取事务的隔离级别
getName(),获取事务的名称
getPropagationBehavior(),获取事务的传播性
getTimeout(),获取事务的超时时间
isReadOnly(),获取事务是否是只读事务

TransactionDefinition 也有诸多的实现类,如下:

如果开发者使用了编程式事务的话,直接使用 DefaultTransactionDefinition 即可。
2.2.3 TransactionStatus
TransactionStatus 可以直接理解为事务本身,该接口源码如下:
public interface TransactionStatus extends SavepointManager, Flushable {

boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
void flush();
boolean isCompleted();

}
复制代码

isNewTransaction() 方法获取当前事务是否是一个新事务。
hasSavepoint() 方法判断是否存在 savePoint()。
setRollbackOnly() 方法设置事务必须回滚。
isRollbackOnly() 方法获取事务只能回滚。
flush() 方法将底层会话中的修改刷新到数据库,一般用于 Hibernate/JPA 的会话,对如 JDBC 类型的事务无任何影响。
isCompleted() 方法用来获取是一个事务是否结束。

这就是 Spring 中支持事务的三大基础设施。

  1. 编程式事务

我们先来看看编程式事务怎么玩。
通过 PlatformTransactionManager 或者 TransactionTemplate 可以实现编程式事务。如果是在 Spring Boot 项目中,这两个对象 Spring Boot 会自动提供,我们直接使用即可。但是如果是在传统的 SSM 项目中,则需要我们通过配置来提供这两个对象,松哥给一个简单的配置参考,如下(简单起见,数据库操作我们使用 JdbcTemplate):

<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///spring_tran?serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123"/>


<property name="dataSource" ref="dataSource"/>


<property name="transactionManager" ref="transactionManager"/>


<property name="dataSource" ref="dataSource"/>


复制代码
有了这两个对象,接下来的代码就简单了:
@Service
public class TransferService {

@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
PlatformTransactionManager txManager;

public void transfer() {
    DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
    TransactionStatus status = txManager.getTransaction(definition);
    try {
        jdbcTemplate.update("update user set account=account+100 where username='zhangsan'");
        int i = 1 / 0;
        jdbcTemplate.update("update user set account=account-100 where username='lisi'");
        txManager.commit(status);
    } catch (DataAccessException e) {
        e.printStackTrace();
        txManager.rollback(status);
    }
}

}
复制代码
这段代码很简单,没啥好解释的,在 try...catch... 中进行业务操作,没问题就 commit,有问题就 rollback。如果我们需要配置事务的隔离性、传播性等,可以在 DefaultTransactionDefinition 对象中进行配置。
上面的代码是通过 PlatformTransactionManager 实现的编程式事务,我们也可以通过 TransactionTemplate 来实现编程式事务,如下:
@Service
public class TransferService {

@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
TransactionTemplate tranTemplate;
public void transfer() {
    tranTemplate.execute(new TransactionCallbackWithoutResult() {
        @Override
        protected void doInTransactionWithoutResult(TransactionStatus status) {
            try {
                jdbcTemplate.update("update user set account=account+100 where username='zhangsan'");
                int i = 1 / 0;
                jdbcTemplate.update("update user set account=account-100 where username='lisi'");
            } catch (DataAccessException e) {
                status.setRollbackOnly();
                e.printStackTrace();
            }
        }
    });
}

}
复制代码
直接注入 TransactionTemplate,然后在 execute 方法中添加回调写核心的业务即可,当抛出异常时,将当前事务标注为只能回滚即可。注意,execute 方法中,如果不需要获取事务执行的结果,则直接使用 TransactionCallbackWithoutResult 类即可,如果要获取事务执行结果,则使用 TransactionCallback 即可。
这就是两种编程式事务的玩法。
编程式事务由于代码入侵太严重了,因为在实际开发中使用的很少,我们在项目中更多的是使用声明式事务。

  1. 声明式事务

声明式事务如果使用 XML 配置,可以做到无侵入;如果使用 Java 配置,也只有一个 @Transactional 注解侵入而已,相对来说非常容易。
以下配置针对传统 SSM 项目(因为在 Spring Boot 项目中,事务相关的组件已经配置好了):
4.1 XML 配置
XML 配置声明式事务大致上可以分为三个步骤,如下:

配置事务管理器

<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///spring_tran?serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123"/>


<property name="dataSource" ref="dataSource"/>


复制代码

配置事务通知

<tx:advice transaction-manager="transactionManager" id="txAdvice">

<tx:attributes>
    <tx:method name="m3"/>
    <tx:method name="m4"/>
</tx:attributes>

</tx:advice>
复制代码

配置 AOP

<aop:config>

<aop:pointcut id="pc1" expression="execution(* org.javaboy.demo.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc1"/>

</aop:config>
复制代码
第二步和第三步中定义出来的方法交集,就是我们要添加事务的方法。
配置完成后,如下一些方法就自动具备事务了:
public class UserService {

public void m3(){
    jdbcTemplate.update("update user set money=997 where username=?", "zhangsan");
}

}
复制代码
4.2 Java 配置
我们也可以使用 Java 配置来实现声明式事务:
@Configuration
@ComponentScan
//开启事务注解支持
@EnableTransactionManagement
public class JavaConfig {

@Bean
DataSource dataSource() {
    DriverManagerDataSource ds = new DriverManagerDataSource();
    ds.setPassword("123");
    ds.setUsername("root");
    ds.setUrl("jdbc:mysql:///test01?serverTimezone=Asia/Shanghai");
    ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
    return ds;
}

@Bean
JdbcTemplate jdbcTemplate(DataSource dataSource) {
    return new JdbcTemplate(dataSource);
}

@Bean
PlatformTransactionManager transactionManager() {
    return new DataSourceTransactionManager(dataSource());
}

}
复制代码
这里要配置的东西其实和 XML 中配置的都差不多,最最关键的就两个:

事务管理器 PlatformTransactionManager。
@EnableTransactionManagement 注解开启事务支持。

配置完成后,接下来,哪个方法需要事务就在哪个方法上添加 @Transactional 注解即可,向下面这样:
@Transactional(noRollbackFor = ArithmeticException.class)
public void update4() {

jdbcTemplate.update("update account set money = ? where username=?;", 998, "lisi");
int i = 1 / 0;

}
复制代码
当然这个稍微有点代码入侵,不过问题不大,日常开发中这种方式使用较多。当@Transactional 注解加在类上面的时候,表示该类的所有方法都有事务,该注解加在方法上面的时候,表示该方法有事务。
4.3 混合配置
也可以 Java 代码和 XML 混合配置来实现声明式事务,就是一部分配置用 XML 来实现,一部分配置用 Java 代码来实现:
假设 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:tx="http://www.springframework.org/schema/tx"
   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd   http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

<!--
开启事务的注解配置,添加了这个配置,就可以直接在代码中通过 @Transactional 注解来开启事务了
-->
<tx:annotation-driven />


复制代码
那么 Java 代码中的配置如下:
@Configuration
@ComponentScan
@ImportResource(locations = "classpath:applicationContext3.xml")
public class JavaConfig {

@Bean
DataSource dataSource() {
    DriverManagerDataSource ds = new DriverManagerDataSource();
    ds.setPassword("123");
    ds.setUsername("root");
    ds.setUrl("jdbc:mysql:///test01?serverTimezone=Asia/Shanghai");
    ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
    return ds;
}

@Bean
JdbcTemplate jdbcTemplate(DataSource dataSource) {
    return new JdbcTemplate(dataSource);
}

@Bean
PlatformTransactionManager transactionManager() {
    return new DataSourceTransactionManager(dataSource());
}

}
复制代码
Java 配置中通过 @ImportResource 注解导入了 XML 配置,XML 配置中的内容就是开启 @Transactional 注解的支持,所以 Java 配置中省略了 @EnableTransactionManagement 注解。
这就是声明式事务的几种配置方式。好玩吧!

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
2月前
|
SQL Java 关系型数据库
Spring事务传播机制:7种姿势教你玩转"事务接力赛"
事务传播机制是Spring框架中用于管理事务行为的重要概念,它决定了在方法调用时事务如何传递与执行。通过7种传播行为,开发者可以灵活控制事务边界,适应不同业务场景。例如:REQUIRED默认加入或新建事务,REQUIRES_NEW独立开启新事务,NESTED支持嵌套回滚等。合理使用传播机制不仅能保障数据一致性,还能提升系统性能与健壮性。掌握这“七种人格”,才能在复杂业务中游刃有余。
|
3月前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
|
8月前
|
Java Spring
Spring中事务失效的场景
因为Spring事务是基于代理来实现的,所以某个加了@Transactional的⽅法只有是被代理对象调⽤时, 那么这个注解才会⽣效 , 如果使用的是被代理对象调用, 那么@Transactional会失效 同时如果某个⽅法是private的,那么@Transactional也会失效,因为底层cglib是基于⽗⼦类来实现 的,⼦类是不能重载⽗类的private⽅法的,所以⽆法很好的利⽤代理,也会导致@Transactianal失效 如果在业务中对异常进行了捕获处理 , 出现异常后Spring框架无法感知到异常, @Transactional也会失效
|
8月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——常见问题总结
本文总结了Spring Boot中使用事务的常见问题,虽然通过`@Transactional`注解可以轻松实现事务管理,但在实际项目中仍有许多潜在坑点。文章详细分析了三个典型问题:1) 异常未被捕获导致事务未回滚,需明确指定`rollbackFor`属性;2) 异常被try-catch“吃掉”,应避免在事务方法中直接处理异常;3) 事务范围与锁范围不一致引发并发问题,建议调整锁策略以覆盖事务范围。这些问题看似简单,但一旦发生,排查难度较大,因此开发时需格外留意。最后,文章提供了课程源代码下载地址,供读者实践参考。
209 0
|
8月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——Spring Boot 事务配置
本文介绍了 Spring Boot 中的事务配置与使用方法。首先需要导入 MySQL 依赖,Spring Boot 会自动注入 `DataSourceTransactionManager`,无需额外配置即可通过 `@Transactional` 注解实现事务管理。接着通过创建一个用户插入功能的示例,展示了如何在 Service 层手动抛出异常以测试事务回滚机制。测试结果表明,数据库中未新增记录,证明事务已成功回滚。此过程简单高效,适合日常开发需求。
1121 0
|
8月前
|
Java 数据库 微服务
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——事务相关
本文介绍Spring Boot事务配置管理,阐述事务在企业应用开发中的重要性。事务确保数据操作可靠,任一异常均可回滚至初始状态,如转账、购票等场景需全流程执行成功才算完成。同时,事务管理在Spring Boot的service层广泛应用,但根据实际需求也可能存在无需事务的情况,例如独立数据插入操作。
230 0
|
6月前
|
人工智能 Java 数据库连接
Spring事务失效场景
本文深入探讨了Spring框架中事务管理可能失效的几种常见场景及解决方案,包括事务方法访问级别不当、方法内部自调用、错误的异常处理、事务管理器或数据源配置错误、数据库不支持事务以及不合理的事务传播行为或隔离级别。通过合理配置和正确使用`@Transactional`注解,开发者可以有效避免这些问题,确保应用的数据一致性和完整性。
357 10
|
5月前
|
Java 关系型数据库 MySQL
【Spring】【事务】初学者直呼学会了的Spring事务入门
本文深入解析了Spring事务的核心概念与使用方法。Spring事务是一种数据库事务管理机制,通过确保操作的原子性、一致性、隔离性和持久性(ACID),维护数据完整性。文章详细讲解了声明式事务(@Transactional注解)和编程式事务(TransactionTemplate、PlatformTransactionManager)的区别与用法,并探讨了事务传播行为(如REQUIRED、REQUIRES_NEW等)及隔离级别(如READ_COMMITTED、REPEATABLE_READ)。
452 1
|
8月前
|
SQL Java 数据库连接
Spring中的事务是如何实现的
1. Spring事务底层是基于数据库事务和AOP机制的 2. ⾸先对于使⽤了@Transactional注解的Bean,Spring会创建⼀个代理对象作为Bean 3. 当调⽤代理对象的⽅法时,会先判断该⽅法上是否加了@Transactional注解 4. 如果加了,那么则利⽤事务管理器创建⼀个数据库连接 5. 并且修改数据库连接的autocommit属性为false,禁⽌此连接的⾃动提交,这是实现Spring事务⾮ 常重要的⼀步 6. 然后执⾏当前⽅法,⽅法中会执⾏sql 7. 执⾏完当前⽅法后,如果没有出现异常就直接提交事务 8. 如果出现了异常,并且这个异常是需要回滚的就会回滚事务
|
8月前
|
JavaScript Java 开发者
Spring事务失效,常见的情况有哪些?
本文总结了Spring事务失效的7种常见情况,包括未启用事务管理功能、方法非public类型、数据源未配置事务管理器、自身调用问题、异常类型错误、异常被吞以及业务和事务代码不在同一线程中。同时提供了两种快速定位事务相关Bug的方法:通过查看日志(设置为debug模式)或调试代码(在TransactionInterceptor的invoke方法中设置断点)。文章帮助开发者更好地理解和解决Spring事务中的问题。
336 7

热门文章

最新文章