多线程事务怎么回滚?说用Transactional可以回去等通知了!
背景介绍
1,最近有一个大数据量插入的操作入库的业务场景,需要先做一些其他修改操作,然后在执行插入操作,由于插入数据可能会很多,用到多线程去拆分数据并行处理来提高响应时间,如果有一个线程执行失败,则全部回滚。
2,在spring中可以使用 @Transactional 注解去控制事务,使出现异常时会进行回滚,在多线程中,这个注解则不会生效,如果主线程需要先执行一些修改数据库的操作,当子线程在进行处理出现异常时,主线程修改的数据则不会回滚,导致数据错误。
3,下面用一个简单示例演示多线程事务。 公用的类和方法/** * 平均拆分list方法. * @param source * @param n * @param * @return */ public static List> averageAssign(List source,int n){ List> result=new ArrayList>(); int remaider=source.size()%n; int number=source.size()/n; int offset=0;//偏移量 for(int i=0;i value=null; if(remaider>0){ value=source.subList(i*number+offset, (i+1)*number+offset+1); remaider--; offset++; }else{ value=source.subList(i*number+offset, (i+1)*number+offset); } result.add(value); } return result; } /** 线程池配置 * @version V1.0 */ public class ExecutorConfig { private static int maxPoolSize = Runtime.getRuntime().availableProcessors(); private volatile static ExecutorService executorService; public static ExecutorService getThreadPool() { if (executorService == null){ synchronized (ExecutorConfig.class){ if (executorService == null){ executorService = newThreadPool(); } } } return executorService; } private static ExecutorService newThreadPool(){ int queueSize = 500; int corePool = Math.min(5, maxPoolSize); return new ThreadPoolExecutor(corePool, maxPoolSize, 10000L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(queueSize),new ThreadPoolExecutor.AbortPolicy()); } private ExecutorConfig(){} } /** 获取sqlSession * @author 86182 * @version V1.0 */ @Component public class SqlContext { @Resource private SqlSessionTemplate sqlSessionTemplate; public SqlSession getSqlSession(){ SqlSessionFactory sqlSessionFactory = sqlSessionTemplate.getSqlSessionFactory(); return sqlSessionFactory.openSession(); } }示例事务不成功操作 /** * 测试多线程事务. * @param employeeDOList */ @Override @Transactional public void saveThread(List employeeDOList) { try { //先做删除操作,如果子线程出现异常,此操作不会回滚 this.getBaseMapper().delete(null); //获取线程池 ExecutorService service = ExecutorConfig.getThreadPool(); //拆分数据,拆分5份 List> lists=averageAssign(employeeDOList, 5); //执行的线程 Thread []threadArray = new Thread[lists.size()]; //监控子线程执行完毕,再执行主线程,要不然会导致主线程关闭,子线程也会随着关闭 CountDownLatch countDownLatch = new CountDownLatch(lists.size()); AtomicBoolean atomicBoolean = new AtomicBoolean(true); for (int i =0;i list = lists.get(i); threadArray[i] = new Thread(() -> { try { //最后一个线程抛出异常 if (!atomicBoolean.get()){ throw new ServiceException("001","出现异常"); } //批量添加,mybatisPlus中自带的batch方法 this.saveBatch(list); }finally { countDownLatch.countDown(); } }); } for (int i = 0; i employeeDOList = new ArrayList<>(size); for (int i = 0; i employeeDOList) throws SQLException { // 获取数据库连接,获取会话(内部自有事务) SqlSession sqlSession = sqlContext.getSqlSession(); Connection connection = sqlSession.getConnection(); try { // 设置手动提交 connection.setAutoCommit(false); //获取mapper EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class); //先做删除操作 employeeMapper.delete(null); //获取执行器 ExecutorService service = ExecutorConfig.getThreadPool(); List> callableList = new ArrayList<>(); //拆分list List> lists=averageAssign(employeeDOList, 5); AtomicBoolean atomicBoolean = new AtomicBoolean(true); for (int i =0;i list = lists.get(i); //使用返回结果的callable去执行, Callable callable = () -> { //让最后一个线程抛出异常 if (!atomicBoolean.get()){ throw new ServiceException("001","出现异常"); } return employeeMapper.saveBatch(list); }; callableList.add(callable); } //执行子线程 List> futures = service.invokeAll(callableList); for (Future future:futures) { //如果有一个执行不成功,则全部回滚 if (future.get()<=0){ connection.rollback(); return; } } connection.commit(); System.out.println("添加完毕"); }catch (Exception e){ connection.rollback(); log.info("error",e); throw new ServiceException("002","出现异常"); }finally { connection.close(); } } // sql INSERT INTO employee (employee_id,age,employee_name,birth_date,gender,id_number,creat_time,update_time,status) values ( #{item.employeeId}, #{item.age}, #{item.employeeName}, #{item.birthDate}, #{item.gender}, #{item.idNumber}, #{item.creatTime}, #{item.updateTime}, #{item.status} )
数据库中一条数据:
测试结果:抛出异常,
删除操作的数据回滚了,数据库中的数据依旧存在,说明事务成功了。
成功操作示例: @Resource SqlContext sqlContext; /** * 测试多线程事务. * @param employeeDOList */ @Override public void saveThread(List employeeDOList) throws SQLException { // 获取数据库连接,获取会话(内部自有事务) SqlSession sqlSession = sqlContext.getSqlSession(); Connection connection = sqlSession.getConnection(); try { // 设置手动提交 connection.setAutoCommit(false); EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class); //先做删除操作 employeeMapper.delete(null); ExecutorService service = ExecutorConfig.getThreadPool(); List> callableList = new ArrayList<>(); List> lists=averageAssign(employeeDOList, 5); for (int i =0;i list = lists.get(i); Callable callable = () -> employeeMapper.saveBatch(list); callableList.add(callable); } //执行子线程 List> futures = service.invokeAll(callableList); for (Future future:futures) { if (future.get()<=0){ connection.rollback(); return; } } connection.commit(); System.out.println("添加完毕"); }catch (Exception e){ connection.rollback(); log.info("error",e); throw new ServiceException("002","出现异常"); // throw new ServiceException(ExceptionCodeEnum.EMPLOYEE_SAVE_OR_UPDATE_ERROR); } }
测试结果:
数据库中数据:
删除的删除了,添加的添加成功了,测试成功。
攀枝花,西昌,米易。三个地方哪个更适合退休后养老?这三个地方都小住过。要单说气候环境米易排第一,蔬菜水果很新鲜。但是米易就那么大,也就河滨边晒太阳,时间一长也寂寞。周边农家乐价格不便宜。还有待进一步开发建设丰富多彩的老年公寓。第二
执业药师是鸡肋职业吗?之所以有人认为执业药师是鸡肋,原因有几个。1。参加考试门槛比较低,通过率相对还是比较高的,之所以每年通过率20左右,主要在于报考的人太多了,很多人基础相对较差,如果按照本科以上药学
古人讲好女不观灯,好男不鞭春,是什么道理?鞭春是什么意思?伊朗公共场合中的男女界限非常严格,与男子待遇大不相同,女子连看球赛曾经也被禁绝,已知近年有位女球迷为了抗议政府此项规定怒而自焚。我国封建社会比之进步的多,但是女子同样不能随便出去,
郑州人民医院平均奖高还是郑州中心医院平均奖高?两个医院应该差别不大。郑州市中心医院前身是郑州市第四人民医院,其患者的主要是西郊,比如纺织系统啊和西郊的一些大企业的职工。而且经过多年的医疗服务,口碑和生意还可以。郑州市人民医院前
你拍过最好的照片是什么?你好,我是一名摄影爱好者,很高兴回答这个问题。非常愿意用自己手机拍摄的照片在这里与大家一起分享,但愿让你感到美。手机拍照好的照片太多了,也不知道你喜欢什么景色,我先给您看一下我的家
你见过最有财运的人是怎样的?我讲讲我朋友的财运,简直是颠覆我的认知。我朋友现在退休了,每个月有一万多的退休金,到现在我都没搞明白她怎么就那么年轻就退休,还能拿到那么多的工资,各种节日还有各种补助,冬天取暖费,
为什么邮政银行那么不遗余力的为保险拉业务?各家银行都在拉保险。只是很多时候银行压根不告诉你那是保险而已。很多人认可保险,但就是听不得保险两个字,银行会说明白前五年不能取,会告诉你满期后能取出多少,但很少明确告诉人们产品具体
什么是倒春寒?倒春寒就是春天的气候应该是温暖的,但是,它却乍暖还寒。春天开始于每年农历的2月末3月初,这个时间段的气候温度变化反差较大,冷空气活动频繁。这个时间段,人们体验最敏感最深。早中晚的气
老年人再婚家庭里,老年女性最后悔的事是什么?最后悔的事是她没有得到再婚男人的尊重和爱,反被人利用做了人家的免费保姆。对于老年女性再婚,老头再好,最后结局大部分不乐观。我周边有好几对再婚老人,婚后生活相辅相成,过得还好,可到将
沧州石家庄天津,哪里值得我用一生积蓄买套房子呢?当然是天津了。现在沧州,石家庄跟天津是没法比的,要拿出来一生积蓄买房子的话,这三个城市里面,应该首选天津。天津是中国四大直辖市之一,城市级别定位很高,现在天津经济正处于调整期,房子
山东正科退休工资4800,什么水平?退休工资按地区和从事的职业有关系。在山区县城正科退休工资4800元属正常。不能与一二线城市及金融国企等相比。真要是4800的话确实不多,还没有老师多呢,我们东营这边刚退休的老师55