30

200204-SpringBoot系列教程之编程式事务使用姿势介绍篇

 4 years ago
source link: http://spring.hhui.top/spring-blog/2020/02/04/200204-SpringBoot系列教程之编程式事务使用姿势介绍篇/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

前面介绍的几篇事务的博文,主要是利用 @Transactional 注解的声明式使用姿势,其好处在于使用简单,侵入性低,可辨识性高(一看就知道使用了事务);然而缺点也比较明显,不够灵活,稍不注意,可能就因为姿势不对,导致事务不生效

本文将介绍另外一种事务的使用姿势,借助 TransactionTemplate 的编程式事务

I. 配置

本篇主要介绍的是 jdbcTemplate + transactionTemplate 来完成一个编程式事务的实例demo

创建一个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. 初始化

创建几条数据,用于事务操作

@Service
public class ManualDemo {
    @Autowired
    private TransactionTemplate transactionTemplate;
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @PostConstruct
    public void init() {
        String sql = "replace into money (id, name, money) values (220, '初始化', 200)";
        jdbcTemplate.execute(sql);
    }
}

2. 使用case

为了演示事务的特性,我们设计几个简单的sql操作,并抛出异常,引发回滚,如下

  • 在doUpdate方法中,显示更新name,输出更新的结果,然后再更新money的值,最后抛出一个异常,希望事务回滚
private boolean doUpdate(int id) throws Exception {
    if (this.updateName(id)) {
        this.query("after updateMoney name", id);
        if (this.updateMoney(id)) {
            return true;
        }
    }

    throw new Exception("参数异常");
}


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;
}

上面这一端逻辑,如果看了前面几篇博文,会比较熟悉,区别在于doUpdate方法上面没有添加 @Transactional 注解,当下它的调用并不会在事务中执行

接下来我们看一下编程式事务的核心写法

public void testTransaction(int id) {
    transactionTemplate.execute(new TransactionCallback<Boolean>() {
        @Override
        public Boolean doInTransaction(TransactionStatus transactionStatus) {
            try {
                return doUpdate(id);
            } catch (Exception e) {
                transactionStatus.setRollbackOnly();
                return false;
            }
        }
    });
}

如上,将方法的调用,封装在 transactionTemplate.execute 的调用中,通过设置 transactionStatus.setRollbackOnly() 来标记回滚

通过前面几篇博文的学习我们知道实际使用时,事务的隔离级别,传递属性也很重要,在编程式事务中,当然也是可以设置的

// 设置隔离级别
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
// 设置传播属性
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

最后写一个测试代码,验证一下是否生效

@Component
public class TransactionalSample {
    @Autowired
    private ManualDemo manualDemo;
    
    public void testManualCase() {
        System.out.println("======= 编程式事务 start ========== ");
        manualDemo.query("transaction before", 220);
        manualDemo.testTransaction(220);
        manualDemo.query("transaction end", 220);
        System.out.println("======= 编程式事务 end ========== ");
    }
}

输出结果如下,最终数据big没有被修改

======= 编程式事务 start ========== 
transaction before >>>> {id=220, name=初始化, money=200, is_deleted=false, create_at=2020-02-03 13:52:39.0, update_at=2020-02-03 13:52:39.0}
after updateMoney name >>>> {id=220, name=更新, money=200, is_deleted=false, create_at=2020-02-03 13:52:39.0, update_at=2020-02-03 13:52:39.0}
transaction end >>>> {id=220, name=初始化, money=200, is_deleted=false, create_at=2020-02-03 13:52:39.0, update_at=2020-02-03 13:52:39.0}
======= 编程式事务 end ==========

III. 其他

0. 系列博文&源码

系列博文

源码

1. 一灰灰Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

ri6Vj2M.png!web

打赏 如果觉得我的文章对您有帮助,请随意打赏。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK