-
2020-09-28 19:44:13
说明
由于某些原因系统jvm内存最大只能给到512,但是要导出百万数据该如何实现呢?传统的一次性导出肯定是不行的
优化
Excel导出基于 springboot , easyexcel
依赖:<dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>2.2.6</version> </dependency>
导出优化
基于自增id滚动式查询写入
@GetMapping("/standard-product-ext/export") public void productExtExport(HttpServletResponse response, StandardProductExtQuery query) throws Exception{ String today = DateUtil.today(); String fileName = "标准商品标签导入模板" + today; String fileNameCode = URLEncoder.encode(fileName, "UTF-8"); response.setContentType("application/force-download"); response.setHeader("Content-Disposition", "attachment;filename=" + fileNameCode + ".xlsx"); ExcelWriter writer = new ExcelWriterBuilder() .autoCloseStream(true) .file(response.getOutputStream()) .head(DscStandardProductExtVO.class) .build(); Integer lastBatchMaxId = 0; query.setLastBatchMaxId(lastBatchMaxId); WriteSheet writeSheet = new WriteSheet(); writeSheet.setSheetName("标准商品标签导入模板" + today); List<DscStandardProductExtVO> productExt for (; ; ) { productExt = dictionaryService.getProductExt(query); if (productExt.isEmpty()) { writer.write(productExt, writeSheet); writer.finish(); break; } else { lastBatchMaxId = productExt.stream().map(DscStandardProductExtVO::getId).max(Integer::compareTo).orElse(Integer.MAX_VALUE); query.setLastBatchMaxId(lastBatchMaxId); writer.write(productExt, writeSheet); } } }
- dictionaryService.getProductExt(query) 的xml
SELECT * FROM base_drug where id > #{lastBatchMaxId}
导入优化
- 使用 easyexcel 监听器导出
@PostMapping("/standard-product-ext/import") public ResponseResult productExtImport(MultipartFile file) throws IOException { File localFile = new File(appSettings.getTempdir() + RandomStringUtils.randomAlphanumeric(12) + file.getOriginalFilename()); // 将上传文件 写入到 localFile 本地文件,后续对 localFile 操作读取 FileUtils.multipartFileToFile(file, localFile); try { ExcelUploadResult excelUploadResult = dictionaryService.updateBatchProductExtByFile(localFile); return ResponseUtils.success(excelUploadResult); } catch (DaoException e) { ResponseUtils.fail(ErrorCodeConstants.FAIL, "导入标准商品标签失败"); } return null; }
dictionaryService.updateBatchProductExtByFile(localFile) 方法
public ExcelUploadResult updateBatchProductExtByFile(File excelFile) throws DaoException{ StandardProductExtListener listener = new StandardProductExtListener(clearSearchDao, dscStandardProductExtDao, cosUtils, appSettings); try { EasyExcel.read(excelFile, DscStandardProductExtVO.class, listener).sheet().doRead(); } catch (Exception e) { log.error("[标签导入]读取excel出错", e); throw new ServiceException("excel导入失败"); } finally { if (excelFile.exists()) { excelFile.delete(); } } int successTotal = listener.getSuccessTotal(); int total = listener.getTotal(); int error = listener.getError(); ExcelUploadResult result = error > 0 ? new ExcelUploadResult(total, successTotal,error, listener.getErrorFileUrl()) : new ExcelUploadResult(successTotal, total, 0, null); return result; }
- StandardProductExtListener
需要注意的是 StandardProductExtListener 不能被spirng管理,需要手动new,依赖spring的类通过构造方法注入,这里是官方说明的,如果给spirng管理会有什么问题暂时没有尝试
public class StandardProductExtListener extends AnalysisEventListener<DscStandardProductExtVO> { int successTotal = 0; int total = 0; int error = 0; String errorFileUrl; List<DscStandardProductExtVO> list = new ArrayList<>(); ClearSearchDao clearSearchDao; DscStandardProductExtDao dscStandardProductExtDao; CosUtils cosUtils; AppSettings appSettings; List<DscStandardProductExtVO> errorList = Lists.newArrayList(); public StandardProductExtListener(ClearSearchDao clearSearchDao, DscStandardProductExtDao dscStandardProductExtDao, CosUtils cosUtils, AppSettings appSettings) { this.clearSearchDao = clearSearchDao; this.dscStandardProductExtDao = dscStandardProductExtDao; this.cosUtils = cosUtils; this.appSettings = appSettings; } /** * 每条数据的解析 * @param vo * @param analysisContext */ @Override public void invoke(DscStandardProductExtVO vo, AnalysisContext analysisContext) { list.add(vo); if (list.size() > 3000) { try { doFile(list); total += list.size(); list.clear(); } catch (DaoException e) { e.printStackTrace(); } } } @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { try { total += list.size(); doFile(list); log.info("处理最后数据{}", total); errorFileUrl = updateErrorFile(); } catch (DaoException e) { e.printStackTrace(); } } public void doFile(List<DscStandardProductExtVO> readExcels) throws DaoException{ // 业务逻辑处理 } }
总结
经过测试 导出百万数据 使用这两种优化没什么问题,只是导入速度可能有点慢,但是不会OOM,其次可能系统full gc会相对严重。
关于我
觉得文章不错请扫码关注我吧
更多相关内容 -
easyexcel导出百万级数据_百万级别数据Excel导出优化
2021-12-14 21:42:55文章标签:easyexcel导出百万级数据excel一次滚动太多excel翻页不连续java导出数据到excel模板java导出数据到excel表格java导出超过65535 这篇文章不是标题党,下文会通过一个仿真例子分析如何优化百万级别数据...weixin_39850981 2020-11-29 21:08:52
文章标签: easyexcel导出百万级数据 excel一次滚动太多 excel翻页不连续 java导出数据到excel模板 java导出数据到excel表格 java导出超过65535
这篇文章不是标题党,下文会通过一个仿真例子分析如何优化百万级别数据Excel导出。
笔者负责维护的一个数据查询和数据导出服务是一个相对远古的单点应用,在上一次云迁移之后扩展为双节点部署,但是发现了服务经常因为大数据量的数据导出频繁Full GC,导致应用假死无法响应外部的请求。因为某些原因,该服务只能够「分配2GB的最大堆内存」,下面的优化都是以这个堆内存极限为前提。通过查看服务配置、日志和APM定位到两个问题:
- 启动脚本中添加了CMS参数,采用了CMS收集器,该收集算法对内存的敏感度比较高,大批量数据导出容易瞬间打满老年代导致Full GC频繁发生。
- 数据导出的时候采用了一次性把目标数据全部查询出来再写到流中的方式,大量被查询的对象驻留在堆内存中,直接打满整个堆。
对于问题1咨询过身边的大牛朋友,直接把所有CMS相关的所有参数去掉,由于生产环境使用了JDK1.8,相当于直接使用默认的GC收集器参数-XX:+UseParallelGC,也就是Parallel Scavenge + Parallel Old的组合然后重启服务。观察APM工具发现Full GC的频率是有所下降,但是一旦某个时刻导出的数据量十分巨大(例如查询的结果超过一百万个对象,超越可用的最大堆内存),还是会陷入无尽的Full GC,也就是修改了JVM参数只起到了治标不治本的作用。所以下文会针对这个问题(也就是问题2),通过一个仿真案例来分析一下如何进行优化。
一些基本原理
如果使用Java(或者说依赖于JVM的语言)开发数据导出的模块,下面的伪代码是通用的:
-
数据导出方法(参数,输出流[OutputStream]){
-
1. 通过参数查询需要导出的结果集
-
2. 把结果集序列化为字节序列
-
3. 通过输出流写入结果集字节序列
-
4. 关闭输出流
-
}
一个例子如下:
-
@Data
-
public static class Parameter{
-
private OffsetDateTime paymentDateTimeStart;
-
private OffsetDateTime paymentDateTimeEnd;
-
}
-
public void export(Parameter parameter, OutputStream os) throws IOException {
-
List<OrderDTO> result =
-
orderDao.query(parameter.getPaymentDateTimeStart(), parameter.getPaymentDateTimeEnd()).stream()
-
.map(order -> {
-
OrderDTO dto = new OrderDTO();
-
......
-
return dto;
-
}).collect(Collectors.toList());
-
byte[] bytes = toBytes(result);
-
os.write(bytes);
-
os.close();
-
}
针对不同的OutputStream实现,最终可以把数据导出到不同类型的目标中,例如对于FileOutputStream而言相当于把数据导出到文件中,而对于SocketOutputStream而言相当于把数据导出到网络流中(客户端可以读取该流实现文件下载)。目前B端应用比较常见的文件导出都是使用后一种实现,基本的交互流程如下:
为了节省服务器的内存,这里的返回数据和数据传输部分可以设计为分段处理,也就是查询的时候考虑把查询全量的结果这个思路改变为每次只查询部分数据,直到得到全量的数据,每批次查询的结果数据都写进去OutputStream中。
这里以MySQL为例,可以使用类似于分页查询的思路,但是鉴于LIMIT offset,size的效率太低,结合之前的一些实践,采用了一种「改良的"滚动翻页"的实现方式」(这个方式是前公司的某个架构小组给出来的思路,后面广泛应用于各种批量查询、数据同步、数据导出以及数据迁移等等场景,这个思路肯定不是首创的,但是实用性十分高),注意这个方案要求表中包含一个有自增趋势的主键,单条查询SQL如下:
SELECT * FROM tableX WHERE id > #{lastBatchMaxId} [其他条件] ORDER BY id [ASC|DESC](这里一般选用ASC排序) LIMIT ${size}
把上面的SQL放进去前一个例子中,并且假设订单表使用了自增长整型主键id,那么上面的代码改造如下:
-
public void export(Parameter parameter, OutputStream os) throws IOException {
-
long lastBatchMaxId = 0L;
-
for (;;){
-
List<Order> orders = orderDao.query([SELECT * FROM t_order WHERE id > #{lastBatchMaxId}
-
AND payment_time >= #{parameter.paymentDateTimeStart} AND payment_time <= #{parameter.paymentDateTimeEnd} ORDER BY id ASC LIMIT ${LIMIT}]);
-
if (orders.isEmpty()){
-
break;
-
}
-
List<OrderDTO> result =
-
orderDao.query([SELECT * FROM t_order]).stream()
-
.map(order -> {
-
OrderDTO dto = new OrderDTO();
-
......
-
return dto;
-
}).collect(Collectors.toList());
-
byte[] bytes = toBytes(result);
-
os.write(bytes);
-
os.flush();
-
lastBatchMaxId = orders.stream().map(Order::getId).max(Long::compareTo).orElse(Long.MAX_VALUE);
-
}
-
os.close();
-
}
「上面这个示例就是百万级别数据Excel导出优化的核心思路」。查询和写入输出流的逻辑编写在一个死循环中,因为查询结果是使用了自增主键排序的,而属性lastBatchMaxId则存放了本次查询结果集中的最大id,同时它也是下一批查询的起始id,这样相当于基于id和查询条件向前滚动,直到查询条件不命中任何记录返回了空列表就会退出死循环。而limit字段则用于控制每批查询的记录数,可以按照应用实际分配的内存和每批次查询的数据量考量设计一个合理的值,这样就能让单个请求下常驻内存的对象数量控制在limit个从而使应用的内存使用更加可控,避免因为并发导出导致堆内存瞬间被打满。
❝
这里的滚动翻页方案远比LIMIT offset,size效率高,因为此方案每次查询都是最终的结果集,而一般的分页方案使用的LIMIT offset,size需要先查询,后截断。
❞仿真案例
某个应用提供了查询订单和导出记录的功能,表设计如下:
-
DROP TABLE IF EXISTS `t_order`;
-
CREATE TABLE `t_order`
-
(
-
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
-
`creator` VARCHAR(16) NOT NULL DEFAULT 'admin' COMMENT '创建人',
-
`editor` VARCHAR(16) NOT NULL DEFAULT 'admin' COMMENT '修改人',
-
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-
`edit_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
-
`version` BIGINT NOT NULL DEFAULT 1 COMMENT '版本号',
-
`deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除标识',
-
`order_id` VARCHAR(32) NOT NULL COMMENT '订单ID',
-
`amount` DECIMAL(10, 2) NOT NULL DEFAULT 0 COMMENT '订单金额',
-
`payment_time` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT '支付时间',
-
`order_status` TINYINT NOT NULL DEFAULT 0 COMMENT '订单状态,0:处理中,1:支付成功,2:支付失败',
-
UNIQUE uniq_order_id (`order_id`),
-
INDEX idx_payment_time (`payment_time`)
-
) COMMENT '订单表';
现在要基于支付时间段导出一批订单数据,先基于此需求编写一个简单的SpringBoot应用,这里的Excel处理工具选用Alibaba出品的EsayExcel,主要依赖如下:
-
<dependency>
-
<groupId>org.springframework.boot</groupId>
-
<artifactId>spring-boot-starter-web</artifactId>
-
</dependency>
-
<dependency>
-
<groupId>org.springframework.boot</groupId>
-
<artifactId>spring-boot-starter-jdbc</artifactId>
-
</dependency>
-
<dependency>
-
<groupId>mysql</groupId>
-
<artifactId>mysql-connector-java</artifactId>
-
<version>8.0.18</version>
-
</dependency>
-
<dependency>
-
<groupId>com.alibaba</groupId>
-
<artifactId>easyexcel</artifactId>
-
<version>2.2.6</version>
-
</dependency>
模拟写入200W条数据,生成数据的测试类如下:
-
public class OrderServiceTest {
-
private static final Random OR = new Random();
-
private static final Random AR = new Random();
-
private static final Random DR = new Random();
-
@Test
-
public void testGenerateTestOrderSql() throws Exception {
-
HikariConfig config = new HikariConfig();
-
config.setUsername("root");
-
config.setPassword("root");
-
config.setJdbcUrl("jdbc:mysql://localhost:3306/local?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false");
-
config.setDriverClassName(Driver.class.getName());
-
HikariDataSource hikariDataSource = new HikariDataSource(config);
-
JdbcTemplate jdbcTemplate = new JdbcTemplate(hikariDataSource);
-
for (int d = 0; d < 100; d++) {
-
String item = "('%s','%d','2020-07-%d 00:00:00','%d')";
-
StringBuilder sql = new StringBuilder("INSERT INTO t_order(order_id,amount,payment_time,order_status) VALUES ");
-
for (int i = 0; i < 20_000; i++) {
-
sql.append(String.format(item, UUID.randomUUID().toString().replace("-", ""),
-
AR.nextInt(100000) + 1, DR.nextInt(31) + 1, OR.nextInt(3))).append(",");
-
}
-
jdbcTemplate.update(sql.substring(0, sql.lastIndexOf(",")));
-
}
-
hikariDataSource.close();
-
}
-
}
基于JdbcTemplate编写DAO类OrderDao:
-
@RequiredArgsConstructor
-
@Repository
-
public class OrderDao {
-
private final JdbcTemplate jdbcTemplate;
-
public List<Order> queryByScrollingPagination(long lastBatchMaxId,
-
int limit,
-
LocalDateTime paymentDateTimeStart,
-
LocalDateTime paymentDateTimeEnd) {
-
return jdbcTemplate.query("SELECT * FROM t_order WHERE id > ? AND payment_time >= ? AND payment_time <= ? " +
-
"ORDER BY id ASC LIMIT ?",
-
p -> {
-
p.setLong(1, lastBatchMaxId);
-
p.setTimestamp(2, Timestamp.valueOf(paymentDateTimeStart));
-
p.setTimestamp(3, Timestamp.valueOf(paymentDateTimeEnd));
-
p.setInt(4, limit);
-
},
-
rs -> {
-
List<Order> orders = new ArrayList<>();
-
while (rs.next()) {
-
Order order = new Order();
-
order.setId(rs.getLong("id"));
-
order.setCreator(rs.getString("creator"));
-
order.setEditor(rs.getString("editor"));
-
order.setCreateTime(OffsetDateTime.ofInstant(rs.getTimestamp("create_time").toInstant(), ZoneId.systemDefault()));
-
order.setEditTime(OffsetDateTime.ofInstant(rs.getTimestamp("edit_time").toInstant(), ZoneId.systemDefault()));
-
order.setVersion(rs.getLong("version"));
-
order.setDeleted(rs.getInt("deleted"));
-
order.setOrderId(rs.getString("order_id"));
-
order.setAmount(rs.getBigDecimal("amount"));
-
order.setPaymentTime(OffsetDateTime.ofInstant(rs.getTimestamp("payment_time").toInstant(), ZoneId.systemDefault()));
-
order.setOrderStatus(rs.getInt("order_status"));
-
orders.add(order);
-
}
-
return orders;
-
});
-
}
-
}
编写服务类OrderService:
-
@Data
-
public class OrderDTO {
-
@ExcelIgnore
-
private Long id;
-
@ExcelProperty(value = "订单号", order = 1)
-
private String orderId;
-
@ExcelProperty(value = "金额", order = 2)
-
private BigDecimal amount;
-
@ExcelProperty(value = "支付时间", order = 3)
-
private String paymentTime;
-
@ExcelProperty(value = "订单状态", order = 4)
-
private String orderStatus;
-
}
-
@Service
-
@RequiredArgsConstructor
-
public class OrderService {
-
private final OrderDao orderDao;
-
private static final DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
-
public List<OrderDTO> queryByScrollingPagination(String paymentDateTimeStart,
-
String paymentDateTimeEnd,
-
long lastBatchMaxId,
-
int limit) {
-
LocalDateTime start = LocalDateTime.parse(paymentDateTimeStart, F);
-
LocalDateTime end = LocalDateTime.parse(paymentDateTimeEnd, F);
-
return orderDao.queryByScrollingPagination(lastBatchMaxId, limit, start, end).stream().map(order -> {
-
OrderDTO dto = new OrderDTO();
-
dto.setId(order.getId());
-
dto.setAmount(order.getAmount());
-
dto.setOrderId(order.getOrderId());
-
dto.setPaymentTime(order.getPaymentTime().format(F));
-
dto.setOrderStatus(OrderStatus.fromStatus(order.getOrderStatus()).getDescription());
-
return dto;
-
}).collect(Collectors.toList());
-
}
-
}
最后编写控制器OrderController:
-
@RequiredArgsConstructor
-
@RestController
-
@RequestMapping(path = "/order")
-
public class OrderController {
-
private final OrderService orderService;
-
@GetMapping(path = "/export")
-
public void export(@RequestParam(name = "paymentDateTimeStart") String paymentDateTimeStart,
-
@RequestParam(name = "paymentDateTimeEnd") String paymentDateTimeEnd,
-
HttpServletResponse response) throws Exception {
-
String fileName = URLEncoder.encode(String.format("%s-(%s).xlsx", "订单支付数据", UUID.randomUUID().toString()),
-
StandardCharsets.UTF_8.toString());
-
response.setContentType("application/force-download");
-
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
-
ExcelWriter writer = new ExcelWriterBuilder()
-
.autoCloseStream(true)
-
.excelType(ExcelTypeEnum.XLSX)
-
.file(response.getOutputStream())
-
.head(OrderDTO.class)
-
.build();
-
// xlsx文件上上限是104W行左右,这里如果超过104W需要分Sheet
-
WriteSheet writeSheet = new WriteSheet();
-
writeSheet.setSheetName("target");
-
long lastBatchMaxId = 0L;
-
int limit = 500;
-
for (; ; ) {
-
List<OrderDTO> list = orderService.queryByScrollingPagination(paymentDateTimeStart, paymentDateTimeEnd, lastBatchMaxId, limit);
-
if (list.isEmpty()) {
-
writer.finish();
-
break;
-
} else {
-
lastBatchMaxId = list.stream().map(OrderDTO::getId).max(Long::compareTo).orElse(Long.MAX_VALUE);
-
writer.write(list, writeSheet);
-
}
-
}
-
}
-
}
这里为了方便,把一部分业务逻辑代码放在控制器层编写,实际上这是不规范的编码习惯,这一点不要效仿。添加配置和启动类之后,通过请求http://localhost:10086/order/export?paymentDateTimeStart=2020-07-01 00:00:00&paymentDateTimeEnd=2020-07-16 00:00:00测试导出接口,某次导出操作后台输出日志如下:
导出数据耗时:29733 ms,start:2020-07-01 00:00:00,end:2020-07-16 00:00:00
导出成功后得到一个文件(连同表头一共1031540行):
小结
这篇文章详细地分析大数据量导出的性能优化,最要侧重于内存优化。该方案实现了在尽可能少占用内存的前提下,在效率可以接受的范围内进行大批量的数据导出。这是一个可复用的方案,类似的设计思路也可以应用于其他领域或者场景,不局限于数据导出。
文中demo项目的仓库地址是:
- Github:https://github.com/zjcscut/spring-boot-guide/tree/master/ch10086-excel-export
(本文完 c-2-d e-a-20200820 20:27 PM)
-
使用easyExcel导出百万级数据
2019-07-17 18:55:321:测试数据量两百二十多万,Excel2007,一个Excel多个sheet 2:导入maven依赖包 <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <...1:测试数据量两百二十多万,Excel2007,一个Excel多个sheet
2:导入maven依赖包
<dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>1.1.2-beta5</version> </dependency>
3:实体类添加注解。注意事项:1:必须继承BaseRowModel。2:@ExcelProperty必须写,其中value是要导出到Excel的表头名字自己起,index:代表某一列从0开始排序,由于实体类字段比较多,所以在导出时会自动忽略没有加注解的字段
public class TaskStoreItemDTO extends BaseRowModel{ @ExcelProperty(value = "id",index = 0) private String id; @ExcelProperty(value = "replenish_item_store_id",index = 1) private String replenishItemStoreId; @ExcelProperty(value = "tab_nbr",index = 2) private String tabNbr; }
4:业务层书写。1:aa:代表要导出的路径:例:D:/test/.....随意。2:writer.write()方法可以改变,因为我的是两百万的数据所以这么写,你要是少可以把代码改一下把百万级中分sheet的代码删除掉。下边给大家截图修改那些。10000:这个是一个sheet要展示多少条数据
//查询数据库返回的数据集合 List<TaskStoreItemDTO> exportList = mapper.export(taskId); //导出开始,生成EXCEL并指定输出路径 OutputStream out=null; try { String aa=serviceConfig.getExportFeedbackPath()+"任务信息详情表.xlsx"; out = new FileOutputStream(aa); ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX); //测试导出200万数据 //List<TaskStoreItemDTO> exportList = mapper.test(); List<List<TaskStoreItemDTO>> taskStoreItemList = Lists.partition(exportList, 1000000); //一个sheet一百万调数据,自动分页 for(int i=0;i<taskStoreItemList.size();i++) { // 设置SHEET Sheet sheet = new Sheet((i+1), 0,TaskStoreItemDTO.class,"sheet"+(i+1),null); writer.write(taskStoreItemList.get(i), sheet); } writer.finish(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally { try { out.flush(); out.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
5:可以修改的数据
第一张截图是在修改第二张截图后的操作:三个参数:1一个sheet,从0行开始,继承BaseRowModel的实体类,必须写不然找不到要导出的表头。下边是设置这个sheet的名字。在修改第二张截图后writer.write()方法可以改变,自己writer.的时候能用的方法就会展示。自己选择适合自己的就可以了。
6:导出结果,数据就不展示了
7:由于easyExcel是阿里巴巴的所以还可去查看官方给的例子。给大家粘贴几个写的比较好的导出地址,如有问题欢迎大家留言交流。
https://www.cnblogs.com/technologykai/articles/10984878.html
https://blog.csdn.net/Java_Mrsun/article/details/85678028
本博客只供大家学习交流,禁止使用在其他用途。对于使用在其他用途的自行承担责任与本博客无关,最终解释权归本人所有
-
SpringBoot分片上传Excel大文件,支持断点续传,EasyExcel处理百万级数据
2020-07-05 16:24:31前端Excel大文件file slice分片,md5校验文件完整性并作文件标识记录写入数据库,支持断点...文件上传完毕后,使用EasyExcel读取文件流,处理Excel数据写入数据库中,可处理百万级数据。项目完整,连接数据库即可运行。 -
java使用EasyExcel导出百万数据
2021-02-24 14:33:50Maven依赖如下: <dependency>...easyexcel</artifactId> <version>2.1.1</version> </dependency> <dependency> <groupId>org.apache.poi</gMaven依赖如下:
<dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>2.1.1</version> </dependency> <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>
Controller:
package com.excel.demo.controller; import com.alibaba.excel.EasyExcel; import com.excel.demo.vo.LedgerreRealFeeVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; /** * @author :Hj * @date 2021/2/24 10:50 */ @RestController @RequestMapping("/person") public class PersonControl { @Autowired private LedgerRealFeeService feeService; @RequestMapping(value = "/getData") public String test(){ //获取数据 List<LedgerreRealFeeVo> realFeeVos= feeService.getDataVo(); String filename = "D:\\Data\\Data.xlsx"; long start = System.currentTimeMillis(); //关键代码 EasyExcel.write(filename,LedgerreRealFeeVo.class).sheet("数据").doWrite(realFeeVos); long finish = System.currentTimeMillis(); long timeElapsed=finish-start; System.out.print(timeElapsed); return "sucess"; } }
VO:
package com.excel.demo.vo; import com.alibaba.excel.annotation.ExcelProperty; import java.math.BigDecimal; import java.util.Date; /** * @author :Hj * @date 2021/2/24 10:47 */ public class LedgerreRealFeeVo { @ExcelProperty(value = "业务号") private Long sno; @ExcelProperty(value = "任务号") private String taskId; @ExcelProperty(value = "类型") private String certiType; @ExcelProperty(value = "人员代码") private String agentCode; @ExcelProperty(value = "机构") private String comCode; @ExcelProperty(value = "业务性质") private String businessNature; @ExcelProperty(value = "团队代码") private String teamCode; @ExcelProperty(value = "团队名称") private String teamName; @ExcelProperty(value = "日期") private Date realPayDate; @ExcelProperty(value = "afee") private BigDecimal afee; @ExcelProperty(value = "bfee") private BigDecimal bfee; @ExcelProperty(value = "cfee") private BigDecimal cfee; public Long getSno() { return sno; } public void setSno(Long sno) { this.sno = sno; } public String getTaskId() { return taskId; } public void setTaskId(String taskId) { this.taskId = taskId; } public String getCertiType() { return certiType; } public void setCertiType(String certiType) { this.certiType = certiType; } public String getAgentCode() { return agentCode; } public void setAgentCode(String agentCode) { this.agentCode = agentCode; } public String getComCode() { return comCode; } public void setComCode(String comCode) { this.comCode = comCode; } public String getBusinessNature() { return businessNature; } public void setBusinessNature(String businessNature) { this.businessNature = businessNature; } public String getTeamCode() { return teamCode; } public void setTeamCode(String teamCode) { this.teamCode = teamCode; } public String getTeamName() { return teamName; } public void setTeamName(String teamName) { this.teamName = teamName; } public Date getRealPayDate() { return realPayDate; } public void setRealPayDate(Date realPayDate) { this.realPayDate = realPayDate; } public BigDecimal getAfee() { return afee; } public void setAfee(BigDecimal afee) { this.afee = afee; } public BigDecimal getBfee() { return bfee; } public void setBfee(BigDecimal bfee) { this.bfee = bfee; } public BigDecimal getCfee() { return cfee; } public void setCfee(BigDecimal cfee) { this.cfee = cfee; } }
1000960行数据耗时:
-
EasyExcel导出百万级连表查询xlsx数据方法实测102万105秒
2020-08-04 17:08:23对于百万级的数据如果一下查出来是会内存溢出的,所以需要分页查出来,分页放到excel表格中 直接上我写的代码:excel表头类就不沾出来了,每个人的都不一样 /** * 导出excel * */ @RequestMapping("/export") @... -
xlsx百万级模板数据导出
2019-02-14 22:59:35xlsx百万级模板数据导出,可以使用8M内存模板导出excel. easypoi和hutool对07版本的excel都没有提供模板导出,因此个人封装了下(原创)。 目前支持的模板表达式有 #if:表达式不成立则不输出#if #end中的行 #each ... -
利用easyExcel导出上万条数据,自定义策略合并单元格
2020-12-04 12:45:30easyExcel 2.1.7 poi 3.17 springboot 2.2.5 lombok 1.18.12 -
使用EasyExcel进行百万数据文件导出思路
2022-07-12 15:57:00EasyExcel文件导出 -
easyExcel实现大数据导出
2019-11-13 09:55:05阿里巴巴easyExcel实现大数据导出!! -
easyExcel百万数据导出测试
2021-07-19 00:14:16System.out.println(“导出完成”); } catch (Exception e) { e.printStackTrace(); } } public void download() throws Exception { int totalCount= 2000001;// 查询数据库数据总量,假设 200万数据,分2个sheet... -
EasyExcel 百万级别数据高效率导入/导出
2022-07-25 17:53:59EasyExcel 百万级别数据高效率导入/导出 -
【EasyExcel导入、导出(百万数据量测试)粘贴即用】
2022-02-28 10:41:14需求:使用excel一次导入100万条数据,分页导出100万条数据并生成excel文件 导入流程:用户上传文件-->后端获取文件流-->侦听器读取文件-->批量插入数据库 导出流程:用户点击按钮-->调用后端接口-->分页查询需要... -
EasyExcel导出.txt
2021-11-04 11:53:48EasyExcel 导出,含动态表头导出 -
使用EasyExcel导出百万数据报错 GC overhead limit exceeded
2021-02-24 11:12:15调整idea运行内存大小 1、右击快捷方式,打开文件所在位置 2、修改idea.exe.vmoptions配置 -
阿里easyexcel 2.x实现百万级海量数据效率导入导出demo
2021-12-13 11:53:09https://github.com/wojozer/easyexcel-demo -
easyExcel导出合并单元格策略
2021-12-21 17:12:32easyExcel导出合并单元格策略 WriteSheet writeSheet = EasyExcel.writerSheet(i, "Sheet" + (i + 1)) .registerWriteHandler(new CustomCellWriteHandler()) //设置合并单元格策略 .registerWriteHandler(new ... -
百万级数据EXCEL导出--easyexcel
2019-01-03 10:06:51easyexcel 项目git地址为: https://github.com/alibaba/easyexcel 官方使用指南见: https://github.com/alibaba/easyexcel/blob/master/quickstart.md 官方介绍:Java解析、生成Excel比较有名的框架有Apache poi... -
记录一次Easy Excel 导出百万级数据功能优化及分页查询sql优化
2022-03-02 10:32:36记录一次Easy Excel 导出百万级数据功能优化及分页查询sql优化 前言 业务需求,需要一起将40多万条数据导出成 Excel。所以在技术选型上就是用了阿里巴巴的 Easy Excel ,因为官网是这么介绍的EasyExcel是一个... -
阿里EasyExcel导出案例
2022-01-19 14:24:45阿里EasyExcel导出案例 -
多线程导出Excel(百万级别)_Java版优化.zip
2019-09-10 22:39:38用开源 Apache POI 技术导出Excel,解决导出大数据出现OOM、栈溢出问题,此资源可实现百万级数据多线程分批导出Excel文件,不会内存溢出,生产环境已很稳定的使用者,所以用到的技术很核心、值得参考 -
EasyExcel导出动态表头处理
2022-03-26 16:20:23以前做导出功能,表头和数据都是固定的。使用EasyExcel实体类上注解的方式就是。比如这样 @Getter @Setter @EqualsAndHashCode public class DemoData { @ExcelProperty("姓名") private String name; @... -
easyExcel 导出海量数据
2021-09-28 17:11:20easyExcel 导出百万数据 -
SpringBoot整合easyExcel——数据导出
2022-03-25 13:29:07基于easyExcel数据导出功能的实现及部分注解的补充 -
Springboot导出百万级数据到excel
2022-03-16 13:16:28最近项目有导出数据到excel功能,于是觉得很轻松的写了下面代码 @ApiOperation(value = "万级数据导出") @RequestMapping(value = "/export1",method = RequestMethod.GET) public void exportExcel1... -
EasyExcel导出大量数据
2021-08-25 11:35:111:EasyExcel导出数据 2:查询时分sheet页查询写入 3:自定义导出格式 1:pom文件: <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> &... -
踩坑:EasyExcel导出excel导出数据为空
2021-11-24 17:22:42我再java代码中导出excel导出数据为空如下图 问题探究 经过断点排查数据 发现数据取出来了 那么问题就不在取数据而在数据写入部分 经过排查翻阅博客发现了这么一句话 读写反射对象用到了Cglib,所以成员变量... -
easyExcel千万级别数据下载
2021-12-27 09:56:40基于EasyExcel进行大数据文件下载。