最近公司在做一个数据报表的功能,其中需要导入原来的手动操作的数据到现在的系统中,经过整理,数据以excel的格式导入。在处理的过程中发现,需要读取解析的数据大概10W+,以原有的处理方式,读取excel中的数据需要消耗时间2分钟,插入到数据库中需要消耗时间高达20分钟。结果就是直接系统卡死了。
后来想到的处理方式有2种:1.使用队列中间件 。 2.多线程
考虑中间件要调整的还挺多,就选择第二种方式了,解决思路是:解析excel时按sheet进行线程分解,每一次sheet数据解析完成后插入数据库中时都单独启动一个新的线程进行保存。于是着手干吧。
一开始是自己去写了一个实现线程池的类,如下:
public class AsyncConfiguration {
/**
* 组件计算
*/
@Bean("excelExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//核心线程数5:线程池创建时候初始化的线程数
executor.setCorePoolSize(10);
//最大线程数5:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
executor.setMaxPoolSize(100);
//缓冲队列500:用来缓冲执行任务的队列
executor.setQueueCapacity(500);
//允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
executor.setKeepAliveSeconds(60);
//线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
executor.setThreadNamePrefix("DailyAsync-");
executor.initialize();
return executor;
}
}
线程调用类创建后,调用方式便是在需要调用的方法上使用注解@Async("excelExecutor")
/**
* 批量存储导入数据
* @param list
* @return
* @throws Exception
*/
@Async("excelExecutor")
public void insertBigData(List<LclForeginFee> list)throws Exception{
// 1、设置ExecutorType.BATCH批量模式:
// (1.1)把SQL语句发给数据库,数据库预编译好,数据库等待需要运行的参数,接收到参数后一次运行;
// (1.2)ExecutorType.BATCH只打印一次SQL语句,多次设置参数步骤,
// 2、设置autoCommit=false,改为手动提交事务
SqlSession sqlSession = factory.openSession(ExecutorType.BATCH, false);
try {
// 每次手动提交事务时,批次已插入数据量。如1000条一次提交。
int batchSize = 5000;
int size = list.size();
LclForeginFeeMapper tMapper = sqlSession.getMapper(LclForeginFeeMapper.class);
for (int i = 0; i < size; i++) {
// 调用单条插入,而非batchInsert。
// 原因:ExecutorType.BATCH+insert并非直接插入,而是数据库预编译,等待事务提交
LclForeginFee model = list.get(i);
tMapper.insert(model);
// 批次提交事务
if (i > 0 && i % batchSize == 0) {
// 预插入,代替commit()和closeCache()
sqlSession.flushStatements();
}
}
} catch (Exception e) {
e.printStackTrace();
sqlSession.rollback();
} finally {
// 关闭事务
sqlSession.close();
}
System.out.println("执行线程任务。。。。。。。。。。。。。。。。。。。");
}
我的数据插入也做了处理,把数据的存储改成手动提交,数据的插入为预插入的处理方式,并且数据提交为5000条做一次事务提交。
整好了跑一次试试吧。额。。。。。好像没有实现多线程啊。
写了测试方法试了一下,的确还是单线程执行的。咋回事。搜索吧。
答案就是在同一个类中,一个方法调用另外一个有@Async注解的方法,注解不会生效.
@Async
注解会在以下几个场景失效:
- 异步方法使用static关键词修饰;
- 异步类不是一个Spring容器的bean(一般使用注解
@Component
和@Service
,并且能被Spring扫描到); - SpringBoot应用中没有添加
@EnableAsync
注解; - 在同一个类中,一个方法调用另外一个有@Async注解的方法,注解不会生效。原因是@Async注解的方法,是在代理类中执行的。
赶紧把注解了@Async的方法移植到新的类中。再试一次。成功了。经过测试,10W+的数据处理完成后消耗的时间大概在3-8分钟。只要将前端调用处理成异步调用
让前端可以做出插入进度的提醒,后端进行插入。可以不影响用户进行其他的操作。