当前位置: 首页 > 编程日记 > 正文

mybatis分页练手

最近碰到个需求,要做个透明的mybatis分页功能,描述如下:
目标:搜索列表的Controller action要和原先保持一样,并且返回的json需要有分页信息,如:

@ResponseBody
@RequestMapping(value="/search", method={RequestMethod.POST})
public List<ProjectInfo> search(@RequestBody SearchProjectCommand command)
{List<ProjectInfo> projects=projectFetcher.search(command.getKey(), command.getFrom(), command.getTo());return projects;
}

返回信息:

{"successful": true,"message": null,"messages": null,"dateTime": 1505651777350,"body": {"totalCount": 2,"totalPage": 1,"records": [{"projectId": "1111","projectName": "11111111111111","title": "11111111111111"},{"projectId": "22222","projectName": "222222","title": "222222"}]}
}

关键点:

  1. 针对Controller方法的aop
  2. Mybatis interceptor && PagingContext保存分页信息
  3. ResponseBodyAdvice(用于在输出json之前加入通用格式)

开始之前,先来看看消息格式,以及某些限制,主要是针对分页pageIndex这种参数的传递:

public abstract class PagingCommand {private int pageSize;private int pageIndex;public PagingCommand getPagingInfo(){return this;}public int getPageSize() {if(pageSize<=0)return Integer.MAX_VALUE;return pageSize;}public void setPageSize(int pageSize) {this.pageSize = pageSize;}public int getPageIndex() {if(pageIndex<0)return 0;return pageIndex;}public void setPageIndex(int pageIndex) {this.pageIndex = pageIndex;}
}public class PagingResponse {private int totalCount;private int totalPage;private List<Object> records;public int getTotalCount() {return totalCount;}public void setTotalCount(int totalCount) {this.totalCount = totalCount;}public List<Object> getRecords() {return records;}public int getTotalPage() {return totalPage;}public void setTotalPage(int totalPage) {this.totalPage = totalPage;}public void setRecords(List<Object> records) {this.records = records;}
}

PagingCommand是抽象类,所有的具体Command必须继承这个Command
PagingResponse是分页结果

先来看看横切入口AOP类:

 1 @Aspect
 2 @Component
 3 public class PagingAop {
 4     private static final Logger logger = LoggerFactory.getLogger(PagingAop.class);
 5 
 6     @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
 7     public void controllerMethodPointcut() {
 8     }
 9 
10     @Around("controllerMethodPointcut()")
11     public Object Interceptor(ProceedingJoinPoint pjp) throws Throwable {
12 
13         logger.info("Paging...");
14 
15         //找到是否具有PagingCommand的class作为输入参数
16         //有,则放入PagingContext中
17         for(Object arg:pjp.getArgs())
18         {
19             if(arg==null)
20                 continue;
21 
22             logger.info(arg.getClass().toString());
23             if(PagingCommand.class.isAssignableFrom(arg.getClass()))
24             {
25                 logger.info("需要分页行为");
26                 PagingContext.setPagingCommand((PagingCommand)arg);
27             }
28             else
29             {
30                 logger.info("不需要分页行为");
31             }
32         }
33 
34         return pjp.proceed();
35     }
36 }

代码很容易识别,判断参数是否是继承自PagingCommand,只要有1个继承自PagingCommand就会设置相应参数到PagingContext来标识需要分页处理,下面看看这个Context类:

 1 public final class PagingContext {
 2     private static ThreadLocal<PagingCommand> pagingCommand=new ThreadLocal<PagingCommand>();
 3     private static ThreadLocal<Integer> totalCount=new ThreadLocal<Integer>();
 4     private static ThreadLocal<Integer> totalPage=new ThreadLocal<Integer>();
 5 
 6     public static void setPagingCommand(PagingCommand cmd)
 7     {
 8         pagingCommand.set(cmd);
 9     }
10 
11     public static PagingCommand getPagingCommand()
12     {
13         return pagingCommand.get();
14     }
15 
16     public static boolean isPagingCommandEmpty()
17     {
18         if(pagingCommand.get()==null)
19             return true;
20 
21         return  false;
22     }
23 
24 
25     public static int getTotalCount() {
26         return totalCount.get();
27     }
28 
29     public static void setTotalCount(int count) {
30         totalCount.set(count);
31     }
32 
33     public static boolean isTotalCountEmpty()
34     {
35         if(totalCount.get()==null)
36             return true;
37 
38         return false;
39     }
40 
41 
42     public static int getTotalPage() {
43         return totalPage.get();
44     }
45 
46     public static void setTotalPage(int pages) {
47         totalPage.set(pages);
48     }
49 }

