内容简介:当我们希望一组操作,要么都成功,要么都失败时,往往会考虑里利用事务来实现这一点;之前介绍的db操作,主要在于单表的CURD,本文将引入声明式事务本篇主要介绍的是创建一个SpringBoot项目,版本为
当我们希望一组操作,要么都成功,要么都失败时,往往会考虑里利用事务来实现这一点;之前介绍的db操作,主要在于单表的CURD,本文将引入声明式事务 @Transactional
的使用姿势
I. 配置
本篇主要介绍的是 jdbcTemplate
配合事务注解 @Transactional
的使用姿势,至于JPA,mybatis在实际的使用区别上,并不大,后面会单独说明
创建一个SpringBoot项目,版本为 2.2.1.RELEASE
,使用 mysql 作为目标数据库,存储引擎选择 Innodb
,事务隔离级别为RR
1. 项目配置
在项目 pom.xml
文件中,加上 spring-boot-starter-jdbc
,会注入一个 DataSourceTransactionManager
的bean,提供了事务支持
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> ``` ### 2. 数据库配置 进入spring配置文件`application.properties`,设置一下db相关的信息 ```properties ## DataSource spring.datasource.url=jdbc:mysql://127.0.0.1:3306/story?useUnicode=true&characterEncoding=UTF-8&useSSL=false spring.datasource.username=root spring.datasource.password=
3. 数据库
新建一个简单的表结构,用于测试
CREATE TABLE `money` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL DEFAULT '' COMMENT '用户名', `money` int(26) NOT NULL DEFAULT '0' COMMENT '钱', `is_deleted` tinyint(1) NOT NULL DEFAULT '0', `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `name` (`name`) ) ENGINE=InnoDB AUTO_INCREMENT=551 DEFAULT CHARSET=utf8mb4;
II. 使用说明
1. 初始化
为了体现事务的特点,在不考虑DDL的场景下,DML中的增加,删除or修改属于不可缺少的语句了,所以我们需要先初始化几个用于测试的数据
@Service public class SimpleDemo { @Autowired private JdbcTemplate jdbcTemplate; @PostConstruct public void init() { String sql = "replace into money (id, name, money) values (120, '初始化', 200)," + "(130, '初始化', 200)," + "(140, '初始化', 200)," + "(150, '初始化', 200)"; jdbcTemplate.execute(sql); } }
我们使用 replace into
语句来初始化数据,每次bean创建之后都会执行,确保每次执行后面你的操作时,初始数据都一样
2. transactional
这个注解可以放在类上,也可以放在方法上;如果是标注在类上,则这个类的所有公共方法,都支持事务;
如果类和方法上都有,则方法上的注解相关配置,覆盖类上的注解
下面是一个简单的事务测试case
private boolean updateName(int id) { String sql = "update money set `name`='更新' where id=" + id; jdbcTemplate.execute(sql); return true; } public void query(String tag, int id) { String sql = "select * from money where id=" + id; Map map = jdbcTemplate.queryForMap(sql); System.out.println(tag + " >>>> " + map); } private boolean updateMoney(int id) { String sql = "update money set `money`= `money` + 10 where id=" + id; jdbcTemplate.execute(sql); return false; } /** * 运行异常导致回滚 * * @return */ @Transactional public boolean testRuntimeExceptionTrans(int id) { if (this.updateName(id)) { this.query("after updateMoney name", id); if (this.updateMoney(id)) { return true; } } throw new RuntimeException("更新失败,回滚!"); }
在我们需要开启事务的公共方法上添加注解 @Transactional
,表明这个方法的正确调用姿势下,如果方法内部执行抛出运行异常,会出现事务回滚
注意上面的说法,正确的调用姿势,事务才会生效;换而言之,某些case下,不会生效
3. 测试
接下来,测试一下上面的方法事务是否生效,我们新建一个Bean
@Component public class TransactionalSample { @Autowired private SimpleDemo simpleService; public void testSimpleCase() { System.out.println("============ 事务正常工作 start ========== "); simpleService.query("transaction before", 130); try { // 事务可以正常工作 simpleService.testRuntimeExceptionTrans(130); } catch (Exception e) { } simpleService.query("transaction end", 130); System.out.println("============ 事务正常工作 end ========== \n"); } }
在上面的调用中,打印了修改之前的数据和修改之后的数据,如果事务正常工作,那么这两次输出应该是一致的
实际输出结果如下,验证了事务生效,中间的修改name的操作被回滚了
============ 事务正常工作 start ========== transaction before >>>> {id=130, name=初始化, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:21.0} after updateMoney name >>>> {id=130, name=更新, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:22.0} transaction end >>>> {id=130, name=初始化, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:21.0} ============ 事务正常工作 end ==========
4. 注意事项
a. 适用场景
在使用注解 @Transactional
声明式事务时,其主要是借助AOP,通过代理来封装事务的逻辑,所以aop不生效的场景,也适用于这个事务注解不生效的场景
简单来讲,下面几种case,注解不生效
- private方法上装饰
@Transactional
,不生效 - 内部调用,不生效
- 举例如: 外部调用服务A的普通方法m,而这个方法m,调用本类中的声明有事务注解的方法m2, 正常场景下,事务不生效
b. 异常类型
此外,注解 @Transactional
默认只针对运行时异常生效,如下面这种case,虽然是抛出了异常,但是并不会生效
@Transactional public boolean testNormalException(int id) throws Exception { if (this.updateName(id)) { this.query("after updateMoney name", id); if (this.updateMoney(id)) { return true; } } throw new Exception("声明异常"); }
如果需要它生效,可以借助 rollbackFor
属性来指明,触发回滚的异常类型
@Transactional(rollbackFor = Exception.class) public boolean testSpecialException(int id) throws Exception { if (this.updateName(id)) { this.query("after updateMoney name", id); if (this.updateMoney(id)) { return true; } } throw new IllegalArgumentException("参数异常"); }
测试一下上面的两种case
public void testSimpleCase() { System.out.println("============ 事务不生效 start ========== "); simpleService.query("transaction before", 140); try { // 因为抛出的是非运行异常,不会回滚 simpleService.testNormalException(140); } catch (Exception e) { } simpleService.query("transaction end", 140); System.out.println("============ 事务不生效 end ========== \n"); System.out.println("============ 事务生效 start ========== "); simpleService.query("transaction before", 150); try { // 注解中,指定所有异常都回滚 simpleService.testSpecialException(150); } catch (Exception e) { } simpleService.query("transaction end", 150); System.out.println("============ 事务生效 end ========== \n"); }
输出结果如下,正好验证了上面提出的内容
============ 事务不生效 start ========== transaction before >>>> {id=140, name=初始化, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:21.0} after updateMoney name >>>> {id=140, name=更新, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:22.0} transaction end >>>> {id=140, name=更新, money=210, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:22.0} ============ 事务不生效 end ========== ============ 事务生效 start ========== transaction before >>>> {id=150, name=初始化, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:21.0} after updateMoney name >>>> {id=150, name=更新, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:22.0} transaction end >>>> {id=150, name=初始化, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:21.0} ============ 事务生效 end ==========
c. @Transactional 注解的属性信息
上面的内容,都属于比较基本的知识点,足以满足我们一般的业务需求,如果需要进阶的话,有必要了解一下属性信息
以下内容来自: 透彻的掌握 Spring 中@transactional 的使用
属性名 | 说明 |
---|---|
name | 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。 |
propagation | 事务的传播行为,默认值为 REQUIRED。 |
isolation | 事务的隔离度,默认值采用 DEFAULT。 |
timeout | 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。 |
read-only | 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。 |
rollback-for | 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。 |
no-rollback- for | 抛出 no-rollback-for 指定的异常类型,不回滚事务。 |
关于上面几个属性的使用实例,以及哪些情况下,会导致声明式事务不生效,会新开坑进行说明,敬请期待。。。
II. 其他
0. 系列博文&源码
系列博文
- 180926-SpringBoot高级篇DB之基本使用
- 190407-SpringBoot高级篇JdbcTemplate之数据插入使用姿势详解
- 190412-SpringBoot高级篇JdbcTemplate之数据查询上篇
- 190417-SpringBoot高级篇JdbcTemplate之数据查询下篇
- 190418-SpringBoot高级篇JdbcTemplate之数据更新与删除
源码
- 工程: https://github.com/liuyueyi/spring-boot-demo
- 实例源码: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/101-jdbctemplate-transaction
1. 一灰灰Blog
尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
- 一灰灰Blog个人博客 https://blog.hhui.top
- 一灰灰Blog-Spring专题博客 http://spring.hhui.top
打赏 如果觉得我的文章对您有帮助,请随意打赏。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 200202-SpringBoot系列教程之事务传递属性
- Spring Boot 系列教程之事务传递属性
- Spring Boot 系列教程之声明式事务 Transactional
- Spring Boot 系列教程之声明式事务 Transactional
- Spring Boot 2.x基础教程:事务管理入门
- 200203-SpringBoot系列教程之事务不生效的几种case
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
数据结构及应用算法教程
2011-5 / 45.00元
《数据结构及应用算法教程(修订版)》从数据类型的角度,分别讨论了四大类型的数据结构的逻辑特性、存储表示及其应用。此外,还专辟一章,以若干实例阐述以抽象数据类型为中心的程序设计方法。书中每一章后都配有适量的习题,以供读者复习提高之用。第1~9章还专门设有“解题指导与示例”一节内容,不仅给出答案,对大部分题目提供了详尽的解答注释;其中的一些算法题还给出了多种解法。书中主要算法和最后一章的实例中的全部程......一起来看看 《数据结构及应用算法教程》 这本书的介绍吧!