前言
spring事务生效场景可能大家在很多文章都看过了,所以明天就水一篇,看大家能不能播种一些不一样的货色。间接进入主题
spring事务生效场景以及起因
1、场景一:service没有托管给spring
public class TranInvalidCaseWithoutInjectSpring {
private UserService userService;
public TranInvalidCaseWithoutInjectSpring(UserService userService) {
this.userService = userService;
}
@Transactional
public boolean add(User user){
boolean isSuccess = userService.save(user);
int i = 1 % 0;
return isSuccess;
}
}
@Test
public void testServiceWithoutInjectSpring(){
boolean randomBoolean = new Random().nextBoolean();
TranInvalidCaseWithoutInjectSpring tranInvalidCaseWithoutInjectSpring;
if(randomBoolean){
tranInvalidCaseWithoutInjectSpring = applicationContext.getBean(TranInvalidCaseWithoutInjectSpring.class);
System.out.println("service曾经被spring托管");
}else{
tranInvalidCaseWithoutInjectSpring = new TranInvalidCaseWithoutInjectSpring(userService);
System.out.println("service没被spring托管");
}
boolean isSuccess = tranInvalidCaseWithoutInjectSpring.add(user);
Assert.assertTrue(isSuccess);
}
生效起因: spring事务失效的前提是,service必须是一个bean对象
解决方案: 将service注入spring
2、场景二:抛出受检异样
@Service
public class TranInvalidCaseByThrowCheckException {
@Autowired
private UserService userService;
@Transactional
public boolean add(User user) throws FileNotFoundException {
boolean isSuccess = userService.save(user);
new FileInputStream("1.txt");
return isSuccess;
}
}
@Test
public void testThrowCheckException() throws Exception{
boolean randomBoolean = new Random().nextBoolean();
boolean isSuccess = false;
TranInvalidCaseByThrowCheckException tranInvalidCaseByThrowCheckException = applicationContext.getBean(TranInvalidCaseByThrowCheckException.class);
if(randomBoolean){
System.out.println("配置@Transactional(rollbackFor = Exception.class)");
isSuccess = tranInvalidCaseByThrowCheckException.save(user);
}else{
System.out.println("配置@Transactional");
tranInvalidCaseByThrowCheckException.add(user);
}
Assert.assertTrue(isSuccess);
}
生效起因: spring默认只会回滚非查看异样和error异样
解决方案: 配置rollbackFor
3、场景三:业务本人捕捉了异样
@Transactional
public boolean add(User user) {
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
}
return isSuccess;
}
@Test
public void testCatchExecption() throws Exception{
boolean randomBoolean = new Random().nextBoolean();
boolean isSuccess = false;
TranInvalidCaseWithCatchException tranInvalidCaseByThrowCheckException = applicationContext.getBean(TranInvalidCaseWithCatchException.class);
if(randomBoolean){
randomBoolean = new Random().nextBoolean();
if(randomBoolean){
System.out.println("将异样原样抛出");
tranInvalidCaseByThrowCheckException.save(user);
}else{
System.out.println("设置TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();");
tranInvalidCaseByThrowCheckException.addWithRollBack(user);
}
}else{
System.out.println("业务本人捕捉了异样");
tranInvalidCaseByThrowCheckException.add(user);
}
Assert.assertTrue(isSuccess);
}
生效起因: spring事务只有捕捉到了业务抛出去的异样,能力进行后续的解决,如果业务本人捕捉了异样,则事务无奈感知
解决方案:
1、将异样原样抛出;
2、设置TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
4、场景四:切面程序导致
@Service
public class TranInvalidCaseWithAopSort {
@Autowired
private UserService userService;
@Transactional
public boolean save(User user) {
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
throw new RuntimeException();
}
return isSuccess;
}
}
@Aspect
@Component
@Slf4j
public class AopAspect {
@Around(value = " execution (* com.github.lybgeek.transcase.aopsort..*.*(..))")
public Object around(ProceedingJoinPoint pjp){
try {
System.out.println("这是一个切面");
return pjp.proceed();
} catch (Throwable throwable) {
log.error("{}",throwable);
}
return null;
}
}
生效起因: spring事务切面的优先级程序最低,但如果自定义的切面优先级和他一样,且自定义的切面没有正确处理异样,则会同业务本人捕捉异样的那种场景一样
解决方案:
1、在切面中将异样原样抛出;
2、在切面中设置TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
5、场景五:非public办法
@Service
public class TranInvalidCaseWithAccessPerm {
@Autowired
private UserService userService;
@Transactional
protected boolean save(User user){
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
throw new RuntimeException();
}
return isSuccess;
}
}
public class TranInvalidCaseWithAccessPermTest {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class);
TranInvalidCaseWithAccessPerm tranInvalidCaseWithAccessPerm = context.getBean(TranInvalidCaseWithAccessPerm.class);
boolean isSuccess = tranInvalidCaseWithAccessPerm.save(UserUtils.getUser());
System.out.println(isSuccess);
}
}
生效起因: spring事务默认失效的办法权限都必须为public
解决方案:
1、将办法改为public;
2、批改TansactionAttributeSource,将publicMethodsOnly改为false【这个从源码跟踪得出结论】
3、开启 AspectJ 代理模式【从spring文档得出结论】
文档如下
Method visibility and @Transactional
When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.
具体步骤:
1、在pom引入aspectjrt坐标以及相应插件
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.9</version>
<configuration>
<showWeaveInfo>true</showWeaveInfo>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal> <!-- use this goal to weave all your main classes -->
<goal>test-compile</goal> <!-- use this goal to weave all your test classes -->
</goals>
</execution>
</executions>
</plugin>
2、在启动类上加上如下配置
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
注: 如果是在idea上运行,则需做如下配置
4、间接用TransactionTemplate
示例:
@Autowired
private TransactionTemplate transactionTemplate;
private void process(){
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
processInTransaction();
}
});
}
6、场景六:父子容器
生效起因: 子容器扫描范畴过大,将未加事务配置的serivce扫描进来
解决方案:
1、父子容器个扫个的范畴;
2、不必父子容器,所有bean都交给同一容器治理
注: 因为示例是应用springboot,而springboot启动默认没有父子容器,只有一个容器,因而就该场景就演示示例了
7、场景七:办法用final润饰
@Transactional
public final boolean add(User user, UserService userService) {
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
throw new RuntimeException();
}
return isSuccess;
}
生效起因: 因为spring事务是用动静代理实现,因而如果办法应用了final润饰,则代理类无奈对指标办法进行重写,植入事务性能
解决方案:
1、办法不要用final润饰
8、场景八:办法用static润饰
@Transactional
public static boolean save(User user, UserService userService) {
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
throw new RuntimeException();
}
return isSuccess;
}
生效起因: 起因和final一样
解决方案:
1、办法不要用static润饰
9、场景九:调用本类办法
public boolean save(User user) {
return this.saveUser(user);
}
@Transactional
public boolean saveUser(User user) {
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
throw new RuntimeException();
}
return isSuccess;
}
生效起因: 本类办法不通过代理,无奈进行加强
解决方案:
1、注入本人来调用;
2、应用@EnableAspectJAutoProxy(exposeProxy = true) + AopContext.currentProxy()
10、场景十:多线程调用
@Transactional(rollbackFor = Exception.class)
public boolean save(User user) throws ExecutionException, InterruptedException {
Future<Boolean> future = executorService.submit(() -> {
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
throw new Exception();
}
return isSuccess;
});
return future.get();
}
生效起因: 因为spring的事务是通过数据库连贯来实现,而数据库连贯spring是放在threadLocal外面。同一个事务,只能用同一个数据库连贯。而多线程场景下,拿到的数据库连贯是不一样的,即是属于不同事务
11、场景十一:谬误的流传行为
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public boolean save(User user) {
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
throw new RuntimeException();
}
return isSuccess;
}
生效起因: 应用的流传个性不反对事务
12、场景十二:应用了不反对事务的存储引擎
生效起因: 应用了不反对事务的存储引擎。比方mysql中的MyISAM
13、场景十三:数据源没有配置事务管理器
注: 因为springboot,他默认曾经开启事务管理器。org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration。因而示例略过
14、场景十四:被代理的类过早实例化
@Service
public class TranInvalidCaseInstantiatedTooEarly implements BeanPostProcessor , Ordered {
@Autowired
private UserService userService;
@Transactional
public boolean save(User user) {
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
throw new RuntimeException();
}
return isSuccess;
}
@Override
public int getOrder() {
return 1;
}
}
生效起因: 当代理类的实例化早于AbstractAutoProxyCreator后置处理器,就无奈被AbstractAutoProxyCreator后置处理器加强
总结
本文列举了14种spring事务生效的场景,其实这14种外面有很多都是归根结底都是属于同一类问题引起,比方因为动静代理起因、办法限定符起因、异样类型起因等
demo链接
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-transaction-invalid-case
发表回复