针对各个线程的ThreadLocal变量,但是目前只支持普通的httprequest线程才能正常工作,ThreadPool的有问题,等以后再解决。

下面是核心的mybatis分页插件了:

 1 @Intercepts({@Signature(type=Executor.class,method="query",args={ MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class/*, CacheKey.class, BoundSql.class*/})})
 2 public class PagingInterceptor implements Interceptor {
 3     private static final Logger logger = LoggerFactory.getLogger(PagingInterceptor.class);
 4 
 5     @Override
 6     public Object intercept(Invocation invocation) throws Throwable {
 7 
 8 
 9         logger.info("intercept.............");
10 
11         //判断是否需要分页行为, from PagingContext中
12         if(PagingContext.isPagingCommandEmpty())
13             return invocation.proceed();
14 
15         MappedStatement mappedStatement=(MappedStatement)invocation.getArgs()[0];
16         Object parameter = invocation.getArgs()[1];
17         BoundSql boundSql = mappedStatement.getBoundSql(parameter);
18         String originalSql = boundSql.getSql().trim();
19 
20         //生成count sql,然后执行
21         int totalCount = getTotalCount(mappedStatement, boundSql, originalSql);
22         //set totalCount value to context
23         PagingContext.setTotalCount(totalCount);
24 
25         int totalPages=calculateTotalPagesCount(totalCount, PagingContext.getPagingCommand().getPageSize());
26         PagingContext.setTotalPage(totalPages);
27 
28         //生成分页limit sql,然后执行
29         MappedStatement newMs = wrapPagedMappedStatement(mappedStatement, boundSql, originalSql);
30         invocation.getArgs()[0]= newMs;
31 
32         return invocation.proceed();
33     }
34 
35     private int calculateTotalPagesCount(int totalCount, int pageSize) {
36         int pageCount=totalCount/pageSize;
37 
38         if(pageCount==0)
39             return 1;
40 
41         if(pageCount*pageSize<=totalCount)
42             return pageCount;
43 
44         return pageCount+1;
45     }
46 
47     private MappedStatement wrapPagedMappedStatement(MappedStatement mappedStatement, BoundSql boundSql, String originalSql) {
48         PagingCommand page= PagingContext.getPagingCommand();
49         int offset = (page.getPageIndex()) * page.getPageSize();
50         StringBuffer sb = new StringBuffer();
51         sb.append(originalSql).append(" limit ").append(offset).append(",").append(page.getPageSize());
52         BoundSql newBoundSql = MyBatisUtils.copyFromBoundSql(mappedStatement, boundSql, sb.toString());
53         return MyBatisUtils.copyFromMappedStatement(mappedStatement,new BoundSqlSqlSource(newBoundSql));
54     }
55 
56     private int getTotalCount(MappedStatement mappedStatement, BoundSql boundSql, String originalSql) throws SQLException {
57         Object parameterObject = boundSql.getParameterObject();
58         String countSql = getCountSql(originalSql);
59         Connection connection=mappedStatement.getConfiguration().getEnvironment().getDataSource().getConnection()  ;
60         PreparedStatement countStmt = connection.prepareStatement(countSql);
61         BoundSql countBS = MyBatisUtils.copyFromBoundSql(mappedStatement, boundSql, countSql);
62         DefaultParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, countBS);
63         parameterHandler.setParameters(countStmt);
64         ResultSet rs = countStmt.executeQuery();
65         int totalCount=0;
66         if (rs.next()) {
67             totalCount = rs.getInt(1);
68         }
69         rs.close();
70         countStmt.close();
71         connection.close();
72         return totalCount;
73     }
74 
75     private String getCountSql(String sql) {
76         return "SELECT COUNT(1) FROM (" + sql + ") Mybatis_Pager_TBL_ALIAS";
77     }
78 
79     @Override
80     public Object plugin(Object o) {
81         return Plugin.wrap(o, this);
82     }
83 
84     @Override
85     public void setProperties(Properties properties) {
86 
87     }
88 }


