经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » MyBatis » 查看文章
MyBatis流式查询的项目实践
来源:jb51  时间:2022/8/16 17:28:16  对本文有异议

1.应用场景说明 MyBatis

preview: JDBC三种读取方式:
1.一次全部(默认):一次获取全部。
2.流式:多次获取,一次一行。
3.游标:多次获取,一次多行。

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

2.模拟excel导出场景

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

  1. CREATE TABLE dept( /*部门表*/
  2. deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,
  3. dname VARCHAR(20) NOT NULL DEFAULT "",
  4. loc VARCHAR(13) NOT NULL DEFAULT ""
  5. ) ;
  6.  
  7. #创建表EMP雇员
  8. CREATE TABLE emp
  9. (empno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, /*编号*/
  10. ename VARCHAR(20) NOT NULL DEFAULT "", /*名字*/
  11. job VARCHAR(9) NOT NULL DEFAULT "",/*工作*/
  12. mgr MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,/*上级编号*/
  13. hiredate DATE NOT NULL,/*入职时间*/
  14. sal DECIMAL(7,2) NOT NULL,/*薪水*/
  15. comm DECIMAL(7,2) NOT NULL,/*红利*/
  16. deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0 /*部门编号*/
  17. ) ;
  18.  
  19. #工资级别表
  20. CREATE TABLE salgrade
  21. (
  22. grade MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,
  23. losal DECIMAL(17,2) NOT NULL,
  24. hisal DECIMAL(17,2) NOT NULL
  25. );
  26.  
  27. #测试数据
  28. INSERT INTO salgrade VALUES (1,700,1200);
  29. INSERT INTO salgrade VALUES (2,1201,1400);
  30. INSERT INTO salgrade VALUES (3,1401,2000);
  31. INSERT INTO salgrade VALUES (4,2001,3000);
  32. INSERT INTO salgrade VALUES (5,3001,9999);
  33.  
  34. delimiter $$
  35.  
  36. #创建一个函数,名字 rand_string,可以随机返回我指定的个数字符串
  37. create function rand_string(n INT)
  38. returns varchar(255) #该函数会返回一个字符串
  39. begin
  40. #定义了一个变量 chars_str, 类型 varchar(100)
  41. #默认给 chars_str 初始值 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ'
  42. declare chars_str varchar(100) default
  43. 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
  44. declare return_str varchar(255) default '';
  45. declare i int default 0;
  46. while i < n do
  47. # concat 函数 : 连接函数mysql函数
  48. set return_str =concat(return_str,substring(chars_str,floor(1+rand()*52),1));
  49. set i = i + 1;
  50. end while;
  51. return return_str;
  52. end $$
  53.  
  54.  
  55. #这里我们又自定了一个函数,返回一个随机的部门号
  56. create function rand_num( )
  57. returns int(5)
  58. begin
  59. declare i int default 0;
  60. set i = floor(10+rand()*500);
  61. return i;
  62. end $$
  63.  
  64. #创建一个存储过程, 可以添加雇员
  65. create procedure insert_emp(in start int(10),in max_num int(10))
  66. begin
  67. declare i int default 0;
  68. #set autocommit =0 把autocommit设置成0
  69. #autocommit = 0 含义: 不要自动提交
  70. set autocommit = 0; #默认不提交sql语句
  71. repeat
  72. set i = i + 1;
  73. #通过前面写的函数随机产生字符串和部门编号,然后加入到emp表
  74. insert into emp values ((start+i) ,rand_string(6),'SALESMAN',0001,curdate(),2000,400,rand_num());
  75. until i = max_num
  76. end repeat;
  77. #commit整体提交所有sql语句,提高效率
  78. commit;
  79. end $$
  80.  
  81. #添加8000000数据
  82. call insert_emp(100001,8000000)$$
  83.  
  84. #命令结束符,再重新设置为;
  85. delimiter ;

