融易宝项目之EasyExcel和数据字典的使用
融易寶項目之EasyExcel和數據字典的使用
歡迎關注微信公眾號:序輯
一、Alibaba EasyExcel
1.EasyEscel簡介
1.1Excel導入導出的應用場景
數據導入 減輕錄入工作量
數據導出 統計信息歸檔
數據傳輸 異構系統之間數據傳輸
1.2EasyExcel簡介
常見excel分解框架:POL、EasyExcel
1.2.1官方網站
https://github.com/alibaba/easyexcel
快速開始:
https://www.yuque.com/easyexcel/doc/easyexcel
1.2.2EasyExcel特點
1.Java領域解析、生成Excel 比較有名的框架有Apache poi、jxl等。但他們都存在一個嚴重的問題就是非常的耗內存。如果你的系統并發量不大的話可能還行,但是一旦并發上來后一定會OOM或者JVM頻繁的full gc。
2.EasyExcel是阿里巴巴開源的一個excel處理框架,以使用簡單、節省內存著稱。EasyExcel能大大減少占用內存的主要原因是在解析Excel時沒有將文件數據一次性全部加載到內存中,而是從磁盤上一行行讀取數據,逐個解析。
3.EasyExcel采用一行一行的解析模式,并將一行的解析結果以觀察者的模式通知處理(AnalysisEventListener)。
2.讀寫Excel
2.1創建項目
2.1.1創建一個普通的maven項目
項目名:alibaba-easyexcel
2.1.2pom中引入xml相關依賴
<dependencies><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.1.7</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>1.7.5</version></dependency><dependency><groupId>org.apache.xmlbeans</groupId><artifactId>xmlbeans</artifactId><version>3.1.0</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.12</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency></dependencies>2.2寫
2.2.1創建實體類
package com.aojiaoge.easyexcel.dto;@Data public class ExcelStudentDTO {@ExcelProperty("姓名")private String name;@ExcelProperty("生日")private Date birthday;@ExcelProperty("薪資")private Double salary; }2.2.2最簡單的寫
package com.aojiaoge.easyexcel; public class ExcelWriteTest {@Testpublic void simpleWriteXlsx() {String fileName = "d:/excel/simpleWrite.xlsx"; //需要提前新建目錄// 這里 需要指定寫用哪個class去寫,然后寫到第一個sheet,名字為模板 然后文件流會自動關閉EasyExcel.write(fileName, ExcelStudentDTO.class).sheet("模板").doWrite(data());}//輔助方法private List<ExcelStudentDTO> data(){List<ExcelStudentDTO> list = new ArrayList<>();//算上標題,做多可寫65536行//超出:java.lang.IllegalArgumentException: Invalid row number (65536) outside allowable range (0..65535)for (int i = 0; i < 65535; i++) {ExcelStudentDTO data = new ExcelStudentDTO();data.setName("Helen" + i);data.setBirthday(new Date());data.setSalary(123456.1234);list.add(data);}return list;} }2.2.3不同版本的寫
@Test public void simpleWriteXls() {String fileName = "d:/excel/simpleWrite.xls";// 如果這里想使用03 則 傳入excelType參數即可EasyExcel.write(fileName, ExcelStudentDTO.class).excelType(ExcelTypeEnum.XLS).sheet("模板").doWrite(data()); }2.2.4寫入大數據量
xls版本的Excel最多一次可寫0-65535行
xlsx版本的Excel最多一次可寫0-104875行
2.3讀
2.3.1創建監聽器
package com.aojiaoge.easyexcel.listener;@Slf4j public class ExcelStudentDTOListener extends AnalysisEventListener<ExcelStudentDTO> {/*** 這個每一條數據解析都會來調用*/@Overridepublic void invoke(ExcelStudentDTO data, AnalysisContext context) {log.info("解析到一條數據:{}", data);}/*** 所有數據解析完成了 都會來調用*/@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {log.info("所有數據解析完成!");} }2.3.2測試用例
package com.aojiaoge.easyexcel;public class ExcelReadTest {/*** 最簡單的讀*/@Testpublic void simpleReadXlsx() {String fileName = "d:/excel/simpleWrite.xlsx";// 這里默認讀取第一個sheetEasyExcel.read(fileName, ExcelStudentDTO.class, new ExcelStudentDTOListener()).sheet().doRead();}@Testpublic void simpleReadXls() {String fileName = "d:/excel/simpleWrite.xls";EasyExcel.read(fileName, ExcelStudentDTO.class, new ExcelStudentDTOListener()).excelType(ExcelTypeEnum.XLS).sheet().doRead();} }二、數據字典
1.數據字典的設計
1.1什么是數據字典
何為數據字典?數據字典負責管理系統常用的分類數據或者一些固定數據,例如:省市區三級聯動數據、民族數據、行業數據、學歷數據等,數據字典幫助我們方便的獲取和適用這些通用數據。
1.2數據字典的設計
1.parent_id:上級id,通過id與parent_id構建上下級關系,例如:我們要獲取所有行業數據,那么只需要查詢parent_id=20000的數據
2.name: 名稱,例如:填寫用戶信息,我們要select標簽選擇民族,“漢族”就是數據字典的名稱
3.value: 值,例如:填寫用戶信息,我們要select標簽選擇民族,“1”(漢族的標識)就是數據字典的值
4.dict_code: 編碼,編碼是我們自定義的,全局唯一,例如:我們要獲取行業數據,我們可以通過parent_id獲取,但是parent_id是不確定的,所以我們可以根據編碼來獲取行業數據
2.Excel數據批量導入
2.1后端接口
2.1.1添加依賴
core中添加以下依賴
<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId> </dependency><dependency><groupId>org.apache.xmlbeans</groupId><artifactId>xmlbeans</artifactId> </dependency>2.1.2創建Excel實體類
package com.aojiaoge.srb.core.pojo.dto; @Data public class ExcelDictDTO {@ExcelProperty("id")private Long id;@ExcelProperty("上級id")private Long parentId;@ExcelProperty("名稱")private String name;@ExcelProperty("值")private Integer value;@ExcelProperty("編碼")private String dictCode; }2.1.3創建監聽器
package com.aojaioge.srb.core.listener;@Slf4j //@AllArgsConstructor //全參 @NoArgsConstructor //無參 public class ExcelDictDTOListener extends AnalysisEventListener<ExcelDictDTO> {/*** 每隔5條存儲數據庫,實際使用中可以3000條,然后清理list ,方便內存回收*/private static final int BATCH_COUNT = 5;List<ExcelDictDTO> list = new ArrayList();private DictMapper dictMapper;//傳入mapper對象public ExcelDictDTOListener(DictMapper dictMapper) {this.dictMapper = dictMapper;}/***遍歷每一行的記錄* @param data* @param context*/@Overridepublic void invoke(ExcelDictDTO data, AnalysisContext context) {log.info("解析到一條記錄: {}", data);list.add(data);// 達到BATCH_COUNT了,需要去存儲一次數據庫,防止數據幾萬條數據在內存,容易OOMif (list.size() >= BATCH_COUNT) {saveData();// 存儲完成清理 listlist.clear();}}/*** 所有數據解析完成了 都會來調用*/@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {// 這里也要保存數據,確保最后遺留的數據也存儲到數據庫saveData();log.info("所有數據解析完成!");}/*** 加上存儲數據庫*/private void saveData() {log.info("{}條數據,開始存儲數據庫!", list.size());dictMapper.insertBatch(list); //批量插入log.info("存儲數據庫成功!");} }2.1.4Mapper層批量插入
接口:DictMapper
void insertBatch(List<ExcelDictDTO> list);xml:DictMapper.xml
<insert id="insertBatch">insert into dict (id ,parent_id ,name ,value ,dict_code) values<foreach collection="list" item="item" index="index" separator=",">(#{item.id} ,#{item.parentId} ,#{item.name} ,#{item.value} ,#{item.dictCode})</foreach> </insert>2.1.5Service層創建監聽器實例
接口:DictService
void importData(InputStream inputStream);實現:DictServiceImpl
注意:此處添加了事務處理,默認情況下 rollbackFor = RuntimeException.class
2.1.6Controller層接收客戶端上傳
AdminDictController
package com.aojiaoge.srb.core.controller.admin;@Api(tags = "數據字典管理") @RestController @RequestMapping("/admin/core/dict") @Slf4j @CrossOrigin public class AdminDictController {@Resourceprivate DictService dictService;@ApiOperation("Excel批量導入數據字典")@PostMapping("/import")public R batchImport(@ApiParam(value = "Excel文件", required = true)@RequestParam("file") MultipartFile file) {try {InputStream inputStream = file.getInputStream();dictService.importData(inputStream);return R.ok().message("批量導入成功");} catch (Exception e) {//UPLOAD_ERROR(-103, "文件上傳錯誤"),throw new BusinessException(ResponseEnum.UPLOAD_ERROR, e);}} }2.1.7添加mapper發布配置
注意:因為maven工程在默認情況下src/main/java目錄下的所有資源文件是不發布到target目錄下的,因此我們需要在pom.xml中添加xml配置文件發布配置
<build><!-- 項目打包時會將java目錄中的*.xml文件也進行打包 --><resources><resource><directory>src/main/java</directory><includes><include>**/*.xml</include></includes><filtering>false</filtering></resource></resources> </build>測試
2.2前端調用
2.2.1創建頁面插件
創建 src/views/core/dict/list.vue
<template><div class="app-container"></div> </template><script> export default {} </script>2.2.2配置路由
{path: '/core',component: Layout,redirect: '/core/dict/list',name: 'coreDict',meta: { title: '系統設置', icon: 'el-icon-setting' },alwaysShow: true,children: [{path: 'dict/list',name: '數據字典',component: () => import('@/views/core/dict/list'),meta: { title: '數據字典' }}] }2.2.3實現數據導入
<template><div class="app-container"><div style="margin-bottom: 10px;"><el-button@click="dialogVisible = true"type="primary"size="mini"icon="el-icon-download">導入Excel</el-button></div><el-dialog title="數據字典導入" :visible.sync="dialogVisible" width="30%"><el-form><el-form-item label="請選擇Excel文件"><el-upload:auto-upload="true":multiple="false":limit="1":on-exceed="fileUploadExceed":on-success="fileUploadSuccess":on-error="fileUploadError":action="BASE_API + '/admin/core/dict/import'"name="file"accept="application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"><el-button size="small" type="primary">點擊上傳</el-button></el-upload></el-form-item></el-form> <div slot="footer" class="dialog-footer"><el-button @click="dialogVisible = false">取消</el-button></div></el-dialog></div> </template><script> export default {// 定義數據data() {return {dialogVisible: false, //文件上傳對話框是否顯示BASE_API: process.env.VUE_APP_BASE_API //獲取后端接口地址}},methods: {// 上傳多于一個文件時fileUploadExceed() {this.$message.warning('只能選取一個文件')},//上傳成功回調fileUploadSuccess(response) {if (response.code === 0) {this.$message.success('數據導入成功')this.dialogVisible = false} else {this.$message.error(response.message)}},//上傳失敗回調fileUploadError(error) {this.$message.error('數據導入失敗')}} } </script>3.Excel數據批量導出
3.1后端接口
3.1.1Service層解析Excel數據
接口:DictService
List<ExcelDictDTO> listDictData();實現:DictServiceImpl
@Override public List<ExcelDictDTO> listDictData() {List<Dict> dictList = baseMapper.selectList(null);//創建ExcelDictDTO列表,將Dict列表轉換成ExcelDictDTO列表ArrayList<ExcelDictDTO> excelDictDTOList = new ArrayList<>(dictList.size());dictList.forEach(dict -> {ExcelDictDTO excelDictDTO = new ExcelDictDTO();BeanUtils.copyProperties(dict, excelDictDTO);excelDictDTOList.add(excelDictDTO);});return excelDictDTOList; }3.1.2Controller層接收客戶端請求
@ApiOperation("Excel數據的導出") @GetMapping("/export") public void export(HttpServletResponse response){try {// 這里注意 有同學反應使用swagger 會導致各種問題,請直接用瀏覽器或者用postmanresponse.setContentType("application/vnd.ms-excel");response.setCharacterEncoding("utf-8");// 這里URLEncoder.encode可以防止中文亂碼 當然和easyexcel沒有關系String fileName = URLEncoder.encode("mydict", "UTF-8").replaceAll("\\+", "%20");response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");EasyExcel.write(response.getOutputStream(), ExcelDictDTO.class).sheet("數據字典").doWrite(dictService.listDictData());} catch (IOException e) {//EXPORT_DATA_ERROR(104, "數據導出失敗"),throw new BusinessException(ResponseEnum.EXPORT_DATA_ERROR, e);} }3.2前端調用
3.2.1頁面添加導出按鈕
<el-button@click="exportData"type="primary"size="mini"icon="el-icon-upload2" >導出Excel</el-button>3.2.2添加導出方法
//Excel數據導出 exportData() {window.location.href = this.BASE_API + '/admin/core/dict/export' }4.數據字典列表展示
4.1后端接口
4.1.1實體類添加屬性
Dict中添加屬性:
@ApiModelProperty(value = "是否包含子節點") @TableField(exist = false)//在數據庫表中忽略此列 private boolean hasChildren;4.1.2Service層實現數據查詢
接口:DictService
List<Dict> listByParentId(Long parentId);實現:DcitServiceImpl
@Override public List<Dict> listByParentId(Long parentId) {List<Dict> dictList = baseMapper.selectList(new QueryWrapper<Dict>().eq("parent_id", parentId));dictList.forEach(dict -> {//如果有子節點,則是非葉子節點boolean hasChildren = this.hasChildren(dict.getId());dict.setHasChildren(hasChildren);});return dictList; }/*** 判斷該節點是否有子節點*/ private boolean hasChildren(Long id) {QueryWrapper<Dict> queryWrapper = new QueryWrapper<Dict>().eq("parent_id", id);Integer count = baseMapper.selectCount(queryWrapper);if(count.intValue() > 0) {return true;}return false; }4.1.3Controller層接收前端請求
@ApiOperation("根據上級id獲取子節點數據列表") @GetMapping("/listByParentId/{parentId}") public R listByParentId(@ApiParam(value = "上級節點id", required = true)@PathVariable Long parentId) {List<Dict> dictList = dictService.listByParentId(parentId);return R.ok().data("list", dictList); }4.2前端調用
4.2.1api
創建src/api/core/dict.js
import request from '@/utils/request' export default {listByParentId(parentId) {return request({url: `/admin/core/dict/listByParentId/${parentId}`,method: 'get'})} }4.2.2組件腳本
在views/core/dict/list.vue
定義data:
生命周期函數:
created() {this.fetchData() },獲取數據的方法:
import dictApi from '@/api/core/dict' // 調用api層獲取數據庫中的數據 fetchData() {dictApi.listByParentId(1).then(response => {this.list = response.data.list}) },//延遲加載子節點 load(row, treeNode, resolve) {dictApi.listByParentId(row.id).then(response => {//負責將子節點數據展示在展開的列表中 resolve(response.data.list)}) },4.2.3組件模板
<el-table :data="list" border row-key="id" lazy :load="load"><el-table-column label="名稱" align="left" prop="name" /><el-table-column label="編碼" prop="dictCode" /><el-table-column label="值" align="left" prop="value" /> </el-table>4.2.4流程優化
數據導入后刷新頁面的數據列表
//上傳成功回調 fileUploadSuccess(response) {if (response.code === 0) {this.$message.success('數據導入成功')this.dialogVisible = falsethis.fetchData()} else {this.$message.error(response.message)} },})
},
//延遲加載子節點
load(row, treeNode, resolve) {
dictApi.listByParentId(row.id).then(response => {
//負責將子節點數據展示在展開的列表中
resolve(response.data.list)
})
},
4.2.4流程優化
數據導入后刷新頁面的數據列表
//上傳成功回調 fileUploadSuccess(response) {if (response.code === 0) {this.$message.success('數據導入成功')this.dialogVisible = falsethis.fetchData()} else {this.$message.error(response.message)} },總結
以上是生活随笔為你收集整理的融易宝项目之EasyExcel和数据字典的使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c# 第四课 interfaces
- 下一篇: Lucene学习笔记(1)