# 准备工作
## 1、数据库
```
CREATE TABLE `money` (
`id` bigint(20) NOT NULL,
`name` varchar(50) CHARACTER SET latin1 COLLATE latin1_swedish_ci DEFAULT NULL COMMENT '姓名',
`money` int(11) DEFAULT NULL COMMENT '金额',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Compact;
INSERT INTO `money` VALUES (1, 'gongj', 900);
INSERT INTO `money` VALUES (2, 'yuanj', 1000);
```
### 2、编码
pom
```
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
```
entity
```
//生成getter,setter等函数
@Data
//生成全参数构造函数
@AllArgsConstructor
//生成无参构造函数
@NoArgsConstructor
public class Money {
private Long id;
private String name;
private Integer money;
}
```
Mapper
```
public interface MoneyMapper extends BaseMapper<Money> {
/**
* 转出
*/
@Update("update money set money = money - #{money} where name =#{name}")
public void rollOut(@Param("name") String name, @Param("money") Integer money);
/**
* 转入
*/
@Update("update money set money = money + #{money} where name =#{toName}")
public void transferInto(@Param("toName") String name, @Param("money") Integer money);
}
```
Service
```
@Service
public class MoneyService {
@Autowired
MoneyMapper mapper;
/**
* 没有事务的转账
* @param name
* @param toName
* @param money
*/
public void transfer(String name,String toName,Integer money){
mapper.rollOut(name,money);
int x = 5/0;
mapper.transferInto(toName,money);
}
/**
* 有事务的转账
* @param name
* @param toName
* @param money
*/
@Transactional
public void transfer1(String name,String toName,Integer money){
mapper.rollOut(name,money);
int x = 5/0;
mapper.transferInto(toName,money);
}
}
```
测试
```
@SpringBootTest
public class MoneyTest {
@Autowired
MoneyService moneyService;
@Test
public void test1(){
moneyService.transfer("gongj","yuanj",100);
}
}
```


可以看到gongj的账户余额减少,但yuanj的账户余额并没有增加。
手动将数据库gongj的余额修改为1000,再次测试
```
@Test
public void test2(){
//有事务的转账
moneyService.transfer1("gongj","yuanj",100);
}
```


可以看到事务已经生效了。
### 传播特性(Propagation )
```
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
}
```



## 测试
### 1、不要事务
#### 1.1 、NEVER
以非事务方式执行,如果当前存在事务,则抛出异常。
```
@Autowired
MoneyService moneyService;
/**==================================*/
@Transactional
public void gongj(String name,String toName,Integer money){
mapper.rollOut(name,money);
//使用this去调用方法,aop不会进行拦截
//this.yuanj(toName,money);
try{
//在本类中注入自己
moneyService.yuanj(toName,money);
}catch (Exception e) {
e.printStackTrace();
}
int i= 5;
if(i == 5){
throw new RuntimeException("发生了异常");
}
}
//以非事务方式执行,如果当前存在事务,则抛出异常。
@Transactional(propagation = Propagation.NEVER)
public void yuanj(String toName,Integer money){
mapper.transferInto(toName,money);
int i= 5;
if(i == 5){
throw new RuntimeException("yuanj收钱发生了异常");
}
}
```
Test
```
@Test
public void test3(){
moneyService.gongj("gongj","yuanj",100);
}
```

调用yuanj方法就会报错。
#### 1.2 、NOT_SUPPORTED
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
```
@Transactional
public void gongj2(String name,String toName,Integer money){
mapper.rollOut(name,money);
//使用this去调用方法,aop不会进行拦截
//this.yuanj(toName,money);
try{
moneyService.yuanj2(toName,money);
}catch (Exception e) {
e.printStackTrace();
}
int i= 5;
if(i == 5){
throw new RuntimeException("发生了异常");
}
}
// 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void yuanj2(String toName,Integer money){
mapper.transferInto(toName,money);
int i= 5;
if(i == 5){
throw new RuntimeException("yuanj收钱发生了异常");
}
}
```

lock wait timeout exceeded; try restarting transaction:超过了锁定等待超时; 尝试重新启动事务。
这是由于操作的是同一个表,造成死锁。
怎么去解决,操作不是同一个表就行了。将money表copy一份。

MoneyCopyMapper
```
public interface MoneyCopyMapper {
/**
* 转出
*/
@Update("update money set money = money - #{money} where name =#{name}")
public void rollOut(@Param("name") String name, @Param("money") Integer money);
/**
* 转入
*/
@Update("update money_copy1 set money = money + #{money} where name =#{toName}")
public void transferInto(@Param("toName") String name, @Param("money") Integer money);
}
```
rollOut方法操作money表,transferInto方法操作money_copy1 表。
```
@Autowired
MoneyCopyMapper moneyCopyMapper;
@Transactional
public void gongj2(String name,String toName,Integer money){
//使用moneyCopyMapper的rollOut操作money表
moneyCopyMapper.rollOut(name,money);
//使用this去调用方法,aop不会进行拦截
//this.yuanj(toName,money);
try{
moneyService.yuanj2(toName,money);
}catch (Exception e) {
e.printStackTrace();
}
int i= 5;
if(i == 5){
throw new RuntimeException("发生了异常");
}
}
// 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void yuanj2(String toName,Integer money){
//使用moneyCopyMapper的transferInto操作money_copy1表
moneyCopyMapper.transferInto(toName,money);
int i= 5;
if(i == 5){
throw new RuntimeException("yuanj收钱发生了异常");
}
}
```