2.MyBatis流式查询

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

  1. @Data
  2. public class Emp {
  3. private Integer empno;
  4. private String ename;
  5. private String job;
  6. private Integer mgr;
  7. private Date hiredate;
  8. private BigDecimal sal;
  9. private BigDecimal comm;
  10. private Integer deptno;
  11. }

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

  1. @Data
  2. public class EmpVo {
  3. @ExcelProperty("员工编号")
  4. private Integer empno;
  5. @ExcelProperty("员工姓名")
  6. private String ename;
  7. @ExcelProperty("员工工种")
  8. private String job;
  9. @ExcelProperty("主管编号")
  10. private Integer mgr;
  11. @ExcelProperty("入职日期")
  12. private Date hiredate;
  13. @ExcelProperty("工资")
  14. private BigDecimal sal;
  15. @ExcelProperty("通讯")
  16. private BigDecimal comm;
  17. @ExcelProperty("部门编号")
  18. private Integer deptno;
  19.  
  20. }

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

  1. @RestController
  2. public class EmpController {
  3.  
  4. @Autowired
  5. private EmpService empService;
  6.  
  7. /**
  8. * 导出员工数据到excel
  9. */
  10. @RequestMapping("/export")
  11. public void exportEmp(){
  12. StopWatch watch = new StopWatch();
  13. watch.start();
  14. List<EmpVo> empList = empService.exportEmp();
  15. //将数据分sheet进行导出
  16. EasyExcleUtil.excelExportDivisionBySheet(EmpVo.class, "员工信息_"+System.currentTimeMillis(), empList);
  17. watch.stop();
  18. long totalTimeMillis = watch.getTotalTimeMillis();
  19. System.out.println("共计耗时:"+totalTimeMillis+"毫秒");
  20. }
  21.  
  22. /**
  23. * 导入excel数据到员工表
  24. * @param file
  25. */
  26. @RequestMapping("/import")
  27. public void importEmp(@RequestParam(name = "file") MultipartFile file){
  28. //这里我们在导入时传入回调接口的匿名内部类实现,在ExcleDataListener easyExcel读取监听器中对接口进行赋值
  29. //在监听器中doAfterAllAnalysed,在所有数据解析完之后回调用这个方法,我们在方法中对导出的数据集进行赋值
  30. EasyExcleUtil.importExcel(file, EmpVo.class, new ExcleFinshCallBack(){
  31. @Override
  32. public void doAfterAllAnalysed(List<Object> result) {
  33. empService.exportEmp();
  34. }
  35. });
  36. }
  37.  
  38. }

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

  1. public interface EmpService {
  2. List<EmpVo> exportEmp();
  3. }

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

  1. @Service
  2. public class EmpServiceImpl implements EmpService {
  3.  
  4. @Resource
  5. private EmpDao empdao;
  6.  
  7. /**
  8. * mybatis流式查询导出员工数据
  9. * @return
  10. */
  11. @Override
  12. public List<EmpVo> exportEmp() {
  13. StopWatch stopWatch = new StopWatch();
  14. stopWatch.start();
  15. List<EmpVo> empList = new ArrayList<>();
  16. empdao.getAll(new ResultHandler<EmpVo>() {
  17. /**
  18. * mybatis流失查询会回调处理逻辑
  19. * @param resultContext
  20. */
  21. @Override
  22. public void handleResult(ResultContext<? extends EmpVo> resultContext) {
  23. empList.add(resultContext.getResultObject());
  24. }
  25. });
  26. stopWatch.stop();
  27. System.out.println("查询共计耗费"+stopWatch.getTotalTimeMillis()+"毫秒");
  28. return empList;
  29. }
  30.  
  31. }

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

  1. @Repository
  2. public interface EmpDao {
  3. void getAll(ResultHandler<EmpVo> handler);
  4. }

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

EmpDao.xml

  1. <mapper namespace="com.llp.llpmybatis.dao.EmpDao">
  2.  
  3. <resultMap id="empResultMap" type="com.llp.llpmybatis.vo.EmpVo">
  4. <result column="empno" property="empno"/>
  5. <result column="ename" property="ename"/>
  6. <result column="job" property="job"/>
  7. <result column="mgr" property="mgr"/>
  8. <result column="hiredate" property="hiredate"/>
  9. <result column="sal" property="sal"/>
  10. <result column="comm" property="comm"/>
  11. <result column="deptno" property="deptno"/>
  12. </resultMap>
  13. <select id="getAll" resultMap="empResultMap" resultSetType="FORWARD_ONLY" fetchSize="-2147483648">
  14. select *
  15. from emp;
  16. </select>
  17. </mapper>

至此mybatis流式查询就完成了

3.Excel通用导出工具类

