精华内容
下载资源
问答
  • 百万数据导出excel
    千次阅读
    2020-05-28 08:04:12

    一.HSSF,XSSF和SXSSF的区别

    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文件的的确不少,但是,能够生成大数据量的excel框架,我倒是没发现,一般数据量大了都会出现内存溢出,所以,生成大数据量的excel文件要返璞归真,用java的基础技术,IO流来实现。...
  • C# datatable直接导出数据Excel,(数据百万级只需3秒)
  • java实现百万级别数据导出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,支持大量数据导出,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 百万量级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

    展开全文
  • 跟大家分享一款Excel组件,之所以分享这款,是因为它在处理excel时很方便,我将百万数据导出excel,耗时仅不用两分钟。poi概述Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft ...

    跟大家分享一款Excel组件,之所以分享这款,是因为它在处理excel时很方便,我将百万数据导出到excel,耗时仅不用两分钟。

    poi概述

    Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office格式档案读和写的功能。

    03498d66fb9dba550385d0db23fff9aa.png

    poi

    ide:Intellij IDEA

    类库需求poi:3.17

    Excel 版本是2007-2010,我们知道,excel一张表最大支持1048576行,16384列。要将百万级别的数据导出到excel,可接近excel单张表的存款大小了。

    poi接入到spring boot ,并不复杂,简单两步就可以完成了。

    在pom中引入配置,编写个辅助类

    1、pom文件

    15eaa8a8c60eb0b4a800b03aa6feffc8.png

    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。

    数据结果

    74534de877f4bcf6586411c7f1bf095d.png

    excel数据

    之前处理百万数据的时候,为了方便,直接把数据存入到了内存,可想而知,总是出现oom异常信息,被总监训了一顿,后来找了一些资料,找到了poi组件。

    现在好了,使用poi组件,大大提高了性能,也没有出现oom的情况了,总监很满意,客户也很满意,总算给客户一个较好的体验了。

    如果你用java实现处理excel数据时,建议你尝试用poi组件试试。

    对于excel数据处理,不知道您有没有更好的组件推荐。

    由于笔者知识水平有限,文中错漏之处在所难免,如有不足之处,欢迎纠正,感谢。

    ba83d6e3d9eb6a029581c068639a714e.png

    微信公众号:爱开发

    往期推荐:

    互联网行业又一轮大裁员,人人自危,究竟哪些人最容易被裁掉?

    互联网公司接二连三地曝出裁员,看得我好焦虑

    新来的架构师对我说,“你怎么用count(*),太慢了,用count(1)”

    展开全文
  • Springboot导出百万数据excel

    千次阅读 2022-03-08 15:54:39
    @ApiOperation(value = "万级数据导出") @RequestMapping(value = "/export1",method = RequestMethod.GET) public void exportExcel1(HttpServletResponse response, HttpServletRequest request) throws ...
  • 前端导出数据excel(兼容chrome 和 IE10以上版本),列举了网上常用的两种方法外,新增了可以导出超过5W行数据的方法
  • 本实例可以把多个不同对象集合或者一个对象集合的数据导出Excel表格中,能实现最大通用化。
  • POI百万级大数据EXCEL导出 - 请叫我猿叔叔的博客 - CSDN博客.htm
  • 百万数据Excel和SqlServer之间相互导入导出
  • 大量数据导出Excel方案

    千次阅读 2021-09-14 17:33:58
    最近可能会遇到大量数据导出Excel的场景,今天趁现在需求告一段落来做下技术预研,然后这里就顺便分享给大家。 一、数据量预判 因为我们是做物联网的,这里要导出的数据就是设备的上报数据。客户说要这些数据导出成...
  • java中使用poi导出Excel大批量数据 存在两个导出方法:存在一个分批量导出ZIP文件,一个导出exel文件
  • 怎样将matlab中数据导出excel中?xlswrite('E:系数.xls',B,'','A2')E:系数.xls 是路径B是需要导入的矩阵A2是指矩阵从表格中的A2开始输入希望可以帮到你怎样将MATLAB中的数据输出到excel中?可以直接用xlswrite命令...
  • web项目中需要有将数据导出excel的操作需求 使用html格式导出方法,但在导出时,根据最大行数重新建立新的excel文件; 数据从数据库取出使用纯jdbc方式读数据,边读边向文件中写; 待所有数据写完,将各个小excel...
  • sqlserver导出大量数据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:05
    excel导出,如果数据量在百万级,会出现俩点内存溢出的问题: 1. 查询数据量过大,导致内存溢出。 该问题可以通过分批查询来解决; 2. 最后下载的时候大EXCEL转换的输出流内存溢出;该方式可以通过新版的...
  • 这是我在公司实习的时候研究的课题,主要将大量数据从数据库导出Excel文件。(一般是值Excel2007及以后版本)因为Excel2003及以前的版本中能保持的数据量是有限制的。Excel2007及以后版本中的数据能保持100万条...
  • 共享一个可以把股票数据导出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:52
    1.java连接mongo数据库查询统计信息导出excel表格 2.所有连接参数都可以动态输入,参数包括:ip、端口、数据库名称、集合名称、查询参数(公众号,写在指定文件中),导出表格存放位置
  • easyui datagrid 数据导出Excel

    热门讨论 2014-03-25 15:48:06
    两种方法将easyui datagrid 中的数据导出Excel中,均以验证可以正常使用
  • 导出Excel 支持大数据量导出

    热门讨论 2012-08-21 23:36:10
    jxl 导出Excel 支持大数据量导出 导出数据量大 速度也快
  • SQL导出excel数据

    千次阅读 2021-08-22 15:37:12
    有时候需要把数据库中的数据导出excel表格中,虽然复制粘贴很管用,但是我们还是需要掌握一种快速导出数据的方法。提高效率不是分分钟的事吗? 1、在桌面或者其他地方新建一个excel表。 2、打开SQL Server,选择...
  • 使用python将大量数据导出Excel中的小技巧分享来源:中文源码网浏览: 次日期:2018年9月2日【下载文档:使用python将大量数据导出Excel中的小技巧分享.txt】(友情提示:右键点上行txt文档名->目标另存为)使用...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 138,552
精华内容 55,420
关键字:

百万数据导出excel