Spring(SpringBoot)--事务失效--原因场景解决⽅案
原⽂⽹址:
简介
本⽂介绍Spring什么时候事务会失效以及如何解决。
Spring通过AOP进⾏事务的控制,如果操作数据库报异常,则会进⾏回滚;如果没有报异常则会提交事务。但是,有时候Spring事务会失效,本⽂将介绍Spring的事务何时会失效,以及如何避免事务失效。
情景1:异常类型错误
声明式事务和注解事务回滚的原理:当被切⾯切中或者是加了注解的⽅法中抛出了unchecked exception异常(默认情况)
时,Spring会进⾏事务回滚。unchecked exception异常也就是:RuntimeException及其⼦类。
不回滚的情况
1. 把异常给try catch了,没有⼿动抛出RuntimeException异常
2. 抛出的异常不属于运⾏时异常(如IO异常),因为Spring默认情况下是捕获到运⾏时异常就回滚
会回滚的情况
1. ⽤了try catch,在catch⾥⾯再抛出⼀个 RuntimeException异常。
2. 将Spring默认的回滚时的异常修改为Exception
1. 这样可以让⾮运⾏时异常也要能回滚
3. 在catch后写回滚代码来实现回滚。
1. TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
2. 这样就可以在抛异常后也能return返回值;⽐较适合需要拿到返回值的场景(),
情况2⽰例:
@Transactional(rollbackFor = { Exception.class })
public boolean test() {
doDbSomeThing();
//其他操作
return true;
}
情况3⽰例
/** TransactionAspectSupport⼿动回滚事务:*/
public boolean test() {
try {
doDbSomeThing();
} catch (Exception e) {
e.printStackTrace();
/
/加上之后抛了异常就能回滚(有这句代码就不需要再⼿动抛出运⾏时异常了)
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return false;
}
return true;
}
情景2:⾃调⽤
简介
如果在⼀个类⾥边,调⽤同类⾥边的⽅法,会导致被调⽤的⽅法事务失效,有如下⼏种情景:
情景1:⽆事务调⽤事务,被调⽤的⽅法事务失效,此时抛出了异常也不会回滚。
情景2:在REQUIRED级别调⽤REQUIRES_NEW级别时,进⼊REQUIRES_NEW级别的⽅法时没有
新创建事务。但若REQUIRES_NEW 级别的⽅法⾥抛了异常,则REQUIRED级别与REQUIRES_NEW级别的操作都会回滚。
复现
情景1:⽆事务调⽤事务
ample.ller;
ample.demo.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
UserService userService;
@PostMapping("/test")
public void test() {
userService.insertAndUpdate();
}
}
ample.demo.user.service;
import sion.service.IService;
ample.ity.User;
public interface UserService extends IService<User> {
void insertAndUpdate();
}
ample.demo.user.mapper.UserMapper;
ample.demo.user.service.UserService;
import org.springframework.stereotype.Service;
import ansaction.annotation.Transactional;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
public void insertAndUpdate() {
User user = new User();
user.setName("Tony");
this.save(user);
updateUser(user);
}
@Transactional
public void updateUser(User user) {
user.setAge(20);
this.updateById(user);
int i = 1 / 0;
}
}
测试结果:(事务失效。我们想要的是:id和name有值正常,age不应该有值)
JDBC Connection [HikariProxyConnection@769992042 sql.cj.jdbc.ConnectionImpl@3eac1b48] will not be managed by Spring
==>  Preparing: INSERT INTO t_user ( name ) VALUES ( ? )
==> Parameters: Tony(String)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@113376db]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5a445157] was not registered fo
r synchronization because synchronization is not active JDBC Connection [HikariProxyConnection@80636421 sql.cj.jdbc.ConnectionImpl@3eac1b48] will not be managed by Spring
==>  Preparing: UPDATE t_user SET name=?, age=? WHERE id=?
==> Parameters: Tony(String), 20(Integer), 1(Long)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5a445157]
2021-05-19 21:58:13.902 ERROR 5948 --- [nio-8080-exec-1] C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with p
java.lang.ArithmeticException: / by zero
at ...
......
事务调⽤事务
ample.demo.user.mapper.UserMapper;
ample.demo.user.service.UserService;
import org.springframework.stereotype.Service;
import ansaction.annotation.Propagation;
import ansaction.annotation.Transactional;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void insertAndUpdate() {
User user = new User();
user.setName("Tony");
this.save(user);
updateUser(user);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateUser(User user) {
user.setAge(20);
this.updateById(user);
}
}
结果: (进⼊REQUIRES_NEW级别的⽅法时没有新创建事务)
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@41d0b10]
JDBC Connection [HikariProxyConnection@367846290 sql.cj.jdbc.ConnectionImpl@7f014cae] will be managed by Spring ==>  Preparing: INSERT INTO t_user ( name ) VALUES ( ? )
==> Parameters: Tony(String)
<==    Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@41d0b10]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@41d0b10] from current transaction
==>  Preparing: UPDATE t_user SET name=?, age=? WHERE id=?
==> Parameters: Tony(String), 20(Integer), 2(Long)
<==    Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@41d0b10]
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@41d0b10]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@41d0b10]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@41d0b10]
解决⽅案
简介
解决⽅案的核⼼:从容器中获取此类的bean,然后使⽤这个bean来调⽤⽅法。有以下三种⽅法:
1. @Autowired注⼊⾃⼰
2. 通过ApplicationContext 获得⾃⼰
3. 通过AopContext获取当前代理对象
法1:@Autowired注⼊⾃⼰
ample.demo.user.mapper.UserMapper;
ample.demo.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import ansaction.annotation.Propagation;
import ansaction.annotation.Transactional;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {    @Autowired
UserServiceImpl userServiceImpl;
@Override
public void insertAndUpdate() {
User user = new User();
user.setName("Tony");
this.save(user);
userServiceImpl.updateUser(user);
}
@Transactional
public void updateUser(User user) {
user.setAge(20);
this.updateById(user);
int i = 1 / 0;
}
}
法2:ApplicationContext 获得⾃⼰
ample.demomon;
import org.springframework.beans.BeansException;
import t.ApplicationContext;
import t.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class ApplicationContextHolder implements ApplicationContextAware {
private static ApplicationContext context;
public void setApplicationContext(ApplicationContext context) throws BeansException {
}
public static ApplicationContext getContext() {
springboot aop
return context;
}
}

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。