1.Excel导入导出工具类

  1. /**
  2. * excel读取监听器
  3. */
  4. public class ExcleDataListener extends AnalysisEventListener {
  5. //定义一个保存Excel所有记录的集合
  6. private List<Object> list = new LinkedList<>();
  7. //回调接口
  8. private ExcleFinshCallBack callBack;
  9.  
  10. /**
  11. * 构造注入ExcleFinshCallBack
  12. * @param callBack
  13. */
  14. public ExcleDataListener(ExcleFinshCallBack callBack) {
  15. this.callBack = callBack;
  16. }
  17.  
  18.  
  19. /**
  20. * 这个每一条数据解析都会来调用
  21. * 我们将每一条数据都保存到list集合中
  22. * @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
  23. * @param context
  24. */
  25. @Override
  26. public void invoke(Object data, AnalysisContext context) {
  27. list.add(data);
  28. }
  29.  
  30. /**
  31. * 所有数据解析完成了 都会来调用这个方法
  32. * 在
  33. * @param context
  34. */
  35. @Override
  36. public void doAfterAllAnalysed(AnalysisContext context) {
  37. this.callBack.doAfterAllAnalysed(this.list);
  38. }
  39. }

2.Excel数据读取监听器

  1. /**
  2. * excel读取监听器
  3. */
  4. public class ExcleDataListener extends AnalysisEventListener {
  5. //定义一个保存Excel所有记录的集合
  6. private List<Object> list = new LinkedList<>();
  7. //回调接口
  8. private ExcleFinshCallBack callBack;
  9.  
  10. /**
  11. * 构造注入ExcleFinshCallBack
  12. * @param callBack
  13. */
  14. public ExcleDataListener(ExcleFinshCallBack callBack) {
  15. this.callBack = callBack;
  16. }
  17.  
  18.  
  19. /**
  20. * 这个每一条数据解析都会来调用
  21. * 我们将每一条数据都保存到list集合中
  22. * @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
  23. * @param context
  24. */
  25. @Override
  26. public void invoke(Object data, AnalysisContext context) {
  27. list.add(data);
  28. }
  29.  
  30. /**
  31. * 所有数据解析完成了 都会来调用这个方法
  32. * 在
  33. * @param context
  34. */
  35. @Override
  36. public void doAfterAllAnalysed(AnalysisContext context) {
  37. this.callBack.doAfterAllAnalysed(this.list);
  38. }
  39. }

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

  1. /**
  2. * excel读取数据完成回调接口
  3. */
  4. public interface ExcleFinshCallBack {
  5. void doAfterAllAnalysed(List<Object> result);
  6. }

5.拆分List集合工具类

  1. /**
  2. * 拆分List集合
  3. */
  4. public class SplitListUtil {
  5.  
  6. /**
  7. *
  8. * @param list 待切割集合
  9. * @param len 集合按照多大size来切割
  10. * @param <T>
  11. * @return
  12. */
  13. public static <T> List<List<T>> splitList(List<T> list, int len) {
  14. if (list == null || list.size() == 0 || len < 1) {
  15. return null;
  16. }
  17. List<List<T>> result = new ArrayList<List<T>>();
  18. int size = list.size();
  19. int count = (size + len - 1) / len;
  20.  
  21. for (int i = 0; i < count; i++) {
  22. List<T> subList = list.subList(i * len, ((i + 1) * len > size ? size : len * (i + 1)));
  23. result.add(subList);
  24. }
  25. return result;
  26. }
  27.  
  28.  
  29. /**
  30. * @param source 源集合
  31. * @param n 分成n个集合
  32. * @param <T> 集合类型
  33. * @return
  34. * @description 集合平均分组
  35. */
  36. public static <T> List<List<T>> groupList(List<T> source, int n) {
  37. if (source == null || source.size() == 0 || n < 1) {
  38. return null;
  39. }
  40. if (source.size() < n) {
  41. return Arrays.asList(source);
  42. }
  43. List<List<T>> result = new ArrayList<List<T>>();
  44. int number = source.size() / n;
  45. int remaider = source.size() % n;
  46. // 偏移量,每有一个余数分配,就要往右偏移一位
  47. int offset = 0;
  48. for (int i = 0; i < n; i++) {
  49. List<T> list1 = null;
  50. if (remaider > 0) {
  51. list1 = source.subList(i * number + offset, (i + 1) * number + offset + 1);
  52. remaider--;
  53. offset++;
  54. } else {
  55. list1 = source.subList(i * number + offset, (i + 1) * number + offset);
  56. }
  57. result.add(list1);
  58. }
  59.  
  60. return result;
  61. }
  62. }

4.测试结果

sheet1

sheet2

sheet3

5.遗留问题,待处理

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

 到此这篇关于MyBatis流式查询的项目实践的文章就介绍到这了,更多相关MyBatis流式查询内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号