MyBatis流式查询的使用详解

流式查询指的是查询成功后不是返回一个集合而是返回一个迭代器,应用每次从迭代器取一条查询结果。流式查询的好处是能够降低内存使用

1.应用场景说明

MyBatis

preview: JDBC三种读取方式:

1.一次全部(默认):一次获取全部。

2.流式:多次获取,一次一行。

3.游标:多次获取,一次多行。

在开发中我们经常需要会遇到统计数据,将数据导出到excel表格中。由于生成报表逻辑要从数据库读取大量数据并在内存中加工处理后再生成Excel返回给客户端。如果数据量过大,采用默认的读取方式(一次性获取全部)会导致内存飙升,甚至是内存溢出。而导出数据又需要查询大量的数据,因此采用流式查询就比较合适了。

2.模拟excel导出场景

1.创建海量数据的sql脚本

CREATE TABLE dept( /*部门表*/ deptno MEDIUMINT   UNSIGNED  NOT NULL  DEFAULT 0, dname VARCHAR(20)  NOT NULL  DEFAULT "", loc VARCHAR(13) NOT NULL DEFAULT "" ) ; #创建表EMP雇员 CREATE TABLE emp (empno  MEDIUMINT UNSIGNED  NOT NULL  DEFAULT 0, /*编号*/ ename VARCHAR(20) NOT NULL DEFAULT "", /*名字*/ job VARCHAR(9) NOT NULL DEFAULT "",/*工作*/ mgr MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,/*上级编号*/ hiredate DATE NOT NULL,/*入职时间*/ sal DECIMAL(7,2)  NOT NULL,/*薪水*/ comm DECIMAL(7,2) NOT NULL,/*红利*/ deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0 /*部门编号*/ ) ; #工资级别表 CREATE TABLE salgrade ( grade MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, losal DECIMAL(17,2)  NOT NULL, hisal DECIMAL(17,2)  NOT NULL ); #测试数据 INSERT INTO salgrade VALUES (1,700,1200); INSERT INTO salgrade VALUES (2,1201,1400); INSERT INTO salgrade VALUES (3,1401,2000); INSERT INTO salgrade VALUES (4,2001,3000); INSERT INTO salgrade VALUES (5,3001,9999); delimiter $$ #创建一个函数,名字 rand_string,可以随机返回我指定的个数字符串 create function rand_string(n INT) returns varchar(255) #该函数会返回一个字符串 begin #定义了一个变量 chars_str, 类型  varchar(100) #默认给 chars_str 初始值   'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ' declare chars_str varchar(100) default 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ'; declare return_str varchar(255) default ''; declare i int default 0; while i 

2.MyBatis流式查询

1.创建src\main\java\com\llp\llpmybatis\entity\Emp.java

@Data public class Emp { private Integer empno; private String ename; private String job; private Integer mgr; private Date hiredate; private BigDecimal sal; private BigDecimal comm; private Integer deptno; } 

2.创建src\main\java\com\llp\llpmybatis\vo\EmpVo.java

@Data public class EmpVo { @ExcelProperty("员工编号") private Integer empno; @ExcelProperty("员工姓名") private String ename; @ExcelProperty("员工工种") private String job; @ExcelProperty("主管编号") private Integer mgr; @ExcelProperty("入职日期") private Date hiredate; @ExcelProperty("工资") private BigDecimal sal; @ExcelProperty("通讯") private BigDecimal comm; @ExcelProperty("部门编号") private Integer deptno; }

3.创建src\main\java\com\llp\llpmybatis\controller\EmpController.java

