-
2020-05-28 08:04:12
HSSF是POI工程对Excel 97-2007 (.xls) 文件操作的纯Java实现
XSSF是POI工程对Excel 2007 OOXML (.xlsx) 文件操作的纯Java实现
从POI 3.8版本开始,提供了一种基于XSSF的低内存占用的API----SXSSF
SXSSF实现了一套自动刷入数据的机制。当数据数量达到一定程度时(用户可以自己设置这个限制)。像文本中刷入部分数据。这样就缓解了程序运行时候的压力。达到高效的目的。二.百万级数据高效导出EXCEL 方案
1.这种数据量的导出,肯定是异步的方式,将内存数据异步生成excel文件,存储到服务器中(根据特定规则命名);
此过程采用 POI 3.8的 SXSSF 进行写excel操作
2.从服务器下载excel文件
异步处理方式:
//根据条件拿到数据集 List<OrderBean> orderExportList = orderQueryService.getOrderList(orderSearchBean); //创建线程池 int size = orderExportList.size(); int count = size / 5; ExecutorService executorService = Executors.newFixedThreadPool(5); //future存储器 List<Future<List<OrderBean>>> futureList = new ArrayList<Future<List<OrderBean>>>(); //截取开启5个线程分起任务完善信息 for (int i = 0; i < 5; i++) { int start = count * i; int end = count * (i + 1); if (i == 5 - 1) { end = size; } @SuppressWarnings("unchecked") Future<List<OrderBean>> future = executorService.submit(new RunOrderThread(orderQueryService, orderExportList.subList(start, end))); // 每个异步计算的结果存放在future存储器中 futureList.add(future); } //启动异步计算结果输出线程,该线程扫描异步计算Futrue的状态,如果已经完成,则输出异步计算结果 ExportThread export = new ExportThread(this, futureList, fileName, staffid); Thread resultThread = new Thread(export); resultThread.start();
SXSSF 方式写excel:
更多相关内容 -
Java 导出大批量数据excel(百万级).pdf
2020-04-07 18:30:41目前java框架中能够生成excel文件的的确不少,但是,能够生成大数据量的excel框架,我倒是没发现,一般数据量大了都会出现内存溢出,所以,生成大数据量的excel文件要返璞归真,用java的基础技术,IO流来实现。... -
C#导出数据到Excel(百万级3秒)
2017-10-31 10:41:40C# datatable直接导出数据到Excel,(数据量百万级只需3秒) -
java实现百万级别数据导出excel(JPA,hibernate,mybatis三种方式)
2022-05-06 22:58:10java实现百万级别数据导出excel,提供了JPA,hibernate,mybatis三种不同的方式来实现在业务系统中,导出报表的需求会很常见,而随着时间推移业务量不断增加,数据库的数据可能达到百万甚至千万级别。对于导出报表功能,最简单的做法就是从数据库里面把需要的数据一次性加载到内存,然后写入excel文件,再把excel文件返回给用户。这种做法在数据量不大的时候是可行的,但是一旦需要导出几十万甚至上百万的数据,很可能出现OOM导致服务崩溃的情况,而且导出所消耗的时间会大大增加。
这里提供一种支持百万级别数据导出的方法,并且消耗很少的内存,核心思想就是不要一次性把数据加载到内存中。主要是从两个方面去解决:
1.从数据库加载数据不要一次性加载,可以分页的方式或者用游标的方式分批加载数据,加载一批数据处理一批并且释放内存,这样内存占用始终处于一个比较平稳的状态。分页的方式加载编码比较繁琐,我一般是采用游标方式逐行加载。目前常用的持久层框架有JPA,mybaits,hibernate,下面会分别列出JPA,hibernate及mybatis通过游标方式加载数据。
2.写入excel也是分批写入,推荐阿里的EasyExcel,占用内存极低。
EasyExcel的pom依赖:
<dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>2.1.1</version> <optional>true</optional> </dependency>
运行环境
jdk1.8,idea2019,堆内存:-Xms256M -Xms256M(导出100万数据毫无压力),springboot,数据库是mysql
先来张效果图,这个是最大堆内存设置为256M,两张表联合查询的情况下导出100万数据的效果,可以看到堆内存变化比较平稳,导出100万数据耗时143秒,这个速度还有优化的空间,如果是单表导出的话速度会更快些:
JPA使用游标方式导出百万数据(两种方式,推荐使用QueryDSL)
1.使用jpa原生方式,这种适合sql比较简单的情况
pom.xml:
<!-- spring web依赖,搭建web项目需要这个依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- jpa --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
repository:
@Repository public interface UserRepository extends JpaRepository<UserEntity,Integer> { //@QueryHint(name = HINT_FETCH_SIZE,value = Integer.MIN_VALUE+"") 值设置为Integer.MIN_VALUE告诉mysql需要逐条返回数据,并且返回值需要用stream来接收 @QueryHints(@QueryHint(name = HINT_FETCH_SIZE,value = Integer.MIN_VALUE+"")) @Query(value = "select * from user limit 500000",nativeQuery = true) Stream<UserEntity> findAllList(); }
service:
注意:
需要加事务注解,并且是只读事务
需要及时调用entityManager的detach方法释放内存,不然还是会出现OOM
@Autowired private EntityManager entityManager; @Autowired private UserRepository userRepository; Transactional(readOnly = true) public void exportData3(ScrollResultsHandler<UserExportVO> scrollResultsHandler){ Stream<UserEntity> allList = userRepository.findAllList.forEach((o)->{ UserEntity userEntity = (UserEntity) o; UserExportVO userExportVO = UserExportVO.builer() .userName(userEntity.getUsername()) .mobile(userEntity.getMobile()) .build(); scrollResultsHandler.handle(userExportVO); //对象被session持有,调用detach方法释放内存 entityManager.detach(userEntity); }); }
controller:
@RequestMapping("export4") public void export4(HttpServletResponse response) throws IOException { response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding("utf-8"); String filenames="bigdata4"; response.addHeader("Content-Disposition", "filename=" + filenames + ".xlsx"); ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), UserExportVO.class).build(); WriteSheet[] writeSheet = new WriteSheet[] { EasyExcel.writerSheet(0, "sheet").build() }; userService.exportData(s->{ UserExportVO resultObject = s; ArrayList arrayList = new ArrayList<UserExportVO>(); arrayList.add(resultObject); excelWriter.write(arrayList, writeSheet[0]); }); excelWriter.finish(); }
使用到的相关的类:
/** * @author 奔腾的野马 * @date 2022/04/25 09:12 */ public interface ScrollResultsHandler<T> { void handle(T t); }
import com.alibaba.excel.annotation.ExcelProperty; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.math.BigDecimal; import java.time.LocalDateTime; /** * @Author: 奔腾的野马 * @Date: 2021/10/16 16:19 */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public class UserExportVO { @ExcelProperty(value = "用户名") private String userName; @ExcelProperty(value = "手机号") private String mobile; }
2.使用querysql游标方式导出,推荐这种方式,可以实现动态sql,多表关联甚至是常见的组函数都可以支持
pom.xml:
<dependencies> <!-- spring web依赖,搭建web项目需要这个依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!--QueryDSL支持--> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-apt</artifactId> <version>5.0.0</version> <scope>provided</scope> </dependency> <!--QueryDSL支持--> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-jpa</artifactId> <version>5.0.0</version> </dependency> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-core</artifactId> <version>5.0.0</version> </dependency> </dependencies> <build> <plugins> <!-- QueryDSL 插件 --> <plugin> <groupId>com.mysema.maven</groupId> <artifactId>apt-maven-plugin</artifactId> <version>1.1.3</version> <executions> <execution> <goals> <goal>process</goal> </goals> <configuration> <outputDirectory>target/generated-sources/java</outputDirectory> <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor> </configuration> </execution> </executions> </plugin> </plugins> </build>
service:
@Autowired private JPAQueryFactory jpaQueryFactory; private QUserEntity qUserEntity = QUserEntity.userEntity; @Transactional(readOnly = true) public void exportData2(ScrollResultsHandler<UserExportVO> scrollResultsHandler){ //需要用stream方式接收,这样才能逐条处理 Stream<UserExportVO> userExportVOStream = jpaQueryFactory.select(Projections.bean(UserExportVO.class , qUserEntity.userName, qUserEntity.mobile)) .from(qUserEntity) //.join(xxxEntity) //.on(xxxx) //setHint(HINT_FETCH_SIZE,Integer.MIN_VALUE+"") 告诉mysql需要逐条返回数据,注意值需要设置为Integer.MIN_VALUE才能生效 .setHint(HINT_FETCH_SIZE,Integer.MIN_VALUE+"") .limit(1000000) .stream(); userExportVOStream.forEach(dto->{ scrollResultsHandler.handle(dto); }); }
controller:
同上
mybatis使用游标方式导出百万数据
pom.xml:
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.9</version> </dependency>
dao:
/** * @author 奔腾的野马 * @date 2022/04/16 19:14 */ @Mapper public interface UserDao { //ResultSetType.TYPE_FORWORD_ONLY 结果集的游标只能向下滚动,fetchSize需要设置为Integer.MIN_VALUE游标才能生效 @Options(resultSetType = ResultSetType.FORWARD_ONLY,fetchSize = Integer.MIN_VALUE) @ResultType(UserExportVO.class) @Select("select userName,mobile from user limit 500000") void reportAll2(ResultHandler<UserExportVO> handler); }
service:
@Transactional(readOnly = true) public void export2(ResultHandler<UserExportVO> handler){ userDao.reportAll2(handler); }
controller:
同上
hibernate使用游标方式导出百万数据
service:
@Autowired private EntityManager entityManager; public void exportData(ScrollResultsHandler<UserExportVO> scrollResultsHandler){ //当不需要缓存时,最好使用StatelessSession StatelessSession session = ((Session) entityManager.getDelegate()).getSessionFactory().openStatelessSession(); Query query = session.getNamedQuery("getAllList"); query.setCacheMode(CacheMode.IGNORE); //setFetchSize(Integer.MIN_VALUE)告诉mysql逐条返回数据 query.setFetchSize(Integer.MIN_VALUE); query.setFirstResult(0); query.setMaxResults(1000000); query.setReadOnly(true); query.setLockMode("a", LockMode.NONE); //ScrollMode.TYPE_FORWORD_ONLY 结果集的游标只能向下滚动 ScrollableResults results = query.scroll(ScrollMode.FORWARD_ONLY); while (results.next()) { UserEntity userEntity = (UserEntity) results.get(0); UserExportVO userExportVO = UserExportVO.builer() .userName(userEntity.getUsername()) .mobile(userEntity.getMobile()) .build(); scrollResultsHandler.handle(userExportVO); } results.close(); session.close(); }
controller:
同上
流式导出遇到的问题(基于jpa和querydsl)及解决方法
1.导出过程中堆内存占用急剧上升,发生OOM
1.1 项目中使用了log4jdbc-log4j2-jdbc4.1(版本是1.16),驱动为net.sf.log4jdbc.sql.jdbcapi.DriverSpy,改成mysql的原生驱动就好了。"log4jdbc-log4j2-jdbc4.1"本来是用来开发过程中方便打印sql的,结果却带来了OOM问题,看来使用第三方jar包一定要慎重啊。
1.2 项目的存在多个版本的querydsl,jar包冲突,解决jar包就正常了
1.3 二次查询时,hibernate的一级缓存没有及时释放,进一步分析,发现大量的对象都被缓存在(org.hibernate.engine.
StatefulPersistenceContext)中,导致一级缓存泄漏
解决方法:
由于 Hibernate 的一级缓存是其内部使用的,无法关闭或停用(随着Session 销毁)。从Hibernate 的手册或文档中可知,Hibernate 的一级缓存的清除可通过以下方式:
1)对于单个对象的清除:
Session session=sessionFactory.getCurrentSession(); session.evict(entity);
2)对于实体集合的清除:
Session session=sessionFactory.getCurrentSession(); session.clear();建议在程序中加入对 Hibernate 一级缓存的清除工作,以便可以其内存数据可以及时释放。2.导出过程中遍历stream需要二次查询数据库时提示"Streaming result set com.mysql.cj.protocol.a.result.ResultsetRowsStreaming@5800daf5 is still active. No statements may be issued when any streaming result sets are open and in use on a given connection. Ensure that you have called .close() on any active streaming result sets before attempting more queries"
错误详细内容:
java.sql.SQLException: Streaming result set com.mysql.cj.protocol.a.result.ResultsetRowsStreaming@3b8732ec is still active. No statements may be issued when any streaming result sets are open and in use on a given connection. Ensure that you have called .close() on any active streaming result sets before attempting more queries. at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129) at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97) at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:953) at com.mysql.cj.jdbc.ClientPreparedStatement.executeQuery(ClientPreparedStatement.java:1003) at net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy.executeQuery(PreparedStatementSpy.java:780) at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52) at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeQuery(HikariProxyPreparedStatement.java) at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:57) at org.hibernate.loader.Loader.getResultSet(Loader.java:2292) at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:2050) at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:2012) at org.hibernate.loader.Loader.doQuery(Loader.java:953) at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:354) at org.hibernate.loader.Loader.doList(Loader.java:2838) at org.hibernate.loader.Loader.doList(Loader.java:2820) at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2652) at org.hibernate.loader.Loader.list(Loader.java:2647) at org.hibernate.loader.hql.QueryLoader.list(QueryLoader.java:506) at org.hibernate.hql.internal.ast.QueryTranslatorImpl.list(QueryTranslatorImpl.java:396) at org.hibernate.engine.query.spi.HQLQueryPlan.performList(HQLQueryPlan.java:219) at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1404) at org.hibernate.query.internal.AbstractProducedQuery.doList(AbstractProducedQuery.java:1562) at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1530) at org.hibernate.query.internal.AbstractProducedQuery.getSingleResult(AbstractProducedQuery.java:1578) at org.hibernate.query.criteria.internal.compile.CriteriaQueryTypeQueryAdapter.getSingleResult(CriteriaQueryTypeQueryAdapter.java:111) at org.springframework.data.jpa.repository.query.JpaQueryExecution$SingleEntityExecution.doExecute(JpaQueryExecution.java:196) at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:88) at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:154) at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:142) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:618) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:605) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:366) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:99) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:149) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212) at com.sun.proxy.$Proxy223.countAllByDeliveryNo(Unknown Source)
查阅资料后发现,是mysql不支持在流式查询过程中使用同一连接再次查询数据库
解决方法:
方法1.使用异步方法查询,这样就可以规避同一个连接二次查询的问题
方法2.需要二次查询时开启一个新的事务去查询就可以,spring中可以使用事务注解开启新的事务就搞定了,注解如下:@Transactional(propagation = Propagation.REQUIRES_NEW,readOnly = true)
-
js实现数据导出为EXCEL(支持大量数据导出)
2020-10-15 11:43:24主要为大家详细介绍了js实现数据导出为EXCEL,支持大量数据导出,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 -
百万量级php excel快速导出代码案例
2018-05-30 16:22:36百万量级php csv快速导出代码案例 已经真实项目导出100万行测试,资源消耗小,导出数据快 百万量级php csv快速导出代码案例 已经真实项目导出100万行测试,资源消耗小,导出数据快 -
多线程实现百万级数据导出到excel
2022-05-03 19:57:53为提升导出数据的性能,采用多线程的方式实现导出百万级别的数据到excel。 2、考虑前提 大数据量导出到文件,首先需要考虑的是内存溢出的场景:数据库读取数据到内存中、将数据写入到excel进行大量的IO操作。 考虑...1、需求背景
为提升导出数据的性能,采用多线程的方式实现导出百万级别的数据到excel。
2、考虑前提
- 大数据量导出到文件,首先需要考虑的是内存溢出的场景:数据库读取数据到内存中、将数据写入到excel进行大量的IO操作。
- 考虑到一个文件数据过大,用户打开慢,体验不好。
针对这些问题的考虑,采用多线程的方式多个线程同时处理查询数据,一个线程生成一个excel,最后在合并数据返回,以达到提高效率的目的。
3、实现思路
(1)计算导出数据的总条数:dataTotalCount。
(2)合理设置每个excel的数据的数量(避免打开一个excel时间过长):LIMIT。
(3)计算出需要导出的excel个数(线程个数):count=dataTotalCount/ LIMIT + (dataTotalCount% LIMIT > 0 ? 1 : 0)。
(4)将分页、生成文件路径信息,初始化到一个队列里面,队列的大小是线程的数量,对每个文件开启一个线程,异步执行导出,文件全部导出结束,此时异步转成同步,将最终生成的excel文件生成zip压缩包。4、代码实现
4.1、多线程批量导出excel工具类
核心点:此处采用并发包中的CountDownLatch做同步器,等子线程将文件全部导出后,再在主线程进行数据整合的操作,即导出压缩包。此处提升了导出的效率,比如子线程的执行时间大约20秒,假设子线程的数量是5个,此处并行处理大约需要20秒处理完成。如果采用原来串行执行的方式的话,处理这五个任务需要100秒的时间。
此处我设置每个excel的数据条数是4万条,这里的数量需要根据实际情况合理设置,否则在导出过程会出现内存溢出的情况。package com.wm.casetest.util; import com.wm.casetest.service.IAsynExportExcelService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.FileOutputStream; import java.util.HashMap; import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import static com.wm.casetest.service.impl.IAsynExportExcelServiceImpl.DATA_TOTAL_COUNT; /** * @ClassName:AsynExcelExportUtil * @Description: 多线程批量导出excel工具类 * @Author:Deamer * @Date:2021/8/8 23:00 **/ @Slf4j @Component public class AsynExcelExportUtil { // 定义导出的excel文件保存的路径 private String filePath = "C:\\Users\\Deamer\\Desktop\\export\\"; @Resource private IAsynExportExcelService asynExportExcelService; /** * 每批次处理的数据量 */ private static final int LIMIT = 40000; // Queue是java自己的队列,是同步安全的 public static Queue<Map<String, Object>> queue; static { // 一个基于链接节点的无界线程安全的队列 queue = new ConcurrentLinkedQueue<>(); } /** * 多线程批量导出 excel * * @param response 用于浏览器下载 * @throws InterruptedException */ public void threadExcel(HttpServletResponse response) throws InterruptedException { long start = System.currentTimeMillis(); initQueue(); //异步转同步,等待所有线程都执行完毕返回 主线程才会结束 try { CountDownLatch cdl = new CountDownLatch(queue.size()); while (queue.size() > 0) { asynExportExcelService.excuteAsyncTask(queue.poll(), cdl); } cdl.await(); System.out.println("excel导出完成·······················"); //压缩文件 File zipFile = new File(filePath.substring(0, filePath.length() - 1) + ".zip"); FileOutputStream fos1 = new FileOutputStream(zipFile); //压缩文件目录 ZipUtils.toZip(filePath, fos1, true); //发送zip包 ZipUtils.sendZip(response, zipFile); } catch (Exception e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("任务执行完毕共消耗: " + (end - start) + "ms"); } /** * 初始化队列 */ public void initQueue() { long dataTotalCount = DATA_TOTAL_COUNT;// 数据的总数 int listCount = (int) dataTotalCount; // 计算出多少页,即循环次数 int count = listCount / LIMIT + (listCount % LIMIT > 0 ? 1 : 0); for (int i = 1; i <= count; i++) { Map<String, Object> map = new HashMap<>(); map.put("page", i); map.put("limit", LIMIT); map.put("path", filePath); //添加元素 queue.offer(map); } } }
4.2、定义异步导出数据的接口
核心点:Async注解用于表示方法需要异步调用,此时Spring会使用后台的线程池来异步的执行它所注解的方法。
4.2.1、启动类加注解@EnableAsync
package com.wm.casetest; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication @EnableAsync public class CasetestApplication { public static void main(String[] args) { SpringApplication.run(CasetestApplication.class, args); } }
4.2.2、线程池配置:
package com.wm.casetest.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.Executor; /** * @ClassName: AsyncTaskPoolConfig * @Description: * @Author: WM * @Date: 2021-08-06 19:23 **/ @Configuration @EnableAsync public class AsyncTaskPoolConfig { @Bean("taskExecutor") public Executor taskExecutor() { int i = Runtime.getRuntime().availableProcessors(); System.out.println("系统最大线程数:" + i); ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); taskExecutor.setCorePoolSize(i); taskExecutor.setMaxPoolSize(i); taskExecutor.setQueueCapacity(99999); taskExecutor.setKeepAliveSeconds(60); taskExecutor.setThreadNamePrefix("taskExecutor--"); taskExecutor.setWaitForTasksToCompleteOnShutdown(true); taskExecutor.setAwaitTerminationSeconds(60); return taskExecutor; } }
4.2.3、service接口:
package com.wm.casetest.service; import java.util.Map; import java.util.concurrent.CountDownLatch; /** * @ClassName: IAsynExportExcelService * @Description: * @Author: WM * @Date: 2021-08-06 20:05 **/ public interface IAsynExportExcelService { /** * 分批次异步导出数据 * * @param countDownLatch */ void excuteAsyncTask(Map<String, Object> map, CountDownLatch countDownLatch); }
实现类:
说明:为方便演示,此处我使用程序模拟创建了40万条数据,根据分页入参,在查询过程中会对数据进行分页。package com.wm.file.service.impl; import cn.afterturn.easypoi.excel.entity.enmus.ExcelType; import com.wm.file.entity.MsgClient; import com.wm.file.service.IAsynExportExcelService; import com.wm.file.util.MyExcelExportUtil; import org.apache.poi.ss.usermodel.Workbook; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.io.File; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; /** * @ClassName: IAsynExportExcelServiceImpl * @Description: * @Author: WM * @Date: 2021-08-06 20:06 **/ @Service public class IAsynExportExcelServiceImpl implements IAsynExportExcelService { // 假定数据量是40万 public static final long DATA_TOTAL_COUNT = 400000; // 查询要导出的批次数据 static List<Object> list = new ArrayList<>(); static { for (int i = 0; i < DATA_TOTAL_COUNT; i++) { //模拟库中一百万数据量 MsgClient client = new MsgClient(); client.setBirthday(new Date()); client.setClientName("小明xxxsxsxsxsxsxsxsxsxsx" + i); client.setClientPhone("18797" + i); client.setCreateBy("JueYue"); client.setId("1" + i); client.setRemark("测试" + i); list.add(client); } } @Resource private MyExcelExportUtil myExcelExportUtil; @Override @Async("taskExecutor") public void excuteAsyncTask(Map<String, Object> map, CountDownLatch cdl) { long start = System.currentTimeMillis(); int currentPage = (int) map.get("page"); int pageSize = (int) map.get("limit"); List subList = new ArrayList(page(list, pageSize, currentPage)); int count = subList.size(); System.out.println("线程:" + Thread.currentThread().getName() + " , 读取数据,耗时 :" + (System.currentTimeMillis() - start)); String filePath = map.get("path").toString() + map.get("page") + ".xlsx"; // 调用导出的文件方法 Workbook workbook = myExcelExportUtil.getWorkbook("计算机一班学生", "学生", MsgClient.class, subList, ExcelType.XSSF); File file = new File(filePath); MyExcelExportUtil.exportExcel2(workbook, file); long end = System.currentTimeMillis(); System.out.println("线程:" + Thread.currentThread().getName() + " , 导出excel" + map.get("page") + ".xlsx成功 , 导出数据:" + count + " ,耗时 :" + (end - start) + "ms"); // 执行完线程数减1 cdl.countDown(); System.out.println("剩余任务数 ===========================> " + cdl.getCount()); } // 手动分页方法 public List page(List list, int pageSize, int page) { int totalcount = list.size(); int pagecount = 0; int m = totalcount % pageSize; if (m > 0) { pagecount = totalcount / pageSize + 1; } else { pagecount = totalcount / pageSize; } List<Integer> subList = new ArrayList<>(); if (pagecount < page) { return subList; } if (m == 0) { subList = list.subList((page - 1) * pageSize, pageSize * (page)); } else { if (page == pagecount) { subList = list.subList((page - 1) * pageSize, totalcount); } else { subList = list.subList((page - 1) * pageSize, pageSize * (page)); } } return subList; } }
4.2.3、导出文件的方法:
说明:导出文件中的方法上篇有做分析,这里不再赘述,我这里只贴了实现类中使用到了两个方法
package com.wm.casetest.util; import cn.afterturn.easypoi.excel.ExcelExportUtil; import cn.afterturn.easypoi.excel.entity.ExportParams; import cn.afterturn.easypoi.excel.entity.TemplateExportParams; import cn.afterturn.easypoi.excel.entity.enmus.ExcelType; import cn.afterturn.easypoi.handler.inter.IExcelExportServer; import com.wm.casetest.util.easypoi.ExcelStyleUtil; import com.wm.casetest.util.easypoi.MyExcelExportService; import lombok.extern.slf4j.Slf4j; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.xssf.streaming.SXSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.stereotype.Component; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; /** * @ClassName: ExcelExportUtil * @Description: Excel导出工具类 * @Author: WM * @Date: 2021-07-24 18:47 **/ @Slf4j @Component public class MyExcelExportUtil { @Resource private IExcelExportServer excelExportServer; /** * 小量数据允许导出的最大条数 */ private static final Integer EXPORT_EXCEL_BASE_MAX_NUM = 100000; public static int USE_SXSSF_LIMIT = 100000; /** * 获取导出的 Workbook对象 * 普通导出 * * @param title 大标题 * @param sheetName 页签名 * @param object 导出实体 * @param list 普通导出传入的数据集合 * @param list 数据集合 * @return Workbook */ public static Workbook getWorkbook(String title, String sheetName, Class<?> object, List<?> list, ExcelType excelType) { // 判断导出数据是否为空 if (list == null) { list = new ArrayList<>(); } // 判断导出数据数量是否超过限定值 // if (list.size() > EXPORT_EXCEL_BASE_MAX_NUM) { // title = "导出数据行数超过:" + EXPORT_EXCEL_BASE_MAX_NUM + "条,无法导出!"; // list = new ArrayList<>(); // } // 获取导出参数 ExportParams exportParams = new ExportParams(title, sheetName, excelType); // 设置导出样式 exportParams.setStyle(ExcelStyleUtil.class); // 设置行高 exportParams.setHeight((short) 8); // 普通导出,输出Workbook流 // return ExcelExportUtil.exportExcel(exportParams, object, list); return createExcel(exportParams, object, list); } public static void exportExcel2(Workbook workbook, File file) { FileOutputStream out = null; try { out = new FileOutputStream(file); // 输出表格 workbook.write(out); } catch (IOException e) { log.error("文件导出异常,详情如下:", e); throw new RuntimeException("文件导出异常"); } finally { try { if (workbook != null) { // 关闭输出流 workbook.close(); } if (out != null) { // 关闭输出流 out.close(); } } catch (IOException e) { log.error("文件导出异常,详情如下:", e); } } } }
4.2.4、执行结果:
控制台信息:
生成的压缩包:
因为导出40万条数据,每个excel是4万条数据,所以会生成10个文件。5、总结
本节主要实现采用多线程的方式实现数据的导出,学会使用并发包中的CountDownLatch去实现,文件导出工具使用的是Apache的poi,怎么使用我在上一篇已讲。
关于文件导出的demo链接:
https://github.com/Deamer1102/ExportExcelData -
java将百万级别数据导出到Excel中,用时仅需要98941毫秒
2022-04-07 10:47:58跟大家分享一款Excel组件,之所以分享这款,是因为它在处理excel时很方便,我将百万数据导出到excel,耗时仅不用两分钟。poi概述Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft ...跟大家分享一款Excel组件,之所以分享这款,是因为它在处理excel时很方便,我将百万数据导出到excel,耗时仅不用两分钟。
poi概述
Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office格式档案读和写的功能。
poi
ide:Intellij IDEA
类库需求poi:3.17
Excel 版本是2007-2010,我们知道,excel一张表最大支持1048576行,16384列。要将百万级别的数据导出到excel,可接近excel单张表的存款大小了。
poi接入到spring boot ,并不复杂,简单两步就可以完成了。
在pom中引入配置,编写个辅助类
1、pom文件
pom配置poi组件
源码如下:
<!-- excel依赖 --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>3.17</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>3.17</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml-schemas</artifactId> <version>3.17</version> </dependency>
/** * POI辅助类 */ public class POIUtil { /** * 导出到excel * @param filePath 文件存储路径 * @throws IOException */ public static void exportExcel(String filePath) throws IOException { //1.在内存中创建一个excel文件 SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(100); //2.创建工作簿 Sheet sheet = sxssfWorkbook.createSheet("数据"); for (int i = 0; i < 1000000; i++) { Row row = sheet.createRow(i); for (int j = 0; j < 11; j++) { if(i == 0) { //3.创建标题行 row.createCell(j).setCellValue(" 列" + j); } else { //4.遍历数据,创建数据行 if (j == 0) { CellUtil.createCell(row, j, String.valueOf(i)); } else CellUtil.createCell(row, j, String.valueOf(getData())); } } } FileOutputStream out = new FileOutputStream(filePath); sxssfWorkbook.write(out); out.close(); } /** * 填充数据 * @return */ public static String getData(){ DecimalFormat df = new DecimalFormat("######0.00"); double shoot= Math.random(); return df.format(shoot); }
程序运行入口
public class Main { final static String path = "E:\\data.xlsx"; public static void main(String[] args) throws Exception { long beginTime = System.currentTimeMillis(); POIUtil.exportExcel(path); long endTime = System.currentTimeMillis(); System.out.println("耗时:" + (endTime - beginTime)); } }
耗时:98941
一百万条数据,11列,文件大小有30多M。
数据结果
excel数据
之前处理百万数据的时候,为了方便,直接把数据存入到了内存,可想而知,总是出现oom异常信息,被总监训了一顿,后来找了一些资料,找到了poi组件。
现在好了,使用poi组件,大大提高了性能,也没有出现oom的情况了,总监很满意,客户也很满意,总算给客户一个较好的体验了。
如果你用java实现处理excel数据时,建议你尝试用poi组件试试。
对于excel数据处理,不知道您有没有更好的组件推荐。
由于笔者知识水平有限,文中错漏之处在所难免,如有不足之处,欢迎纠正,感谢。
微信公众号:爱开发
往期推荐:
-
Springboot导出百万级数据到excel
2022-03-08 15:54:39@ApiOperation(value = "万级数据导出") @RequestMapping(value = "/export1",method = RequestMethod.GET) public void exportExcel1(HttpServletResponse response, HttpServletRequest request) throws ... -
js 导出excel(支持大量数据)
2018-11-02 23:31:55前端导出数据为excel(兼容chrome 和 IE10以上版本),列举了网上常用的两种方法外,新增了可以导出超过5W行数据的方法 -
java中,list集合数据导出到excel表格通用工具类
2018-07-27 14:26:50本实例可以把多个不同对象集合或者一个对象集合的数据导出到Excel表格中,能实现最大通用化。 -
POI百万级大数据量EXCEL导出 - 请叫我猿叔叔的博客 - CSDN博客.htm
2019-12-10 20:57:51POI百万级大数据量EXCEL导出 - 请叫我猿叔叔的博客 - CSDN博客.htm -
百万级数据在Excel和Sql数据库之间相互导入、导出
2012-08-02 07:48:48百万级数据在Excel和SqlServer之间相互导入导出。 -
大量数据导出Excel方案
2021-09-14 17:33:58最近可能会遇到大量数据导出Excel的场景,今天趁现在需求告一段落来做下技术预研,然后这里就顺便分享给大家。 一、数据量预判 因为我们是做物联网的,这里要导出的数据就是设备的上报数据。客户说要这些数据导出成... -
java poi大数据量 导出excel
2016-08-05 16:25:05java中使用poi导出Excel大批量数据 存在两个导出方法:存在一个分批量导出ZIP文件,一个导出exel文件 -
matlab数据点导出excel表格-怎样将matlab中数据导出到excel中?
2021-04-18 10:07:50怎样将matlab中数据导出到excel中?xlswrite('E:系数.xls',B,'','A2')E:系数.xls 是路径B是需要导入的矩阵A2是指矩阵从表格中的A2开始输入希望可以帮到你怎样将MATLAB中的数据输出到excel中?可以直接用xlswrite命令... -
JAVA导出数据到excel中大数据量的解决方法
2014-05-04 10:48:05web项目中需要有将数据导出excel的操作需求 使用html格式导出方法,但在导出时,根据最大行数重新建立新的excel文件; 数据从数据库取出使用纯jdbc方式读数据,边读边向文件中写; 待所有数据写完,将各个小excel... -
sqlserver导出大量数据到Excel,电子表满
2020-06-24 11:35:57sqlserver导出大量数据到Excel 记录导出数据遇到的坑~~~ sqlserver 导出数据到Excel最简单的方法是将查询出来的结果,右键–>另存为excel或者txt。但是这种方式的明显缺点,如果内容中有特殊的字符,往往会影响... -
Excel 百万数据导出 csv格式
2022-01-27 11:28:16也就是说excel2003完全不可能满足百万数据导出的需求。 Excel 2007-2010版本。一张表最大支持1048576行,16384列; 补充,关于CSV 1.1 什么是csv? 纯文本格式 1.2 csv有哪些优点? 数据存储量小 功能强大 兼容... -
C# DataGridView数据导出Excel文件
2021-11-28 12:43:41博主在做项目的时候需要把数据库的数据用DataGridView展示,然后把展示的数据导出为Excel文件,很多时候我们做项目都会有一个下载文件的按钮,我们需要用微软的的接口,Microsoft.Office.Interop.Excel,我们需要... -
POI百万级大数据量EXCEL导出
2018-09-25 20:18:05excel导出,如果数据量在百万级,会出现俩点内存溢出的问题: 1. 查询数据量过大,导致内存溢出。 该问题可以通过分批查询来解决; 2. 最后下载的时候大EXCEL转换的输出流内存溢出;该方式可以通过新版的... -
从数据库将大量数据导出到Excel文件程序总结
2012-09-21 22:38:17这是我在公司实习的时候研究的课题,主要将大量数据从数据库导出到Excel文件。(一般是值Excel2007及以后版本)因为Excel2003及以前的版本中能保持的数据量是有限制的。Excel2007及以后版本中的数据能保持100万条... -
如何把股票数据导出excel?导出股票历史数据到Excel的方法
2020-09-11 00:14:54共享一个可以把股票数据导出Excel的方法,是一个在线下载股票历史数据,目前可以下载A股、港股、美股所有个股的历史数据,数据是Excel的方便分析查看、做表,最主要是免费。。。 只要两步就能下载: 填股票代码、... -
纯js实现数据导出excel表格
2022-03-22 15:57:27纯js实现数据导出excel表格 -
ElementUI导出表格数据为Excel文件
2021-01-21 20:11:14将列表的数据导出成excel文件是管理系统中非常常见的功能。最近正好用到了ElementUI+Vue的组合做了个导出效果,拿出来分享一下,希望可以帮到大家:) 实现效果 实现步骤 1.定义导出按钮 <el-button @click=... -
java MongoDB查询数据导出为excel表格
2015-06-03 21:08:521.java连接mongo数据库查询统计信息导出为excel表格 2.所有连接参数都可以动态输入,参数包括:ip、端口、数据库名称、集合名称、查询参数(公众号,写在指定文件中),导出表格存放位置 -
easyui datagrid 数据导出到Excel
2014-03-25 15:48:06两种方法将easyui datagrid 中的数据导出到Excel中,均以验证可以正常使用 -
导出Excel 支持大数据量导出
2012-08-21 23:36:10jxl 导出Excel 支持大数据量导出 导出数据量大 速度也快 -
SQL导出excel数据
2021-08-22 15:37:12有时候需要把数据库中的数据导出到excel表格中,虽然复制粘贴很管用,但是我们还是需要掌握一种快速导出数据的方法。提高效率不是分分钟的事吗? 1、在桌面或者其他地方新建一个excel表。 2、打开SQL Server,选择... -
使用python将大量数据导出到Excel中的小技巧分享
2021-02-10 09:47:30使用python将大量数据导出到Excel中的小技巧分享来源:中文源码网浏览: 次日期:2018年9月2日【下载文档:使用python将大量数据导出到Excel中的小技巧分享.txt】(友情提示:右键点上行txt文档名->目标另存为)使用...