关于spring事务你需要知道的知识点
一、介绍
大家都知道,在SpringBoot
中,使用事务只需要添加@Transactional
就可以添加事务,很是方便。
那么它到底是怎么工作的呢?
这么说有点晕头晕脑的,那来简单看下
二、事务失效的场景
1)事务需要代理类启动
基本的配置我就贴出来了,就一个连接数据库的配置有啥好看的,数据库表也是一样
那么接下来,先来一个UserDao
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.banmoon.test.dao;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional;
@Repository public class UserDao {
@Autowired private JdbcTemplate jdbcTemplate;
@Transactional public void insert(){ jdbcTemplate.execute("INSERT INTO `user`(`name`) VALUES ('半月无霜')"); throw new NullPointerException(); }
@Transactional public void insert2(){ jdbcTemplate.execute("INSERT INTO `user`(`name`) VALUES ('半月')"); }
}
|
再来一个UserService
,并在14行打上断点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.banmoon.test.service;
import com.banmoon.test.dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;
@Service public class UserService {
@Autowired private UserDao userDao;
public void insert(){ userDao.insert(); }
}
|
写段测试,调用一下这个插入方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package com.banmoon.test;
import com.banmoon.test.service.UserService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest class ServiceTest {
@Autowired private UserService userService;
@Test void insertTest() { userService.insert(); }
}
|
debug
启动后,我们发现这个userDao
是CGlib
创建了一个代理类
放行,可以发现报错,继续看看事务会不会回滚
代码中抛出的空指针异常就是我们写的那段,说明插入语句已经执行了。但看到数据库中,没有自己执行插入的数据,那证明了事务确实生效了。
那如果没有这个代理类,真的事务就不会生效吗?把测试代码改一下,不再依赖spring
注入,我们自己来创建实例对象。
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.banmoon.test.dao;
import lombok.Setter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional;
@Setter @Repository public class UserDao {
@Autowired private JdbcTemplate jdbcTemplate;
@Transactional public void insert(){ jdbcTemplate.execute("INSERT INTO `user`(`name`) VALUES ('半月无霜')"); throw new NullPointerException(); }
@Transactional public void insert2(){ jdbcTemplate.execute("INSERT INTO `user`(`name`) VALUES ('半月')"); }
}
|
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
| package com.banmoon.test;
import com.banmoon.test.dao.UserDao; import com.banmoon.test.service.UserService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.jdbc.core.JdbcTemplate;
@SpringBootTest class ServiceTest {
@Autowired private UserService userService;
@Autowired private JdbcTemplate jdbcTemplate;
@Test void insertTest() { UserDao userDao = new UserDao(); userDao.setJdbcTemplate(jdbcTemplate); userDao.insert(); }
}
|
查看结果,依然报错,这是肯定的。但数据库,表里却已经有了数据。事务失效了,这一次的userDao
是自己创建的实例,而不是动态代理类。
动态代理类,在使用@Transactional
的方法时,前置通知开启事务,后置通知决定是提交还是回滚。
2)修饰在非public方法上时
如果@Transactional
修饰在非public修饰的方法上,事务将会失效。
这是因为CGlib
在创建代理类时,它会查找目标方法是否是public
主要类在此:org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource
1 2 3 4 5 6 7 8 9
| @Nullable protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) { if (this.allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { return null; } else { } }
|
这是个抽象类,allowPublicMethodsOnly
方法由它的子类来实现,本身默认的实现是返回false
。
在使用@Transactional
时,使用到public
的方法上。所以这也是我不建议将注解使用在类上的原因,你以为类中的方法都有事务了,但实际不然。
3)在同一个类中调用方法
还是简单的代码,讲的是同一个类中调用方法,这个方法有@Transactional
。
这里修改一下UserDao
这个类,使得insert
方法去调用insert2
方法,查看事务是否生效
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
| package com.banmoon.test.dao;
import lombok.Setter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional;
@Setter @Repository public class UserDao {
@Autowired private JdbcTemplate jdbcTemplate;
public void insert(){ insert2(); }
@Transactional public void insert2(){ jdbcTemplate.execute("INSERT INTO `user`(`name`) VALUES ('半月')"); throw new NullPointerException(); }
}
|
测试代码启动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package com.banmoon.test;
import com.banmoon.test.dao.UserDao; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest class ServiceTest {
@Autowired private UserDao userDao;
@Test void insertTest() { userDao.insert(); }
}
|
查看结果,报错是肯定的,那么数据呢?
插入成功,怎么回事?
原因估计还是出在代理类上,和第一条不同的是,代理类虽然有代理类,但insert
方法直接调用了insert2
方法,这个是目标类中自己调用的,所以事务没有生效。
4)注解属性 propagation 设置错误
如使用了SUPPORTS
、NOT_SUPPORTED
传播机制,这些传播机制讲的是不再创建事务。
详见第三章
5)注解属性rollbackFor设置错误
rollbackFor:这个注解属性的作用是可以指定能够触发事务回滚的异常类型。
而在默认情况下,spring
只会对非检查的异常做回滚。
所以在抛出其他类型的异常时,我们得指定rollbackFor
属性。一旦有它和它的子类异常被抛出,事务也能够生效
6)异常被自己捕获
事务判断是否提交,还是回滚,就是根据是否捕获到非检查异常。
所以,如果在程序中自己try...catch...
捕获掉,那么事务将会当做成功执行,将事务提交。
自己写了try...catch...
的话,要么自己手动提交回滚,要么就是再抛出一个非检查异常。
三、事务的传播机制
事务的传播机制,简单的来说,就是一个方法,调用另一个方法。原本就有的事务,在遇到一个新的事务后会发生什么机制。这就是我们要讲得事务传播机制。
通过@Transactional
的propagation
属性我们可以进行配置事务的传播机制,我们先看看这个枚举里面都有些什么
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package org.springframework.transaction.annotation;
public enum Propagation { REQUIRED(0), SUPPORTS(1), MANDATORY(2), REQUIRES_NEW(3), NOT_SUPPORTED(4), NEVER(5), NESTED(6);
private final int value;
private Propagation(int value) { this.value = value; }
public int value() { return this.value; } }
|
简简单单,没有说明,一个一个来看吧。先写出两个Dao
,一会测试用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package com.banmoon.test.dao;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository;
@Repository public class UserDao {
@Autowired private JdbcTemplate jdbcTemplate;
@Transactional public void insert(){ jdbcTemplate.execute("INSERT INTO `user`(`name`) VALUES ('半月')"); }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package com.banmoon.test.dao;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository;
@Repository public class PlatformDao {
@Autowired private JdbcTemplate jdbcTemplate;
@Transactional public void insert(){ jdbcTemplate.execute("INSERT INTO `platform`(`name`) VALUES ('博客平台')"); }
}
|
1)REQUIRED(默认)
这是spring
默认的事务传播机制。
我们写一个Service
去调用上面两个Dao
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.banmoon.test.service;
import com.banmoon.test.dao.PlatformDao; import com.banmoon.test.dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;
@Service public class UserService {
@Autowired private UserDao userDao;
@Autowired private PlatformDao platformDao;
public void insert(){ userDao.insert(); platformDao.insert(); }
}
|
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 29 30
| package com.banmoon.test.service;
import com.banmoon.test.dao.PlatformDao; import com.banmoon.test.dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;
@Service public class UserService {
@Autowired private UserDao userDao;
@Autowired private PlatformDao platformDao;
@Transactional public void insert(){ userDao.insert(); platformDao.insert(); }
}
|
2)SUPPORTS
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 29 30 31 32 33 34 35 36 37 38 39 40
| package com.banmoon.test.service;
import com.banmoon.test.dao.PlatformDao; import com.banmoon.test.dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;
@Service public class UserService {
@Autowired private UserDao userDao;
@Autowired private PlatformDao platformDao;
@Transactional public void insert(){ userDao.insert(); platformDao.insert(); }
public void insert(){ userDao.insert(); platformDao.insert(); }
}
|
3)MANDATORY
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 29 30 31 32 33 34 35 36 37 38 39 40
| package com.banmoon.test.service;
import com.banmoon.test.dao.PlatformDao; import com.banmoon.test.dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;
@Service public class UserService {
@Autowired private UserDao userDao;
@Autowired private PlatformDao platformDao;
@Transactional public void insert(){ userDao.insert(); platformDao.insert(); }
public void insert(){ userDao.insert(); platformDao.insert(); }
}
|
来看看异常吧
4)REQUIRES_NEW
-
如果外部方法已经有事务了,重新创建一个新的事务
-
如果外部方法没有事务,重新创建一个新的事务
这也就是说,无论外部有无事务,内部方法都将会创建新的事务
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| package com.banmoon.test.service;
import com.banmoon.test.dao.PlatformDao; import com.banmoon.test.dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;
@Service public class UserService {
@Autowired private UserDao userDao;
@Autowired private PlatformDao platformDao;
@Transactional public void insert(){ userDao.insert(); platformDao.insert(); }
public void insert(){ userDao.insert(); platformDao.insert(); }
}
|
5)NOT_SUPPORTED
-
如果外部方法已经有事务了,以非事务的方式运行
-
如果外部方法没有事务,以非事务的方式运行
也就是说,无论外部方法有无事务,里面的方法执行都是没有事务的。
但我有点疑惑,如果里面方法抛出异常了,外部方法会怎么样。简单测试一下吧
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 29 30 31
| package com.banmoon.test.service;
import com.banmoon.test.dao.PlatformDao; import com.banmoon.test.dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;
@Service public class UserService {
@Autowired private UserDao userDao;
@Autowired private PlatformDao platformDao;
@Transactional public void insert(){ userDao.insert(); jdbcTemplate.execute("INSERT INTO `user`(`name`) VALUES ('半月1')"); platformDao.insert(); }
}
|
我执行了,结果是只有26
行的数据插入被回退。
24
行和28
行是非事务运行的,就算异常了也不会回退
6)NEVER
-
如果外部方法已经有事务了,抛出异常
-
如果外部方法没有事务,以非事务的方式运行
和SUPPORTS
相反的一个传播机制
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 29 30 31 32 33 34 35 36 37 38 39
| package com.banmoon.test.service;
import com.banmoon.test.dao.PlatformDao; import com.banmoon.test.dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;
@Service public class UserService {
@Autowired private UserDao userDao;
@Autowired private PlatformDao platformDao;
@Transactional public void insert(){ userDao.insert(); platformDao.insert(); }
public void insert(){ userDao.insert(); platformDao.insert(); }
}
|
7)NESTED
组成嵌套事务是什么意思呢?
我们将外部方法的事务称为父事务,内部方法创建的事务为子事务
-
当子事务回滚时,不影响父事务
-
当父事务回滚时,子事务一起回滚
这里同样,我们来进行测试一下
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| package com.banmoon.test.service;
import com.banmoon.test.dao.PlatformDao; import com.banmoon.test.dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;
@Service public class UserService {
@Autowired private UserDao userDao;
@Autowired private PlatformDao platformDao;
@Autowired private JdbcTemplate jdbcTemplate;
@Transactional public void insert(){ userDao.insert(); jdbcTemplate.execute("INSERT INTO `user`(`name`) VALUES ('半月1')"); try { platformDao.insert(); } catch (Exception e) { e.printStackTrace(); } } @Transactional public void insert1(){ userDao.insert(); jdbcTemplate.execute("INSERT INTO `user`(`name`) VALUES ('半月1')"); platformDao.insert(); throw new NullPointException(); }
}
|
执行insert()
,这是子事务中抛出的异常。结果是只有30
行的插入数据回滚。注意的就是,内部方法的异常要自己捕获,别被父事务发现了。如果发现了,大家就一起回滚吧。
执行insert1()
,这是父事务中抛出的异常。结果发现所有插入的数据都回滚了。
四、结语
我是半月,祝你幸福!!!