@RestController public class EmpController { @Autowired private EmpService empService; /** * 导出员工数据到excel */ @RequestMapping("/export") public void exportEmp(){ StopWatch watch = new StopWatch(); watch.start(); List empList = empService.exportEmp(); //将数据分sheet进行导出 EasyExcleUtil.excelExportDivisionBySheet(EmpVo.class, "员工信息_"+System.currentTimeMillis(), empList); watch.stop(); long totalTimeMillis = watch.getTotalTimeMillis(); System.out.println("共计耗时:"+totalTimeMillis+"毫秒"); } /** * 导入excel数据到员工表 * @param file */ @RequestMapping("/import") public void importEmp(@RequestParam(name = "file") MultipartFile file){ //这里我们在导入时传入回调接口的匿名内部类实现,在ExcleDataListener easyExcel读取监听器中对接口进行赋值 //在监听器中doAfterAllAnalysed,在所有数据解析完之后回调用这个方法,我们在方法中对导出的数据集进行赋值 EasyExcleUtil.importExcel(file, EmpVo.class, new ExcleFinshCallBack(){ @Override public void doAfterAllAnalysed(List result) { empService.exportEmp(); } }); } }

4.创建src\main\java\com\llp\llpmybatis\service\EmpService.java

public interface EmpService { List exportEmp(); }

5.创建src\main\java\com\llp\llpmybatis\service\impl\EmpServiceImpl.java(重点)

@Service public class EmpServiceImpl implements EmpService { @Resource private EmpDao empdao; /** * mybatis流式查询导出员工数据 * @return */ @Override public List exportEmp() { StopWatch stopWatch = new StopWatch(); stopWatch.start(); List empList = new ArrayList<>(); empdao.getAll(new ResultHandler() { /** * mybatis流失查询会回调处理逻辑 * @param resultContext */ @Override public void handleResult(ResultContext resultContext) { empList.add(resultContext.getResultObject()); } }); stopWatch.stop(); System.out.println("查询共计耗费"+stopWatch.getTotalTimeMillis()+"毫秒"); return empList; } }

6.创建src\main\java\com\llp\llpmybatis\dao\EmpDao.java(重点)

@Repository public interface EmpDao { void getAll(ResultHandler handler); } 

这里dao层没有返回值,但是在还是需要指定resultMap,因为查询的数据要映射到回调函数的resultContext中,此外还需要设置:resultSetType=“FORWARD_ONLY” 、fetchSize=“-2147483648”

EmpDao.xml

  

至此mybatis流式查询就完成了

3.Excel通用导出工具类

1.Excel导入导出工具类

public class EasyExcleUtil { private static final int MAXROWS = 500000; /** * excel读取 * * @param file     excel文件 * @param head     列名 * @param callBack 回调接口的实现类 */ public static void importExcel(MultipartFile file, Class head, ExcleFinshCallBack callBack) { try { EasyExcel.read(file.getInputStream(), head, new ExcleDataListener(callBack)).sheet().doRead(); } catch (IOException e) { e.printStackTrace(); } } /** * 导出数据 * * @param head      类名 * @param excelname excel名字 * @param data      数据 *                  java.lang.IllegalArgumentException: Invalid row number (1048576) outside allowa *                  这是由于Excel的一张sheet允许的最大行数是1048575,由于导出的数据比较大,超出了一张sheet所能容纳的最大行数,导致无法继续创建新的行 *                  1048575 *                  1000000 */ public static void excelExport(Class head, String excelname, List data) { ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletResponse response = requestAttributes.getResponse(); // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman //response.setContentType("application/vnd.ms-excel"); response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); try { // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系 String fileName = URLEncoder.encode(excelname, "UTF-8").replaceAll("\\+", "%20"); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); EasyExcel.write(response.getOutputStream(), head).sheet("Sheet1").doWrite(data); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * 获取默认表头内容的样式 * * @return */ private static HorizontalCellStyleStrategy getDefaultHorizontalCellStyleStrategy() { /** 表头样式 **/ WriteCellStyle headWriteCellStyle = new WriteCellStyle(); // 背景色(浅灰色) // 可以参考:https://www.cnblogs.com/vofill/p/11230387.html headWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); // 字体大小 WriteFont headWriteFont = new WriteFont(); headWriteFont.setFontHeightInPoints((short) 10); headWriteCellStyle.setWriteFont(headWriteFont); //设置表头居中对齐 headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); /** 内容样式 **/ WriteCellStyle contentWriteCellStyle = new WriteCellStyle(); // 内容字体样式(名称、大小) WriteFont contentWriteFont = new WriteFont(); contentWriteFont.setFontName("宋体"); contentWriteFont.setFontHeightInPoints((short) 10); contentWriteCellStyle.setWriteFont(contentWriteFont); //设置内容垂直居中对齐 contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER); //设置内容水平居中对齐 contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); //设置边框样式 contentWriteCellStyle.setBorderLeft(BorderStyle.THIN); contentWriteCellStyle.setBorderTop(BorderStyle.THIN); contentWriteCellStyle.setBorderRight(BorderStyle.THIN); contentWriteCellStyle.setBorderBottom(BorderStyle.THIN); // 头样式与内容样式合并 return new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle); } /** * 将数据分sheet进行导出 * @param data     查询结果 * @param fileName 导出文件名称 * @param clazz    映射实体class类 * @param       查询结果类型 * @throws Exception */ public static  void excelExportDivisionBySheet(Class clazz, String fileName, List data) { OutputStream out = null; ExcelWriter excelWriter = null; try { ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletResponse response = requestAttributes.getResponse(); // 分割的集合 List> lists = SplitList.splitList(data, MAXROWS); out = getOutputStream(fileName, response); ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(out, clazz).excelType(ExcelTypeEnum.XLSX).registerWriteHandler(getDefaultHorizontalCellStyleStrategy()); excelWriter = excelWriterBuilder.build(); ExcelWriterSheetBuilder excelWriterSheetBuilder; WriteSheet writeSheet; for (int i = 1; i <= lists.size(); i++) { excelWriterSheetBuilder = new ExcelWriterSheetBuilder(); excelWriterSheetBuilder.sheetNo(i); excelWriterSheetBuilder.sheetName("sheet" + i); writeSheet = excelWriterSheetBuilder.build(); excelWriter.write(lists.get(i - 1), writeSheet); } } catch (IOException e) { e.printStackTrace(); } finally { if (excelWriter != null) { excelWriter.finish(); } if (out != null) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } } private static OutputStream getOutputStream(String fileName, HttpServletResponse response) throws IOException { fileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20"); //  response.setContentType("application/vnd.ms-excel"); // .xls response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); // .xlsx response.setCharacterEncoding("utf-8"); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); return response.getOutputStream(); } }

2.Excel数据读取监听器

/** * excel读取监听器 */ public class ExcleDataListener extends AnalysisEventListener { //定义一个保存Excel所有记录的集合 private List list = new LinkedList<>(); //回调接口 private ExcleFinshCallBack callBack; /** * 构造注入ExcleFinshCallBack * @param callBack */ public ExcleDataListener(ExcleFinshCallBack callBack) { this.callBack = callBack; } /** * 这个每一条数据解析都会来调用 * 我们将每一条数据都保存到list集合中 * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()} * @param context */ @Override public void invoke(Object data, AnalysisContext context) { list.add(data); } /** * 所有数据解析完成了 都会来调用这个方法 * 在 * @param context */ @Override public void doAfterAllAnalysed(AnalysisContext context) { this.callBack.doAfterAllAnalysed(this.list); } }

3.Excel读取数据完成回调接口

/** * excel读取数据完成回调接口 */ public interface ExcleFinshCallBack { void doAfterAllAnalysed(List result); } 

4.拆分List集合工具类

/** * 拆分List集合 */ public class SplitListUtil { /** * * @param list 待切割集合 * @param len  集合按照多大size来切割 * @param  * @return */ public static  List> splitList(List list, int len) { if (list == null || list.size() == 0 || len <1) { return null; } List> result = new ArrayList>(); int size = list.size(); int count = (size + len - 1) / len; for (int i = 0; i  subList = list.subList(i * len, ((i + 1) * len > size ? size : len * (i + 1))); result.add(subList); } return result; } /** * @param source 源集合 * @param n      分成n个集合 * @param     集合类型 * @return * @description 集合平均分组 */ public static  List> groupList(List source, int n) { if (source == null || source.size() == 0 || n <1) { return null; } if (source.size() > result = new ArrayList>(); int number = source.size() / n; int remaider = source.size() % n; // 偏移量,每有一个余数分配,就要往右偏移一位 int offset = 0; for (int i = 0; i  list1 = null; if (remaider > 0) { list1 = source.subList(i * number + offset, (i + 1) * number + offset + 1); remaider--; offset++; } else { list1 = source.subList(i * number + offset, (i + 1) * number + offset); } result.add(list1); } return result; } }

4.测试结果

sheet1

sheet2

sheet3

5.遗留问题待处理

这个问题时由于excelWriter.finish();去关闭连接时,发现连接已经被终止了导致的,对数据导出的完整性并没有影响

到此这篇关于MyBatis流式查询的使用详解的文章就介绍到这了,更多相关MyBatis流式查询内容请搜索0133技术站以前的文章或继续浏览下面的相关文章希望大家以后多多支持0133技术站!

以上就是MyBatis流式查询的使用详解的详细内容,更多请关注0133技术站其它相关文章!

赞(0) 打赏
未经允许不得转载:0133技术站首页 » Java