最后就一步了,就是写一个ResponseBodyAdvice来根据判断是否分页输出,来返回json:

 1 @ControllerAdvice
 2 public class GlobalMessageResponseBodyAdvice implements ResponseBodyAdvice {
 3 
 4     @Override
 5     public boolean supports(MethodParameter methodParameter, Class aClass) {
 6         return true;
 7     }
 8 
 9     @Override
10     public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
11         
12             Object payload = o;
13 
14             //判断是否需要分页
15             if (isNeedPagingResponse()) {
16                 PagingResponse response = new PagingResponse();
17 
18                 response.setTotalCount(PagingContext.getTotalCount());
19                 response.setTotalPage(PagingContext.getTotalPage());
20                 response.setRecords((List<Object>) payload);
21 
22                 payload = response;
23             }
24 
25             NormalMessage msg = new NormalMessage();
26             msg.setSuccessful(true);
27             msg.setMessage(null);
28             msg.setBody(payload);
29             return msg;
30         
31     }
32 
33     public boolean isNeedPagingResponse() {
34         if(PagingContext.isPagingCommandEmpty())
35             return false;
36 
37         return true;
38     }
39 }


完成。


转载于:https://www.cnblogs.com/aarond/p/mybatis_pager.html

相关文章:

样式集(二) 信息填写样式模板

上图&#xff1a; 代码&#xff1a; // pages/upInfo/upInfo.js Page({data: {tipsTxt: "请填写正确的业务流水号",showTips: false,showCityList:false,city:"",cityList:["济南市","青岛市","枣庄市","东营市"…

12小时进制的时间输出的编辑代码

关于时间输出的编辑代码个人思考了很久&#xff0c;包括顺序&#xff0c;进位之类的&#xff0c;求完善和纠错 public class yunsuanfu {public static void main(String[] arg){double t2;int h38;int m100;int s100;if(s>60){m(s/60)m;ss%60;}if (m>60){h(m/60)h;mm%6…

c++每调用一次函数+1_每个开发人员都应该知道的一些很棒的现代C ++功能

c每调用一次函数1As a language, C has evolved a lot.作为一种语言&#xff0c;C 已经发展了很多。 Of course this did not happen overnight. There was a time when C lacked dynamism. It was difficult to be fond of the language.当然&#xff0c;这并非一overnight而…

Linux ISCSI配置

一、简介 iSCSI&#xff08;internet SCSI&#xff09;技术由IBM公司研究开发&#xff0c;是一个供硬件设备使用的、可以在IP协议的上层运行的SCSI指令集&#xff0c;这种指令集合可以实现在IP网络上运行SCSI协议&#xff0c;使其能够在诸如高速千兆以太网上进行路由选择。iSCS…

样式集(三)成功页面样式模板

上图&#xff1a; 代码&#xff1a; <!--pages/result/result.wxml--> <view><image class"scc" src"/img/scc.png"></image><view class"resuil">办理成功</view> </view> <view class"btn…

C#中Request.servervariables参数

整理一下&#xff0c;我在asp.net下遍历的Request.servervariables这上集合&#xff0c;得出的所有参数如下&#xff1a; &#xff1a; Request.ServerVariables["ALL_HTTP"] 客户端发送的http所有报头信息 返回例&#xff1a;HTTP_CACHE_CONTROL:max-age0 HTT…

