1 多线程操作数据库的问题(事务配置采用spring默认策略)
主线程中开启一个子线程,如果子线程出现异常的话,子线程会回滚吗?主线程会回滚吗?
案例:
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都不会进行回滚。
2 解决
2.1 spring的事务管理
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");
}
- 重点是resources这个变量,首先这是一个threadlocal的结果,枚举中的Map key类型为连接数据库的数据源dataSource,value类型为ConnectionHolder(可以理解为dataSource的一个connect连接)。
- 当方法执行到spring的doBegin开启事务方法,会先从resources获取,以当前数据源dataSource为key,获取value也就是connect存不存在,如果不存在则从dataSource获取一个connect设置进去,如果存在则直接以当前的connect来使用。
- 所以在spring事务管理的情况下,父子线程的数据源连接connect是不同的。
2.2 解决思路
虽然父子线程的数据源连接connect不同,但是我们可以通过线程池的特点来解决上述问题。
2.2.1 首先解决子线程出现异常,让父线程回滚。
之前分析了线程池在执行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操作出现异常时,主线程是可以进行回滚的。
2.2.1 解决子线程出现异常,让父子线程都能够回滚。
上述解决还存在一个问题,那就是虽然父线程能够回滚了,但是子线程还是不能进行回滚。
原理是由于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;
}
- 首先设计一个springUtil来获取spring容器中的对象。
- 在线程池的submit方法中获取,此时的proxyUserService就是被代理的独享(jdk或cglib),再执行添加account方法。这个方法就会被spring事务管理,从而实现回滚。