主线程中开启一个子线程,如果子线程出现异常的话,子线程会回滚吗?主线程会回滚吗?
案例:
UserController:
@RequestMapping(value = "add",method= RequestMethod.POST)
public void add(Integer id,String name,String password,int age,int deleteFlag){
User user = new User();
user.setId(id);
user.setName(name);
user.setPassword(password);
user.setAge(age);
user.setDeleteFlag(deleteFlag);
userService.add(user);
}
UserService:
@Service
@Transactional
public class UserService implements IUserService {
@Autowired
private UserMapper userMapper;
@Autowired
private AccountService accountService;
private Executor executor = ThreadPool.getPoll();
@Override
public Integer add(User user){
int result = userMapper.add(user);
Account account = new Account();
account.setId(user.getId());
executor.execute(() -> {
accountService.add(account);
if (user.getId().intValue() == 11) {
throw new RuntimeException("执行异常");
}
});
return result;
}
}
结果:
Exception in thread "ThreadPoolTaskExecutor-1" java.lang.RuntimeException: 执行异常
at test.service.UserService.lambda$add$0(UserService.java:68)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
主线程的添加user和子线程的添加account都不会进行回滚。
spirng的事务管理已经分析过,详细之前的文章,这里只列出一部分关键的结构信息。
TransactionSynchronizationManager:
public abstract class TransactionSynchronizationManager {
// 线程私有事务资源
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
// 事务同步
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<>("Transaction synchronizations");
// 当前事务的名称
private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<>("Current transaction name");
// 当前事务是否只读
private static final ThreadLocal<Boolean> currentTransactionReadOnly =
new NamedThreadLocal<>("Current transaction read-only status");
// 当前事务的隔离级别
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
new NamedThreadLocal<>("Current transaction isolation level");
// 实际事务是否激活
private static final ThreadLocal<Boolean> actualTransactionActive =
new NamedThreadLocal<>("Actual transaction active");
}
虽然父子线程的数据源连接connect不同,但是我们可以通过线程池的特点来解决上述问题。
之前分析了线程池在执行submit时,会将异常放入futureTask中,可以利用这个特点来解决。
案例:
UserService:
public Integer addV2(User user){
Future<Integer> future = executor.submit(() -> {
Account account = new Account();
account.setId(user.getId());
int result2 = accountService.add(account);
if (user.getId().intValue() == 11) {
throw new RuntimeException("account执行异常");
}
return result2;
});
int result = userMapper.add(user);
try {
Integer integer = future.get();
} catch (InterruptedException e) {
throw new RuntimeException("子线程account执行中断异常");
} catch (ExecutionException e) {
throw new RuntimeException("子线程account执行异常");
}
return result;
}
这样当子线程account操作出现异常时,主线程是可以进行回滚的。
上述解决还存在一个问题,那就是虽然父线程能够回滚了,但是子线程还是不能进行回滚。
原理是由于spring的事务管理也是采用jdk和cglib当方法进入addV2中,里面的逻辑就是Userservice对象本身执行的方法了,所以需要让spring再对子线程中的方法能进行代理。
案例:
SpringUtil
@Component
public class SpringUtil implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public IUserService getUserService(){
return applicationContext.getBean(IUserService.class);
}
public <T> T get(Class<T> clazz){
return applicationContext.getBean(clazz);
}
}
UserService
@Override
public Integer addV3(User user) {
Future<Integer> future = executor.submit(() -> {
IUserService proxyUserService = springUtil.getUserService();
Integer result2 = proxyUserService.addAccount(user.getId());
return result2;
});
int result = userMapper.add(user);
try {
Integer integer = future.get();
} catch (InterruptedException e) {
throw new RuntimeException("子线程account执行中断异常");
} catch (ExecutionException e) {
throw new RuntimeException("子线程account执行异常");
}
return result;
}
@Override
public Integer addAccount(int id){
Account account = new Account();
account.setId(id);
int result2 = accountService.add(account);
if (id == 11) {
throw new RuntimeException("account执行异常");
}
return result2;
}