目录

Spring 事务管理概述

环境准备

@Transactional 最简单用法

@Transactional 注解属性概述

propagation 事务传播行为

@Transactional 事务实现机制

rollbackFor 与 noRollbackFor


Spring 事务管理概述

1、Spring 事务管理分为编程式和声明式的两种方式。编程式事务通过编码方式实现事务,声明式事务基于 AOP(动态代理)将具体业务逻辑与事务处理解耦。

2、声明式事务管理使业务代码逻辑不受污染,在实际使用中较多。声明式事务有两种方式,一种是在配置文件(xml)中做相关的事务规则声明,另一种是基于 @Transactional 注解的方式。

3、因为现在注解编程已经是主流,所以实际中使用 @Transactional 注解的方式更多,也最方便。

1)默认配置下 Spring 只会回滚运行时异常(非受检查异常),即继承自 RuntimeException 的异常或者 Error。(官网)

2)@Transactional 注解只能应用到 public 修饰的方法。(官网)

4、本文测试的环境是:Java JDK8 + Spring Boot 2.1.4,内部 Spring 版本为 5.1.6,Spring Data JPA 操作 Mysql 数据库。

事先提醒:JPA 默认使用 MyISAM 作为 Mysql 的存储引擎,此引擎不支持事务、不支持外键。InnoDB 存储引擎支持事务,可以在配置文件中进行指定。

环境准备

1、pom.xml 文件内容如下:

        <!--jpa内部依赖了spring-boot-starter-jdbc,jdbc内部默认依赖Hikari数据源--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--mysql数据库驱动--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency>

pom.xml · 汪少棠/jpaTransactional - Gitee.com

2、application.yml 配置文件如下:

#配置数据源
spring:datasource:username: rootpassword: root#mysql驱动8.0.5时,指定时区 serverTimezone. 同时 driver-class-name 也变成如下的新地址,不再是以前的 com.mysql.hdbc.Driverurl: jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8&serverTimezone=UTCdriver-class-name: com.mysql.cj.jdbc.Driver#JPA配置jpa:show-sql: truehibernate:ddl-auto: update#指定 JPA 底层的 hibernate方言,使用 InnoDB 作为存储引擎。否则默认 JPA 使用的是 MyISAM 存储引擎,此引擎不支持事务、不支持外键的database-platform: org.hibernate.dialect.MySQL5InnoDBDialect

src/main/resources/application.yml · 汪少棠/jpaTransactional - Gitee.com

3、实体类如下:

import javax.persistence.*;
import java.util.Date;
/*** Created by Administrator on 2019/2/27 0027.* 电视机实体。应用启动时自动,配置文件中配置 ddl-auto: update:如果数据库不存在,则自动新建,否则不再新建。*/
@Entity
public class TV {//strategy 指定主键生成的方式,AUTO 可以指定 H2 数据库主键自动增长,IDENTITY 可以指定 mysql 主键自动增长@Id//标识为主键@GeneratedValue(strategy = GenerationType.IDENTITY)private Integer tvId;//电视id,主键/*** 下面没标识的属性都会以默认值和数据库表的字段进行映射对应* 如果修改默认值,又不属性的,可以参考:https://blog.csdn.net/wangmx1993328/article/details/82048775* 中的 "domain Area" 部分*/@Column(length = 16)//长度为16个字符private String tvName;//电视名称private Float tvPrice;//电视价格private Date dateOfProduction;//生产日期//......
}

src/main/java/com/wmx/entity/TV.java · 汪少棠/jpaTransactional - Gitee.com

4、持久化层如下:

import com.wmx.entity.TV;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/*** Java 接口可以多继承* JpaRepository 中有常用的 CRUD 、分页、排序等方法* JpaSpecificationExecutor 可以实现任意的复杂查询*/
public interface TVRepository extends JpaRepository<TV, Integer>, JpaSpecificationExecutor<TV> {
}

src/main/java/com/wmx/repository/TVRepository.java · 汪少棠/jpaTransactional - Gitee.com

5、service 接口如下:

import com.wmx.entity.TV;
import java.util.List;
public interface TVService {//查询所有List<TV> findAll();//保存或更新void save(TV tv);//根据主键 tvId 删除void deleteById(int id);
}

