对于mysql而言,关于事务的主要知识点可能几种在隔离级别上;在Spring体系中,使用事务的时候,还有一个知识点事务的传递属性同样重要,本文将主要介绍7中传递属性的使用场景
I. 配置本文的case,将使用声明式事务,首先我们创建一个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相关的信息
## 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. 使用说明 0. 准备在正式开始之前,得先准备一些基础数据
@Component public class PropagationDemo { @Autowired private JdbcTemplate jdbcTemplate; @PostConstruct public void init() { String sql = "replace into money (id, name, money) values (420, '初始化', 200)," + "(430, '初始化', 200)," + "(440, '初始化', 200)," + "(450, '初始化', 200)," + "(460, '初始化', 200)," + "(470, '初始化', 200)," + "(480, '初始化', 200)," + "(490, '初始化', 200)"; jdbcTemplate.execute(sql); } }其次测试事务的使用,我们需要额外创建一个测试类,后面的测试case都放在类PropagationSample中; 为了使输出结果更加友好,提供了一个封装的call方法
@Component public class PropagationSample { @Autowired private PropagationDemo propagationDemo; private void call(String tag, int id, CallFunc<Integer> func) { System.out.println("============ " + tag + " start ========== "); propagationDemo.query(tag, id); try { func.apply(id); } catch (Exception e) { System.out.println(e.getMessage()); } propagationDemo.query(tag, id); System.out.println("============ " + tag + " end ========== \n"); } @FunctionalInterface public interface CallFunc<T> { void apply(T t) throws Exception; } } 1. REQUIRED也是默认的传递属性,其特点在于
如果存在一个事务,则在当前事务中运行
如果没有事务则开启一个新的事务
使用方式也比较简单,不设置@Transactional注解的propagation属性,或者设置为 REQUIRED即可
/** * 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务 * * @param id */ @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void required(int id) throws Exception { if (this.updateName(id)) { this.query("required: after updateMoney name", id); if (this.updateMoney(id)) { return; } } throw new Exception("事务回滚!!!"); }上面就是一个基础的使用姿势
private void testRequired() { int id = 420; call("Required事务运行", id, propagationDemo::required); }输出结果如下
============ Required事务运行 start ========== Required事务运行 >>>> {id=420, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0} required: after updateMoney name >>>> {id=420, name=更新, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0} 事务回滚!!! Required事务运行 >>>> {id=420, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0} ============ Required事务运行 end ========== 2. SUPPORTS其特点是在事务里面,就事务执行;否则就非事务执行,即
如果存在一个事务,支持当前事务
如果没有事务,则非事务的执行
使用姿势和前面基本一致
@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class) public void support(int id) throws Exception { if (this.updateName(id)) { this.query("support: after updateMoney name", id); if (this.updateMoney(id)) { return; } } throw new Exception("事务回滚!!!"); }这个传递属性比较特别,所以我们的测试case需要两个,一个事务调用,一个非事务调用