可以看到money表中gongj用户的金额并没有减少,因为它发生异常进行了回滚。而money_copy1的yuanj用户的金额增加了,即使发生了异常也没有进行回滚。说明没有事务。
结论:
NEVER和NOT_SUPPORTED两种传播特性是不会有事务的。
至于这里为什么需要创建一张表去演示NOT_SUPPORTED这种级别的传播特性呢?
是因为money表没有通过`主键`去更新数据导致锁表了。进行了锁升级。
推荐:[https://msd.misuland.com/pd/3053059875815820666](https://msd.misuland.com/pd/3053059875815820666)
### 2、可有可无
##### 2.1、SUPPORTS
如果存在一个事务,就使用当前事务。如果没有事务,则非事务的执行。
有事务执行
```
@Transactional
public void gongj3(String name,String toName,Integer money){
mapper.rollOut(name,money);
//使用this去调用方法,aop不会进行拦截
//this.yuanj(toName,money);
try{
moneyService.yuanj3(toName,money);
}catch (Exception e) {
e.printStackTrace();
}
int i= 5;
if(i == 5){
throw new RuntimeException("发生了异常");
}
}
// 如果存在一个事务,就使用当前事务。如果没有事务,则非事务的执行。
@Transactional(propagation = Propagation.SUPPORTS)
public void yuanj3(String toName,Integer money){
mapper.transferInto(toName,money);
int i= 5;
if(i == 5){
throw new RuntimeException("yuanj收钱发生了异常");
}
}
```
Test
```
@Test
public void test5(){
moneyService.gongj3("gongj","yuanj",100);
}
```

发生了事务回滚。
没事务执行,将gongj3方法的 @Transactional注释。

事务失效。
### 3、必须有事务
##### 3.1、 ROPAGATION_REQUIRED
如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是默认的事务类型。
该事务分为三种情况:
1、gongj4方法没有事务,两个方法都发生异常。结论:gongj4方法不会回滚事务,yuanj4方法会回滚。
2、gongj4方法有事务,没有发生异常,yuanj4发生异常。结论:gongj4方法与yuanj4方法都会进行回滚操作。
2、gongj4方法有事务,发生异常,yuanj4没有发生异常。结论:gongj4方法与yuanj4方法都会进行回滚操作。
##### 3.2、PROPAGATION_MANDATORY
如果存在一个事务,就使用当前事务。如果当前没有事务,就抛出异常。
该事务分为两种情况:
1、如果gongj4没有事务,会抛出异常。

2、如果gongj4有事务,yuanj4就是用gongj4的事务。
##### 3.3、PROPAGATION_REQUIRES_NEW
如果没有事务,就新建一个事务,如果当前存在事务,把当前事务挂起。
该事务分为四种情况:
1、gongj4方法没有事务,两个方法都发生异常。结论:gongj4方法不会回滚事务,yuanj4方法会回滚。
2、gongj4方法有事务,发生了异常,yuanj4没有发生异常。结论:gongj4方法发生异常回滚事务。
3、gongj4方法有事务,没有发生异常,yuanj4发生异常。结论:gongj4方法不会回滚事务,yuanj4会将gongj4的事务挂起,yuanj4方法发生异常会回滚。
3、gongj4方法有事务,发生异常,yuanj4也发生异常。结论:gongj4方法回滚事务,yuanj4会将gongj4的事务挂起,yuanj4方法发生异常会回滚。
```
//@Transactional
public void gongj4(String name,String toName,Integer money){
//使用moneyCopyMapper的rollOut操作money表
moneyCopyMapper.rollOut(name,money);
//使用this去调用方法,aop不会进行拦截
//this.yuanj(toName,money);
try{
moneyService.yuanj4(toName,money);
}catch (Exception e) {
e.printStackTrace();
}
int i= 5;
if(i == 5){
throw new RuntimeException("发生了异常");
}
}
// 如果没有事务,就新建一个事务,如果当前存在事务,把当前事务挂起。
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void yuanj4(String toName,Integer money){
//使用moneyCopyMapper的transferInto操作money_copy1表
moneyCopyMapper.transferInto(toName,money);
int i= 5;
if(i == 5){
throw new RuntimeException("yuanj收钱发生了异常");
}
}
```


这里只展示第一种情况。
#####3.4、PROPAGATION_NESTED
如果当前存在事务,就在当前事务中嵌套其他事务。如果当前没有事务,就新建一个事务。
该事务分为三种情况:
1、gongj4方法没有事务,两个方法都发生异常。结论:gongj4方法不会回滚事务,yuanj4方法会回滚。
2、gongj4方法有事务,发生了异常,yuanj4没有发生异常。结论:gongj4方法发生异常回滚事务并也会将yuanj4方法也进行回滚。
3、gongj4方法有事务,没有发生异常,yuanj4发生异常。结论:gongj4方法不会回滚事务,yuanj4方法发生异常会回滚。

spring事务传播特性