最近碰到个需求,要做个透明的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类:

 @Aspect
@Component
public class PagingAop {
private static final Logger logger = LoggerFactory.getLogger(PagingAop.class); @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void controllerMethodPointcut() {
} @Around("controllerMethodPointcut()")
public Object Interceptor(ProceedingJoinPoint pjp) throws Throwable { logger.info("Paging..."); //找到是否具有PagingCommand的class作为输入参数
//有,则放入PagingContext中
for(Object arg:pjp.getArgs())
{
if(arg==null)
continue; logger.info(arg.getClass().toString());
if(PagingCommand.class.isAssignableFrom(arg.getClass()))
{
logger.info("需要分页行为");
PagingContext.setPagingCommand((PagingCommand)arg);
}
else
{
logger.info("不需要分页行为");
}
} return pjp.proceed();
}
}

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

 public final class PagingContext {
private static ThreadLocal<PagingCommand> pagingCommand=new ThreadLocal<PagingCommand>();
private static ThreadLocal<Integer> totalCount=new ThreadLocal<Integer>();
private static ThreadLocal<Integer> totalPage=new ThreadLocal<Integer>(); public static void setPagingCommand(PagingCommand cmd)
{
pagingCommand.set(cmd);
} public static PagingCommand getPagingCommand()
{
return pagingCommand.get();
} public static boolean isPagingCommandEmpty()
{
if(pagingCommand.get()==null)
return true; return false;
} public static int getTotalCount() {
return totalCount.get();
} public static void setTotalCount(int count) {
totalCount.set(count);
} public static boolean isTotalCountEmpty()
{
if(totalCount.get()==null)
return true; return false;
} public static int getTotalPage() {
return totalPage.get();
} public static void setTotalPage(int pages) {
totalPage.set(pages);
}
}

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

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

 @Intercepts({@Signature(type=Executor.class,method="query",args={ MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class/*, CacheKey.class, BoundSql.class*/})})
public class PagingInterceptor implements Interceptor {
private static final Logger logger = LoggerFactory.getLogger(PagingInterceptor.class); @Override
public Object intercept(Invocation invocation) throws Throwable { logger.info("intercept............."); //判断是否需要分页行为, from PagingContext中
if(PagingContext.isPagingCommandEmpty())
return invocation.proceed(); MappedStatement mappedStatement=(MappedStatement)invocation.getArgs()[0];
Object parameter = invocation.getArgs()[1];
BoundSql boundSql = mappedStatement.getBoundSql(parameter);
String originalSql = boundSql.getSql().trim(); //生成count sql,然后执行
int totalCount = getTotalCount(mappedStatement, boundSql, originalSql);
//set totalCount value to context
PagingContext.setTotalCount(totalCount); int totalPages=calculateTotalPagesCount(totalCount, PagingContext.getPagingCommand().getPageSize());
PagingContext.setTotalPage(totalPages); //生成分页limit sql,然后执行
MappedStatement newMs = wrapPagedMappedStatement(mappedStatement, boundSql, originalSql);
invocation.getArgs()[0]= newMs; return invocation.proceed();
} private int calculateTotalPagesCount(int totalCount, int pageSize) {
int pageCount=totalCount/pageSize; if(pageCount==0)
return 1; if(pageCount*pageSize<=totalCount)
return pageCount; return pageCount+1;
} private MappedStatement wrapPagedMappedStatement(MappedStatement mappedStatement, BoundSql boundSql, String originalSql) {
PagingCommand page= PagingContext.getPagingCommand();
int offset = (page.getPageIndex()) * page.getPageSize();
StringBuffer sb = new StringBuffer();
sb.append(originalSql).append(" limit ").append(offset).append(",").append(page.getPageSize());
BoundSql newBoundSql = MyBatisUtils.copyFromBoundSql(mappedStatement, boundSql, sb.toString());
return MyBatisUtils.copyFromMappedStatement(mappedStatement,new BoundSqlSqlSource(newBoundSql));
} private int getTotalCount(MappedStatement mappedStatement, BoundSql boundSql, String originalSql) throws SQLException {
Object parameterObject = boundSql.getParameterObject();
String countSql = getCountSql(originalSql);
Connection connection=mappedStatement.getConfiguration().getEnvironment().getDataSource().getConnection() ;
PreparedStatement countStmt = connection.prepareStatement(countSql);
BoundSql countBS = MyBatisUtils.copyFromBoundSql(mappedStatement, boundSql, countSql);
DefaultParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, countBS);
parameterHandler.setParameters(countStmt);
ResultSet rs = countStmt.executeQuery();
int totalCount=0;
if (rs.next()) {
totalCount = rs.getInt(1);
}
rs.close();
countStmt.close();
connection.close();
return totalCount;
} private String getCountSql(String sql) {
return "SELECT COUNT(1) FROM (" + sql + ") Mybatis_Pager_TBL_ALIAS";
} @Override
public Object plugin(Object o) {
return Plugin.wrap(o, this);
} @Override
public void setProperties(Properties properties) { }
}

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

 @ControllerAdvice