src/main/java/com/wmx/service/TVService.java · 汪少棠/jpaTransactional - Gitee.com

@Transactional 最简单用法

1、只需要将 @Transactional 注解添加到 service 层实现类的方法上,这个方法就会进行事务管理,这也是最简单的方法。

import com.wmx.entity.TV;
import com.wmx.repository.TVRepository;
import com.wmx.service.TVService;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
@Service
public class TVServiceImpl implements TVService {@Resourceprivate TVRepository tvRepository;@Overridepublic List<TV> findAll() {//查询所有数据,并以主键 tvId 倒序排序return tvRepository.findAll(Sort.by(Sort.Direction.DESC, "tvId"));}//org.springframework.transaction.annotation.Transactional:事务管理//添加到哪个方法上,哪个方法就会进行事务管理@Transactional@Overridepublic void save(TV tv) {tvRepository.save(tv);//故意制造一个数组下标越界异常,典型的运行时异常//如果没加 Transactional 则抛了异常,数据也会添加进数据库,加了Transactional,则会回滚。System.out.println("123".split(",")[1]);}@Overridepublic void deleteById(int id) {tvRepository.deleteById(id);}
}

src/main/java/com/wmx/service/impl/TVServiceImpl.java · 汪少棠/jpaTransactional - Gitee.com

@Transactional 注解属性概述

1、@Transactional 注解管理事务就是如此的简单,下面来看看注解的详细信息,如下所示为 @Transactional 的源码:

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {//当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器。@AliasFor("transactionManager")String value() default "";//当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器。//如 @Transactional(value = "jpaTransactionManager")@AliasFor("value")String transactionManager() default "";//事务的传播行为。默认如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务Propagation propagation() default Propagation.REQUIRED;//事务的隔离级。默认使用底层数据库自己的隔离级别Isolation isolation() default Isolation.DEFAULT;//事务超时时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。-1表示永不超时int timeout() default -1;//事务是否为只读事务,为了忽略那些不需要事务的方法,如读取数据,可以设置 read-only 为 true。boolean readOnly() default false;//用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。Class<? extends Throwable>[] rollbackFor() default {};//用于指定能够触发事务回滚的异常名称,可以指定多个String[] rollbackForClassName() default {};//抛出指定的异常类型,不回滚事务,可以指定多个Class<? extends Throwable>[] noRollbackFor() default {};//抛出指定的异常名称,不回滚事务,可以指定多个。String[] noRollbackForClassName() default {};
}

propagation(传播)事务传播行为,默认值为 Propagation.REQUIRED,它是一个枚举,可选值如下:

REQUIRED:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。required(必须的)。
SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务方式继续运行。supports(支持的)
MANDATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。mandatory(强制的)
REQUIRES_NEW:重新创建一个新的事务,如果当前存在事务,暂停当前的事务。requires_new(依赖新的)
NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务。not_supported(不支持的)
NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常。never(从不)
NESTED:和 REQUIRED 效果一样。nested(嵌套)

isolation(隔离)事务隔离级别,默认值为 Isolation.DEFAULT,它是一个枚举,可选择如下:

DEFAULT:使用底层数据库默认的隔离级别。
READ_UNCOMMITTED:未提交读。无法防止。
READ_COMMITTED:可提交读。可以防止脏读。
REPEATABLE_READ:可重复读。可以防止脏读、可重复读。mysql 默认隔离级别。
SERIALIZABLE:串行事务。最高事务隔离级别,可以防止脏读、可重复读、幻读。

propagation 事务传播行为

情形1:同类中 save 方法内部调用 deleteById 方法,且两者上面都没有加 @Transactional 事务管理,当控制器层调用 save 方法后,显然会先删除 id 为 14 的数据,然后插入新数据。

 @Overridepublic void save(TV tv) {deleteById(14);tv.setTvName(tv.getTvName());tvRepository.save(tv);}@Overridepublic void deleteById(int id) {tvRepository.deleteById(id);}

情形2:save 方法内部调用 deleteById(两者都未加@Transactional) 后,故意制造一个运行时异常,结果就是 deleteById 会正常删除 id 为 13 的数据,之后 save 因为抛异常了,所以不会再 save 插入新数据了。

