Springboot 那年我双手插兜,手写一个excel导出
前言
其实就是利用了csv 和txt 文件转换 。
不多说,开始玩代码。
正文
本篇内容:
① 了解根本生成excel内容的CSV文件玩法
② 手动拼接文本演示
③ 项目内实战写法,从数据库到导出
④ 解决list数据过多,使用分批分页处理生成csv (EXCEL)
思路:
创建csv文件, 往里面写入符合转换成csv文件的内容 即可。
① 了解根本生成excel内容的CSV文件玩法
先看看什么原理 :
首先我们创建一个csv文件
然后打开里面填充一些数据:
然后反手把文件后缀改成.txt 看看里面是啥 :
看看里面:
② 手动拼接文本演示
工具类 :
MyCsvFileUtil.java
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;@Slf4j
public class MyCsvFileUtil {public static final String CSV_DELIMITER = ",";public static final String CSV_TAIL = "\r\n";/*** 将字符串转成csv文件*/public static void createCsvFile(String savePath,String contextStr) throws IOException {File file = new File(savePath);//创建文件file.createNewFile();//创建文件输出流FileOutputStream fileOutputStream = new FileOutputStream(file);//将指定字节写入此文件输出流fileOutputStream.write(contextStr.getBytes("gbk"));fileOutputStream.flush();fileOutputStream.close();}/*** 写文件** @param fileName* @param content*/public static void writeFile(String fileName, String content) {FileOutputStream fos = null;OutputStreamWriter writer = null;try {fos = new FileOutputStream(fileName, true);writer = new OutputStreamWriter(fos, "GBK");writer.write(content);writer.flush();} catch (Exception e) {log.error("写文件异常|{}", e);} finally {if (fos != null) {IOUtils.closeQuietly(fos);}if (writer != null) {IOUtils.closeQuietly(writer);}}}
演示拼接调用工具类生成CSV文件:
@RequestMapping("/createCsvFileTest")public void doTest() throws IOException {//存放地址String path = "D:\\mycsv\\test.csv";String word = "";//表头固定好String tableNames = "CODE,NAME,PARENT_CODE,FULL_NAME";//数据内容。String oneRows = "110100,北京市,110000,北京北京市";String twoRows = "110101,东城区,110100,北京北京市东城区";String threeRows = "110102,西城区,110100,北京北京市西城区";String fourRows = "110105,朝阳区,110100,北京北京市朝阳区";//拼接word += tableNames + "\r\n";word += oneRows+ "\r\n";word += twoRows+ "\r\n";word += threeRows+ "\r\n";word += fourRows+ "\r\n";//调用方法生成MyCsvFileUtil.createCsvFile(path,word);}
代码简析:
调用这个示例接口,看看效果:
③ 项目内实战写法,从数据库到导出
接口使用写法:
@RequestMapping("/createCsvFileTest2")public void createCsvFileTest2() throws IOException {List<District> districts = districtMapper.queryByParentCodes(Arrays.asList("110100"));//存放地址&文件名String fileName = "D:\\mycsv\\test2.csv";String tableNames = "CODE,NAME,PARENT_CODE,FULL_NAME"+MyCsvFileUtil.CSV_DELIMITER;//创建文件MyCsvFileUtil.createCsvFile(fileName,tableNames);//写入数据String contentBody =buildCsvFileBody(districts);//调用方法生成MyCsvFileUtil.createCsvFile(fileName,contentBody);}
解析数据list 做内容拼接处理 函数:
ps: 表头 多少个,字段就多少个, 默认值赋值啥的,格式转换啥的都可以,只要每一行数量对的上即可,每一个最后结尾的标记就是 String CSV_TAIL = "\r\n"
@RequestMapping("/createCsvFileTest2")public void createCsvFileTest2() throws IOException {List<District> districts = districtMapper.queryByParentCodes(Arrays.asList("110100"));//存放地址&文件名String fileName = "D:\\mycsv\\test2.csv";String tableNames = "CODE,NAME,PARENT_CODE,FULL_NAME"+MyCsvFileUtil.CSV_TAIL;//创建文件MyCsvFileUtil.writeFile(fileName,tableNames);//写入数据String contentBody =buildCsvFileBody(districts);//调用方法生成MyCsvFileUtil.writeFile(fileName,contentBody);}
private String buildCsvFileBody(List<District> dataLists) {StringBuilder lineBuilder = new StringBuilder();for (District rowData : dataLists) {lineBuilder.append(rowData.getCode()).append(MyCsvFileUtil.CSV_DELIMITER).append(rowData.getName()).append(MyCsvFileUtil.CSV_DELIMITER).append(rowData.getParentCode()).append(MyCsvFileUtil.CSV_DELIMITER).append(rowData.getFullName()).append(MyCsvFileUtil.CSV_DELIMITER).append(MyCsvFileUtil.CSV_TAIL);}return lineBuilder.toString();}
事不宜迟,看看接口调用效果:
可以看到从数据量里面查出来这 16条数据,list集合:
最后转换生成文件:
可以看到成功出货:
那么问题来了,如果我们需要导出的数据条数很多,拼接的contentBody 会非常长。
那么我们就需要考虑分批查询、分批拼接处理、分批写入,按照实际业务场景和数据长度去考量,每一批的限制。 甚至还可以实现拼接好每一批,然后慢慢再根据当前批次ID,做顺序写入。
④ 解决list数据过多,使用分批分页处理生成csv (EXCEL)
抛转引玉,给大家整个简单的分批玩法,生成csv 。
改造点 1 , 整一个分页查询。
老面孔手动分页DTO:
import lombok.Data;/*** @Author: JCccc* @Date: 2022-6-15 16:53* @Description:*/
@Data
public class PageLimitDTO {private Integer pageSize;private Integer currIndex;}
工具类 MyPageCutUtil.java
import lombok.extern.slf4j.Slf4j;import java.util.LinkedList;
import java.util.List;@Slf4j
public class MyPageCutUtil {public static List<PageLimitDTO> getPageLimitGroupList(Integer totalCount, Integer batchSizeLimit) {log.info("这一次处理的总数据条数为 ={} 条, 每一批次处理条数为 ={} 条,现在开始做分批切割处理。",totalCount,batchSizeLimit);int pageNum = totalCount / batchSizeLimit;int surplus = totalCount % batchSizeLimit;if (surplus > 0) {pageNum = pageNum + 1;}List<PageLimitDTO> pageLimitGroupList =new LinkedList<>();for(int i = 0; i < pageNum; i++){Integer currIndex = i * batchSizeLimit;PageLimitDTO pageLimitDTO=new PageLimitDTO();pageLimitDTO.setPageSize(batchSizeLimit);pageLimitDTO.setCurrIndex(currIndex);pageLimitGroupList.add(pageLimitDTO);log.info("分批切割,第={}次,每次={}条,最终会处理到={}条。",pageLimitGroupList.size(),batchSizeLimit,currIndex+batchSizeLimit);}log.info("这一次处理的总数据条数为 ={} 条, 每一批次处理条数为 ={} 条,总共切割分成了 ={} 次,一切准备就绪。",totalCount,batchSizeLimit,pageLimitGroupList.size());return pageLimitGroupList;}}
老面孔手动分页统计sql :
mapper 简单打个样
/*** 统计所有符合搜索条件的数据* @param codeList* @return*/ int getCountAllList(@Param("codeList") List<String> codeList);
对应xml 的sql
<select id="getCountAllList" resultType="java.lang.Integer">SELECTCOUNT(*)FROM district_infoWHERE PARENT_CODE IN<foreach collection="codeList" item="code" open="(" separator="," close=")">#{code}</foreach></select>
老面孔手动分页查询sql:
/*** 手动分页查询* @param codeList* @param currIndex* @param pageSize* @return*/ List<District> getPageList(@Param("codeList") List<String> codeList,Integer currIndex,Integer pageSize);
sql:
<select id="getPageList" resultMap="BaseResultMap">SELECT<include refid="Base_Column_List"/>FROM district_info<where><if test="codeList != null and !codeList.isEmpty()">PARENT_CODE IN<foreach collection="codeList" item="code" open="(" separator="," close=")">#{code}</foreach></if></where>LIMIT #{currIndex} , #{pageSize} </select>
ok接下来继续改造我们的分批查询,处理数据:
@RequestMapping("/createCsvFileTest3")public void createCsvFileTest3() throws IOException {//存放地址&文件名String fileName = "D:\\mycsv\\test3.csv";String tableNames = "地域编码,地域名称,父级编码,地域全称" + MyCsvFileUtil.CSV_TAIL;//创建文件MyCsvFileUtil.writeFile(fileName, tableNames);//获取数据总计数Integer totalCount = districtMapper.getCountAllList(null);//每批同步的数据条数Integer batchSizeLimit = 500;//分批切割处理List<PageLimitDTO> pageLimitGroupList = MyPageCutUtil.getPageLimitGroupList(totalCount, batchSizeLimit);int count = 1;//物理批次查询for (PageLimitDTO pageBatchLimit : pageLimitGroupList) {List<District> pageBatchList = districtMapper.getPageList(null, pageBatchLimit.getCurrIndex(), pageBatchLimit.getPageSize());if (!CollectionUtils.isEmpty(pageBatchList)) {//写入数据String contentBody = buildCsvFileBody(pageBatchList);//调用方法生成MyCsvFileUtil.writeFile(fileName, contentBody);}log.info("第{}批次,District数据处理结束执行", count);count = count + 1;}}
代码简析:
OK,调用一下接口,看看效果:
打开看看数据:
ps:还有没有优化封装余地?
有的,文中讲到了,还可以考虑加上整个大批次的ID,然后考虑并行查询,并行拼接后,再按顺序插入。
有想法的还可以写个注解,标记相关表头别名,是否参与导出,然后再拼接的时候魔改一手,反射自动拿字段属性等等。
好了该篇就到这。
Springboot 那年我双手插兜,手写一个excel导出相关推荐
- vue @click 赋值_vue 手写一个时间选择器
vue 手写一个时间选择器 最近研究了 DatePicker 的实现原理后做了一个 vue 的 DatePicker 组件,今天带大家一步一步实现 DatePicker 的 vue 组件. 原理 Da ...
- 肝一波 ~ 手写一个简易版的Mybatis,带你深入领略它的魅力!
零.准备工作 <dependencies><dependency><groupId>mysql</groupId><artifactId>m ...
- 俄罗斯小方块游戏html,通过h5的canvas手写一个俄罗斯方块小游戏
开始自己手写一个好玩的俄罗斯方块吧,上变形,左右移动,下加速,空格瞬移等功能,无聊的时候学习下canvas,f12 修改分数,体验金手指的快乐吧 1.定义界面,和按钮 上 下 左 右 2.js部分 1 ...
- zookeeper springboot_摊牌了!我要手写一个“Spring Boot”
❝ 目前的话,已经把 Spring MVC 相关常用的注解比如@GetMapping .@PostMapping .@PathVariable 写完了.我也已经将项目开源出来了,地址:https:// ...
- 摊牌了!我要手写一个“Spring Boot”
目前的话,已经把 Spring MVC 相关常用的注解比如@GetMapping .@PostMapping .@PathVariable 写完了.我也已经将项目开源出来了,地址:https://gi ...
- 手写一个原神祈愿分析工具
手写一个原神祈愿分析工具 之前一直通过游创工坊来进行祈愿抽卡数据分析,但是广告太多,而且担心auth_key泄露,于是自己花了一天时间动手实现了个数据分析工具,数据永久保存在本地,没有信息泄露风险,话 ...
- Java 手写一个SQL分页
Java手写一个类似PageHelper的分页SQL 目前分页插件众所周知的莫过于和mybatis完美融合的PageHelper了,简单两行代码就实现了sql分页,配合PageInfo类,将数据总数量 ...
- 【干货】JDK动态代理的实现原理以及如何手写一个JDK动态代理
动态代理 代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中,更是有举足轻重的地位.代理模式从类型上来说,可以分为静态代理和动态代理两种类型. 在解 ...
- 硬核!手写一个优先队列
文章收录在首发公众号:bigsai 期待你的到访! 前言 事情还要从一个故事讲起: 对于上面那只可爱的小狗狗不会,本篇即为该教程,首先,我要告诉这只可爱的小狗狗,这种问题你要使用的数据结构为优先队列, ...
最新文章
- WEB初学者简介,web入门
- C语言 | 变量的存储方式
- 比较创建几种线程的方式
- vue 圆形 水波_vue项目百度地图+echarts的涟漪水波效果
- leetcode - 368. 最大整除子集
- 数学建模之图论——图与网络模型(二)(最小生成树问题、最大流问题)
- 如何导入asl文件?ps制作知识
- 100篇文献-万字总结 || 强化学习求解车间调度
- JavaScript成都市地图网页代码
- 思岚A1激光雷达调试
- 微信小程序实现蓝牙开门前后端项目(一)
- wow私服,arcemu trunk源码编译架设
- 解决LS-DYNA中负体积方法
- MTK keypad调试,扩张键盘IC AW9523
- VLOOKUP函数最常用的10种用法
- Redisson time out解决
- 用HTML+bootstrap制作个人简历
- 通过ONIE安装NOS系统
- 运筹说 第10期|敲黑板!学习运筹学,怎么能不知道相关的运筹学组织呢?
- 虚拟同步发电机_湖南大学涂春鸣等:具备同步电机特性的级联型光伏发电系统...
热门文章
- skype免费拨打规则
- Java小程序:分别计算1-100内的奇数和偶数的和
- 你的mysql加索引了吗
- c语言以空格分割字符串_c语言中,输入任意字符串,任意空格隔开
- 发布职位:智行者科技idriverplus#d轮结束,即将ipobase:北广鄂渝技术路线:激光雷达+摄像头+高精地图前端后端移动端大数据感知嵌入式算法
- selenium+Webdriver+jsoup爬虫 java
- Git 基础之凌波微步
- 如何快速搭建私人博客
- C语言正则表达式库RegEx库
- Android系统连按5次电源键,连按5次手机电源键竟有这个功能,紧急情况很有用,大家都该知道...