public class GlobalMessageResponseBodyAdvice implements ResponseBodyAdvice { @Override
public boolean supports(MethodParameter methodParameter, Class aClass) {
return true;
} @Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { Object payload = o; //判断是否需要分页
if (isNeedPagingResponse()) {
PagingResponse response = new PagingResponse(); response.setTotalCount(PagingContext.getTotalCount());
response.setTotalPage(PagingContext.getTotalPage());
response.setRecords((List<Object>) payload); payload = response;
} NormalMessage msg = new NormalMessage();
msg.setSuccessful(true);
msg.setMessage(null);
msg.setBody(payload);
return msg; } public boolean isNeedPagingResponse() {
if(PagingContext.isPagingCommandEmpty())
return false; return true;
}
}

完成。

mybatis分页练手的更多相关文章

  1. 简单的ssm练手联手项目

    简单的ssm练手联手项目 这是一个简单的ssm整合项目 实现了汽车的品牌,价格,车型的添加 ,修改,删除,所有数据从数据库中拿取 使用到了jsp+mysql+Mybatis+spring+spring ...

  2. springmvc+spring+mybatis分页查询实例版本3,添加条件检索

    在第二个版本上添加了姓名模糊查询,年龄区间查询;自以为easy,结果发现mybatis的各种参数写法基本搞混或是忘了,zuo啊,直接上代码,然后赶紧把mybatis整理一遍再研究自己的项目,应该还会有 ...

  3. SSM 使用 mybatis 分页插件 pagehepler 实现分页

    使用分页插件的原因,简化了sql代码的写法,实现较好的物理分页,比写一段完整的分页sql代码,也能减少了误差性. Mybatis分页插件 demo 项目地址:https://gitee.com/fre ...

  4. 20个Java练手项目,献给嗜学如狂的人

    给大家推荐一条由浅入深的JAVA学习路径,首先完成 Java基础.JDK.JDBC.正则表达式等基础实验,然后进阶到 J2SE 和 SSH 框架学习.最后再通过有趣的练手项目进行巩固. JAVA基础 ...

  5. Java学习路径及练手项目合集

    Java 在编程语言排行榜中一直位列前排,可知 Java 语言的受欢迎程度了. 实验楼上的[Java 学习路径]中将首先完成 Java基础.JDK.JDBC.正则表达式等基础实验,然后进阶到 J2SE ...

  6. python实现列表页数据的批量抓取练手练手的

    python实现列表页数据的批量抓取,练手的,下回带分页的 #!/usr/bin/env python # coding=utf-8 import requests from bs4 import B ...

  7. 去哪找Java练手项目?

    经常有读者在微信上问我: 在学编程的过程中,看了不少书.视频课程,但是看完.听完之后感觉还是不会编程,想找一些项目来练手,但是不知道去哪儿找? 类似的问题,有不少读者问,估计是大部分人的困惑. 练手项 ...

  8. Python学习路径及练手项目合集

    Python学习路径及练手项目合集 https://zhuanlan.zhihu.com/p/23561159

  9. Cocos2d-Lua (练手) 微信打飞机

    学习下lua,目前入门级,使用版本为 v3.3 Final For Win,空闲时间不足,只能断断续续写点东西.   一.子弹效果          子弹只做了一种,扇形发射,可以增加扇形大小,子弹的 ...

随机推荐

  1. javaScript额外笔记

    --------------------------------------------------------Part 1javascript:脚本语言辅助开发:网页的前台开发三大块:1.HTML ...

  2. SDRAM操作说明

    SDRAM是做嵌入式系统中,常用是的缓存数据的器件.基本概念如下(注意区分几个主要常见存储器之间的差异): SDRAM(Synchronous Dynamic Random Access Memory ...

  3. HTML5浏览器端图片预览&生成Base64

    本文主要介绍如何通过拖拽方式在浏览器端实现图片预览,并生成图片的Base64编码. 工具链接:图片转Base64. 首先介绍一下FileReader, FileReader对象允许浏览器使用File或 ...

  4. 关于node的前端项目编译时内存溢出问题

    最近在做一个基于vue 的多页面项目  , 页面n++多,编译时发生node内存溢出问题,继而百度之,得到解答,故记录之. '如图' 只需在 package.json 里面   加上    --max ...

  5. one 策略模式 strategy

    --读书笔记 定义 策略模式--定义算法簇,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户.(看不懂的话,往下,有人话版/我自己的解释) 相关原则 > 1,变化单独 ...

  6. 通过ELK快速搭建一个你可能需要的集中化日志平台

    在项目初期的时候,大家都是赶着上线,一般来说对日志没有过多的考虑,当然日志量也不大,所以用log4net就够了,随着应用的越来越多,日志散 落在各个服务器的logs文件夹下,确实有点不大方便,这个时候 ...

  7. Python爬虫利器:Beautiful Soup

    Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库.使用它来处理HTML页面就像JavaScript代码操作HTML DOM树一样方便.官方中文文档地址 1. 安 ...

  8. VSCode 同步设置及扩展插件 实现设备上设置统一

    准备工作:电脑上需安装VSCode,拥有一个github账户.实现同步的功能主要依赖于VSCode插件 "Settings Sync" Setting Sync 可同步包含的所有扩 ...

  9. Promise原理与实现探究的一种思路

    写在前面 这个文章,展现的是一个实现Promise的思路,以及如何发现和处理问题的情境. 从现有的Promise分析 如果我们想要自己实现一个简单的Promise,那现有规范规定的Promise肯定是 ...

  10. springboot之Jwt验证

    简介 什么是JWT(Json Web Token) jwt是为了在网络应用环境间传递声明而执行的一种基于json的开放标准.该token被设计紧凑且安全的,特别适用于SSO场景. jwt的声明一般被用 ...