    @Overridepublic void save(TV tv) {deleteById(13);//故意制造一个数组下标越界异常,典型的运行时异常System.out.println("123".split(",")[1]);tv.setTvName(tv.getTvName() + "_xxx");tvRepository.save(tv);}@Overridepublic void deleteById(int id) {tvRepository.deleteById(id);}

情形3:在上面的基础上,在 save 方法上加上 @Transactionl 事务管理,因为事务具有传播特性,所以 save 与 deleteById 位于同一个事务,deleteById 执行后,save 方法抛了异常,会导致数据回滚,所以不会删除也不会新增。

    //org.springframework.transaction.annotation.Transactional:事务管理//@Transactional 添加到哪个方法上,哪个方法就会进行事务管理.默认传播特性为REQUIRED@Transactional(propagation = Propagation.REQUIRED)@Overridepublic void save(TV tv) {deleteById(12);//故意制造一个数组下标越界异常,典型的运行时异常System.out.println("123".split(",")[1]);tv.setTvName(tv.getTvName() + "_xxx");tvRepository.save(tv);}@Overridepublic void deleteById(int id) {tvRepository.deleteById(id);}

情形4:假如 save 方法上面不加 @Transactionl ,而在 deleteById 方法上面加 @Transaction 事务管理,显然就是 save 方法根本没有事务,只有 deleteById 有事务管理,结果就是删除操作会成功,而 save 方法因为自己抛异常了,后面的添加数据,不管有没有事务都不会执行的。

情形5:同类中内部方法相互调用,默认代理模式下,如果调用者自己已经有事务,则被调用的永远和它处于同一事务。如下所示 save 方法有事务管理,deleteById 方法的 REQUIRES_NEW、NOT_SUPPORTED、NEVER 等等都不会有效,REQUIRES_NEW 并不会为 deleteById 新开一个事务。

 //org.springframework.transaction.annotation.Transactional:事务管理//@Transactional 添加到哪个方法上,哪个方法就会进行事务管理.默认传播特性为REQUIRED@Override@Transactional(propagation = Propagation.REQUIRED)public void save(TV tv) {deleteById(11);//故意制造一个数组下标越界异常,典型的运行时异常System.out.println("123".split(",")[1]);tv.setTvName(tv.getTvName() + "_xxx");tvRepository.save(tv);}@Override@Transactional(propagation = Propagation.REQUIRES_NEW)
//    @Transactional(propagation = Propagation.NOT_SUPPORTED)
//    @Transactional(propagation = Propagation.NEVER)public void deleteById(int id) {tvRepository.deleteById(id);}

情形6

1、先准备一个额外的接口与其实现类。

public interface TVServiceExt {//根据主键 tvId 删除void deleteByIdExt(int id);
}

src/main/java/com/wmx/service/TVServiceExt.java · 汪少棠/jpaTransactional - Gitee.com

import com.wmx.repository.TVRepository;
import com.wmx.service.TVServiceExt;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class TVServiceExtImpl implements TVServiceExt {@Resourceprivate TVRepository tvRepository;@Overridepublic void deleteByIdExt(int id) {tvRepository.deleteById(id);}
}

src/main/java/com/wmx/service/impl/TVServiceExtImpl.java · 汪少棠/jpaTransactional - Gitee.com

2、下面从 TvServiceImpl 的 save 方法内部调用 TvServiceExtImpl 的 deleteByIdExt 方法,如下所示,当调用者与被调用双方都没有加 @Transactional 时,完全无事务管理,删除操作会正确执行,save 不执行。

@Service
public class TVServiceImpl implements TVService {@Resourceprivate TVRepository tvRepository;@Resourceprivate TVServiceExt tvServiceExt;//其余方法省略不写了//org.springframework.transaction.annotation.Transactional:事务管理//@Transactional 添加到哪个方法上,哪个方法就会进行事务管理.默认传播特性为REQUIRED@Overridepublic void save(TV tv) {tvServiceExt.deleteByIdExt(11);//调用其它类中的方法//故意制造一个数组下标越界异常,典型的运行时异常System.out.println("123".split(",")[1]);tv.setTvName(tv.getTvName() + "_xxx");tvRepository.save(tv);}
}

情形7:save 方法上加上 @Transactional 事务管理,deleteByIdExt 方法上没加,结果和预期的一样,因为事务的传播特性,deleteByIdExt 和 save 处于同一个事务,一荣俱荣,一损俱损,所以删除与插入都不会成功。

    //org.springframework.transaction.annotation.Transactional:事务管理//@Transactional 添加到哪个方法上,哪个方法就会进行事务管理.默认传播特性为REQUIRED@Override@Transactionalpublic void save(TV tv) {tvServiceExt.deleteByIdExt(10);//调用其它类中的方法//故意制造一个数组下标越界异常,典型的运行时异常System.out.println("123".split(",")[1]);tv.setTvName(tv.getTvName() + "_xxx");tvRepository.save(tv);}

情形8:TvServiceImpl 的 save 方法内部调用 TvServiceExtImpl 的 deleteByIdExt 方法,save 上加了 @Transactional ,deleteById 上也加了,但是 propagation 等于 requires_new,即自己会新开事务。结果就是 deleteByIdExt 与 save 方法位于两个不同的事务,删除操作会成功,save 会失败。

    //REQUIRES_NEW:表示新开一个事务,如果当前已经有事务,则会暂停@Override@Transactional(propagation = Propagation.REQUIRES_NEW)public void deleteByIdExt(int id) {tvRepository.deleteById(id);}

说到这里应该就已经很清楚了,如果  deleteByIdExt 上面传播特性为 @Transactional(propagation = Propagation.REQUIRED),显然就又会和 save 方法处于同一个事务了。

@Transactional 事务实现机制

1、@Transactional 声明目标方法后,Spring Framework 默认使用 AOP 代理,代码运行时生成一个代理对象,根据 @Transactional 的属性配置,代理对象决定该声明 @Transactional 的目标方法是否由拦截器 TransactionInterceptor 来使用拦截

2、在 TransactionInterceptor 拦截时,会在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑, 最后根据执行情况是否出现异常,利用抽象事务管理器 AbstractPlatformTransactionManager 操作数据源 DataSource 提交或回滚事务。

3、Spring AOP 代理有 CglibAopProxy 和 JdkDynamicAopProxy 两种,以 CglibAopProxy 为例,对于 CglibAopProxy,需要调用其内部类的 DynamicAdvisedInterceptor 的 intercept 方法。对于 JdkDynamicAopProxy,需要调用其 invoke 方法。

4、默认的代理模式下,只有目标方法由外部调用,才能被 Spring 事务拦截器拦截。在同一个类中的两个方法直接调用,是不会被 Spring 的事务拦截器拦截。

rollbackFor 与 noRollbackFor

1、默认配置下 Spring 只会回滚运行时异常(未检查异常)(继承自 RuntimeException 的异常)或者 Error,编译时异常默认不会回滚。

如 @Transactional 标注的方法中抛出异常 throw new RuntimeException("xxx"); 则事务会回滚

如 @Transactional 标注的方法中抛出异常 throw new Exception("xxx"); 则事务会不回滚

2、如果想方法中发生某些编译时异常时也回滚,则可以通过 rollbackFor 属性指定,如 :@Transactional(rollbackFor=Exception.class)、@Transactional(rollbackFor=SQLException.class) 等

3、如果想方法中发生某些运行时异常不回滚,则可以通过 noRollbackFor 属性指定,如:@Transactional(notRollbackFor=RunTimeException.class),@Transactional(notRollbackFor=ClassCastException.class)

4、如果 @Transactional 标注的方法中使用 try{}catch{}捕获处理了异常,则事务不再回滚,如果想让事务回滚,则必须继续往外抛:try{xxx}catch(Exception e){ throw new RuntimeException("xxx",e) }

Java 异常捕获注意事项

Spring Boot @Transactional 配置事务管理相关推荐

  1. spring的annotation-driven配置事务管理器详解

    来源:http://blog.sina.com.cn/s/blog_8f61307b0100ynfb.html 这篇文章是我从ITeye上复制来的,看了一遍,觉得很深刻,决定把他复制来,对原作者表示感 ...

  2. Spring Boot中的事务管理

    什么是事务? 我们在开发企业应用时,对于业务人员的一个操作实际是对数据读写的多步操作的结合.由于数据操作在顺序执行的过程中,任何一步操作都有可能发生异常,异常会导致后续操作无法完成,此时由于业务逻辑并 ...

  3. Spring Boot 声明式事务 @Transactional 的使用

    1.Spring Boot 项目中使用事务 首先使用 @EnableTransactionManagement 注解开启事务支持,然后在需要事务管理的 public 方法上添加注解 @Transact ...

  4. spring配置c3p0连接池、spring的声明式事务管理

    一.spring配置c3p0连接池: 1.导入maven依赖: <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 --> & ...

  5. Spring配置事务管理

    1. spring 注解事务的配置 <!-- 注册配置事务管理器 -->     <bean id="transactionManager" class=&quo ...

  6. (附源码gitHub下载地址)spring boot -jta-atomikos分布式事务

    应用场景:双数据源,就是某些项目会涉及到两个数据源或者两个以上的数据源,这个多数据源的项目一般是数据同步,也就是把数据从另一个系统中,保存到另一个系统,两边的 数据库又不一样,比如一个Mysql.一个 ...

  7. 全面分析 Spring 的编程式事务管理及声明式事务管理(转)

    摘要 Spring 的事务管理是 Spring 框架中一个比较重要的知识点,该知识点本身并不复杂,只是由于其比较灵活,导致初学者很难把握.本教程从基础知识开始,详细分析了 Spring 事务管理的使用 ...

  8. 全面分析 Spring 的编程式事务管理及声明式事务管理--转

    开始之前 关于本教程 本教程将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. 先决条件 本 ...

  9. java元婴期(21)----java进阶(spring(5)---事务管理AOP事务管理(全自动)spring整合Junit)

    事务管理 事务:一组业务操作ABCD,要么全部成功,要么全部不成功. 特性:ACID 原子性:整体 一致性:完成 隔离性:并发 持久性:结果 隔离问题: 脏读:一个事务读到另一个事务没有提交的数据 不 ...

  10. 在Spring中使用JTA事务管理

    在Spring中使用JTA事务管理 Spring 通过AOP技术可以让我们在脱离EJB的情况下享受声明式事务的丰盛大餐,脱离Java EE应用服务器使用声明式事务的道路已经畅通无阻.但是很大部分人都还 ...

最新文章

  1. 不是多家族媒体集的一部分,可用bakup with format来构造新的媒体集.
  2. day8--socketserver
  3. 系统设计说明书案例_案例 | 太阳能+热泵枸杞烘干系统设计及经济性分析
  4. 算法一之简单选择排序
  5. 2018黄河奖设计大赛获奖_宣布我们的freeCodeCamp 2018杰出贡献者奖获奖者
  6. 基因共表达聚类分析及可视化
  7. java 树同构_有根树的同构 和 无根树的同构
  8. 解决 HomeBrew 下载缓慢的问题
  9. Altium Designer封装库的绘制
  10. 如何在MFC界面使用OCX控件
  11. c语言编程实现scp功能,scp源码浅析
  12. windbg 查看结构体_windbg常见命令
  13. 北京航空航天大学经管学院《量化交易与大数据金融》课程实验 :自选至少5支基金,和一个大盘指数,比较这5支基金的信息比率
  14. phyton做九九乘法表
  15. 面向接口编程VS《倚天屠龙记》里张三丰教无忌打太极
  16. 山东移动携手华云数据打造DICT战略合作伙伴生态圈 推动区域数字经济高质量发展
  17. 如何设置HttpClient请求的Content-Type标头?
  18. usb接口驱动_UART串行总线舵机转接板规格、接线说明 amp; 驱动安装
  19. 传智播客最新教学视频,共享给你们了,有需要的戳进来~~~~
  20. ✔G【OPA695】【单运放 】<高速>宽带放大模块 1.4G 高速电流型运放 同相反相带偏移

热门文章

  1. MapServer使用笔记(二)
  2. 数据库连接报错2013-lost connection to mysql server at ‘reading initial communication packet’ system error:0
  3. c++构造函数、析构函数为什么不能取地址
  4. matlab红字怎么删除,matlab-系统爱好者
  5. 拓端tecdat|R语言改进的股票配对交易策略分析SPY-TLT组合和中国股市投资组合
  6. 拓端tecdat|Excel 实例:数据进行排序和筛选
  7. 10 Seconds Count Down
  8. Java Greedy Snake, need to be updated
  9. python机器人开发学校,机器人Python青少年编程开发实例
  10. 神经网络np基本用法