SpringBoot2 | 第十一篇:开启声明式事务

​ SpringBoot 开启事务很简单,只需要一个注解@Transactional 就可以了。因为在 SpringBoot 中已经默认对jpa、jdbc、mybatis 开启了事务,引入它们依赖的时候,事务就默认开启。当然,如果你需要用其他的 orm,比如 beatlsql,就需要自己配置相关的事物管理器。

注意:Spring Boot 2.0使用data Jpa时。创建表默认使用的是 MyISAM引擎,MyISAM引擎是不支持事务的。所以把数据库的表改为 InnoDB 引擎就行了。可以在 全局配置文件 中指定引擎

准备阶段

以第八篇文章的代码为例子,即 springboot 整合 mybatis ,第八篇文章是基于注解来实现 mybatis 的数据访问层,这篇文章基于 xml 的来实现,并开启声明式事务。

环境/版本一览:

  • 开发工具:Intellij IDEA 2018.2.2
  • springboot: 2.0.5.RELEASE
  • jdk:1.8.0_171
  • maven:3.3.9

1、pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Core -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

2、application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server:
port: 8080

spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
# 基本属性 allowMultiQueries:设置为true后,数据库那边才允许你批量更新。编码属性设置了存储数据到数据库才不会是乱码
url: jdbc:mysql://localhost:3306/chapter11?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=UTC&useSSL=true
username: root
password: 123456

mybatis:
mapper-locations: classpath:mapper/*.xml # 注意:一定要对应 mapper 映射xml文件的所在路径
type-aliases-package: com.fatal.entity # 注意:对应实体类的路径
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 可以自控制台上输出 sql 语句

3、sql

1
2
3
4
5
6
7
8
9
10
11
-- create table `account`
# DROP TABLE `account` IF EXISTS
CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
`money` double DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
INSERT INTO `account` VALUES ('1', 'aaa', '100');
INSERT INTO `account` VALUES ('2', 'bbb', '100');
INSERT INTO `account` VALUES ('3', 'ccc', '100');

4、resources

在 resources 下新建mapper 文件夹,在 mapper 下新建UserMapper.xml

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.fatal.mapper.AccountMapper">
<update id="update">
UPDATE account
SET money = #{money}
WHERE id = #{id}
</update>
</mapper>

5、entity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.fatal.entity;

import lombok.Data;

/**
* @author: Fatal
* @date: 2018/8/19 0019 20:59
*/
@Data
public class Account {

private Integer id;
private String name;
private Double money;

}

6、mapper

AccountMapper接口

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.fatal.mapper;

import org.apache.ibatis.annotations.Param;

/**
* @author: Fatal
* @date: 2018/8/19 0019 21:02
*/
public interface AccountMapper {

Integer update(@Param("money") Double money, @Param("id") Integer id);

}

7、service

IAccountService

1
2
3
4
5
6
7
8
9
10
11
12
package com.fatal.service;

/**
* Account 服务
* @author: Fatal
* @date: 2018/8/19 0019 21:05
*/
public interface IAccountService {

void update();

}

AccountServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.fatal.service.impl;

import com.fatal.mapper.AccountMapper;
import com.fatal.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
* Account 服务实现类
* @author: Fatal
* @date: 2018/8/19 0019 21:06
*/
@Service
public class AccountServiceImpl implements IAccountService {

@Autowired
private AccountMapper accountMapper; // 这里会报错,但不影响正常使用

@Override
@Transactional
public void update() {
accountMapper.update(90d, 1);//用户1减10块 用户2加10块
Integer i = 1 / 0;
accountMapper.update(110d, 2);
}

}

8、Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.fatal.service.impl;

import com.fatal.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
* @author: Fatal
* @date: 2018/8/19 0019 21:08
*/
@SpringBootTest
@RunWith(SpringRunner.class)
public class IAccountServiceImplTest {

@Autowired
private IAccountService IAccountService;

@Test
public void update() {
IAccountService.update();
}
}

@Transactional,声明事务,并设计一个转账方法,用户1减10块,用户2加10块。在用户1减10 ,之后,抛出异常,即用户2加10块钱不能执行,当加注解@Transactional之后,两个人的钱都没有增减。当不加@Transactional,用户1减了10,用户2没有增加,即没有操作用户2 的数据。可见@Transactional注解开启了事务。

笔记

2018/11/5

今天,是我测试 **@Async 异步事务**的第三天了。在这次测试中最大的收获就是解决了异步方法抛异常,事务方法和它调用的异步方法两个事务都回滚的问题。其中有个知识点很重要,就是 @Transactional 标注的方法中,如何手动回滚事务。

解决方法:一句即可 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); (一般手动回滚会放在 catch 代码块中来用。)

总结

SpringBoot 开启事务 很简单,只需要加一行注解就可以了,前提你用的是 jdbctemplate, jpa, mybatis,这种常见的orm。

SpringBoot的知识已经有前辈在我们之前探索了。比较喜欢的博主有:唐亚峰 | Battcn方志朋的专栏程序猿DD纯洁的微笑。对这门技术感兴趣的可以去他们的博客逛逛。谢谢他们的分享~~

以上文章是我用来学习的Demo,都是基于 SpringBoot2.x 版本。

源码地址: https://github.com/ynfatal/springboot2-learning/tree/master/chapter11

学习 方志朋 前辈的经验