IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> java实现百万级别数据导出excel(JPAhibernatemybatis三种方式) -> 正文阅读

[Java知识库]java实现百万级别数据导出excel(JPAhibernatemybatis三种方式)

在业务系统中,导出报表的需求会很常见,而随着时间推移业务量不断增加,数据库的数据可能达到百万甚至千万级别。对于导出报表功能,最简单的做法就是从数据库里面把需要的数据一次性加载到内存,然后写入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

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:

同上
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-05-08 07:54:26  更:2022-05-08 07:55:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 23:39:24-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码