打开浏览器的包 node_如何发布可在浏览器和Node中使用的软件包

打开浏览器的包 nodeWhen you create a package for others to use, you have to consider where your user will use your package. Will they use it in a browser-based environment (or frontend JavaScript)? Will they use it in Node (or backend JavaScript)? Or bot…

存储过程中SELECT与SET对变量赋值

Create proc insert_bookparam1char(10),param2varchar(20),param3money,param4moneyoutputwith encryption---------加密asinsert into book(编号,书名,价格)Values(param1,param2,param3)select param4sum(价格) from bookgo执行例子&#xff1a;declare total_price moneyex…

AngularJs $resource 高大上的数据交互

$resource 创建一个resource对象的工厂函数&#xff0c;可以让你安全的和RESFUL服务端进行数据交互。 需要注入 ngResource 模块。angular-resource[.min].js 默认情况下&#xff0c;末尾斜杠&#xff08;可以引起后端服务器不期望出现的行为&#xff09;将从计算后的URL中剥离…

样式集(四)搜索框样式

上图&#xff1a; 代码&#xff1a; // pages/search/search.js var textPage({data: {input_val:"",list:[]},input_p(e){this.setData({input_val:e.detail.value})},onLoad: function (options) {}, }) <view classpage_row bindtap"suo"><vi…

初步了解React Native的新组件库firstBorn

first-born is a React Native UI Component Framework, which follows the design methodology Atomic Design by Brad Frost.first-born是React Native UI组件框架&#xff0c;它遵循Brad Frost的设计方法Atomic Design 。 Version 1.0.0 was recently published as an npm …

less里面calc() 语法

转载 Less的好处不用说大家都知道&#xff0c;确实让写CSS的人不在痛苦了&#xff0c;最近我在Less里加入calc时确发现了有点问题&#xff0c;我在Less中这么写&#xff1a;  div {  width : calc(100% - 30px);  }  结果Less把这个当成运算式去执行了&#xff0c;结果…

基于XMPP的IOS聊天客户端程序(XMPP服务器架构)

最近看了关于XMPP的框架&#xff0c;以文本聊天为例&#xff0c;需要发送的消息为&#xff1a; <message type"chat" from"kangserver.com" to"testserver.com"> <body>helloWord</body> </message> …

小程序云开发,判断数据库表的两个字段匹配 云开发数据库匹配之 and 和 or 的配合使用

云开发数据库匹配之 and 和 or 的配合使用 代码&#xff1a; // 获取成员消息onMsg2() {let that thiswx.cloud.init({env: gezi-ofhmx})const DB wx.cloud.database()const _ DB.command;var aa "1"var bb "2"DB.collection(message_logging).where…

react引入多个图片_重新引入React:v16之后的每个React更新都已揭开神秘面纱。

react引入多个图片In this article (and accompanying book), unlike any you may have come across before, I will deliver funny, unfeigned and dead serious comic strips about every React update since v16. It’ll be hilarious, either intentionally or unintention…

75. Find Peak Element 【medium】

75. Find Peak Element 【medium】 There is an integer array which has the following features: The numbers in adjacent positions are different.A[0] < A[1] && A[A.length - 2] > A[A.length - 1].We define a position P is a peek if: A[P] > A[P-1…

云开发地图标记导航 云开发一次性取所有数据

地图取 elx 表格的经纬度数据&#xff0c;存到云开发数据库里面&#xff0c;然后标记在地图上&#xff0c;点击地图的标记可以实现路线规划&#xff0c;导航&#xff0c;拨打电话。 elx数据格式如下&#xff1a; 云开发的数据库不能直接导入elx&#xff0c;所以需要转换为csv文…

未能加载文件或程序集“Report.Basic”或它的某一个依赖项。试图加载格式不正确的程序...

出现问题如下&#xff1a; 解决办法&#xff1a; 这是由于没有开启32位程序兼容模式 具体操作如下&#xff1a;找到对应的程序池--------高级设置-------修改“启用32位应用程序”状态修改为true 转载于:https://www.cnblogs.com/OliverQin/p/5018575.html

flutter开发小程序_为什么我认为Flutter是移动应用程序开发的未来

flutter开发小程序I dabbled a bit in Android and iOS development quite a few years back using Java and Objective-C. After spending about a month working with both of them, I decided to move on. I just couldn’t get into it.几年前&#xff0c;我使用Java和Obje…

小程序获取图片的宽高

代码&#xff1a; imgInfo(url){wx.getImageInfo({src: url,success (res) {console.log(res.width)console.log(res.height)return {width:res.width,height:res.height}}})},

凯撒密码、GDP格式化输出、99乘法表

1.恺撒密码的编码 plaincode input(明文&#xff1a;)print(密文&#xff1a;,end)for i in plaincode: print(chr(ord(i)3),end) 2.国家名称 GDP总量&#xff08;人民币亿元&#xff09; 中国 &#xffe5;765873.4375澳大利亚 &#xffe5; 78312.4375&#xff08;国家名称左…

random类的使用

小栗子a如下: string[] punch new[] { "石头", "剪刀", "布" }; string myPunch; public string MyPunch{get{Random random new Random();int Index random.Next(3);myPunch punch[Index].ToString();return myPunch;}} 转载于:https://ww…

如何使用C#在ASP.NET Core中轻松实现QRCoder

by Yogi由瑜伽士 如何使用C&#xff03;在ASP.NET Core中轻松实现QRCoder (How to easily implement QRCoder in ASP.NET Core using C#) QRCoder is a very popular QR Code implementation library written in C#. It is available in GitHub. Here I am going to implement…

简述软件配置管理

http://blog.csdn.net/zhangmike/article/details/470477本文用菊子曰发布转载于:https://www.cnblogs.com/sdsunjing/p/5019791.html

startActivityForResult和setResult详解

startActivityForResult和setResult详解 原文:startActivityForResult和setResult详解startActivityForResult与startActivity的不同之处在于&#xff1a;1、startActivity( ) 仅仅是跳转到目标页面&#xff0c;若是想跳回当前页面&#xff0c;则必须再使用一次startActivity( …

小程序瀑布流实现

实现思路&#xff1a;把图片分成两排&#xff0c;创建两个数组&#xff0c;计算总数组中图片的宽高&#xff0c;对比上一个图片和当前的图片高度&#xff0c;低的就给另一个数组添加。效果图&#xff1a; 上代码&#xff1a; // miniprogram/pages/find/index.js const app g…

Webhooks上的一个简单方法:恐吓现在停止

Webhook.Webhook。 It sounds like what happens when you cross a spider and a pirate. In the world of the internet though, webhooks are something completely different. Webhooks help connect services together.听起来就像当您越过蜘蛛和海盗时会发生什么。 但是&a…

12.MySql关于获取当前时间的三个函数

这三个函数都是获取当前时间的&#xff0c;获取的详细格式如下图所示&#xff0c;可以根据需要来选用。 转载于:https://www.cnblogs.com/Nick-Hu/p/7566805.html

微信小程序云开发,使用阿里云短信服务,搜索员工生日定期发送短信。

相关API文档地址&#xff1a; 阿里云短信服务API文档地址 小程序云开发云函数正则匹配API文档地址 小程序云开发云函数定时触发器 1.登录阿里云&#xff0c;购买短信服务并添加签名和模板 2.&#xff0c; 登录阿里云&#xff0c;鼠标放在右上角的头像图标就会显示 AccessKey…

信息安全系统设计基础家庭作业

《深入理解计算机系统》家庭作业 * 8.9 答案&#xff1a; 进程对 是否并发 AB 否 AC 是 AD 是 BC 是 BD 是 CD 是 * 8.10 答案&#xff1a; A. 调用一次&#xff0c;返回两次&#xff1a; fork B. 调用一次&#xff0c;从不返回&#xff1a; execve, longjmp C. 调…