最近碰到个需求,要做个透明的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. Oracle与Sql server的区别

    一直搞不明白Oracle数据库和sql server的区别,今天我特意查资料把他们的区别整理出来 Oracle数据库:Oracle Database,又名Oracle RDBMS,或简称Oracle. ...

  2. python基础阶段练习题 拾英札记(1)

    python很灵活,学起来有人机交互的快乐感,贵在坚持. 做题对自学python很有帮助,融汇贯通-查漏补缺-巩固提高. 写了一些注释,希望能对您有所帮助. #1.输入一个3位数,计算个位.百位.十位 ...

  3. [转载] Tomcat架构分析

    转载自http://gearever.iteye.com/category/223001

  4. python基础-------模块与包(三)正则表达式

    re模块正则表达式 正则表达式常用符号: [ re模块使用方法]: match(string[, pos[, endpos]]) | re.match(pattern, string[, flags] ...

  5. ionic开发环境搭建之android及问题

    1. 准备工作: a)     配置java开发环境 b)    配置安卓开发环境 注:下载android-studio后,可能会出现android一直在编译,出现这种情况,关掉你的as ,找到你的路 ...

  6. Ajax-javascript

    一.Web 2.0的特点 提到Ajax不得不提到WEB2.0 1.用户贡献内容 2.内容聚合RSS 3.更丰富的"用户体验" 二.Ajax的作用 无刷新:不刷新整个页面,只刷新局部 ...

  7. 小随笔:利用Shader给斯坦福兔子长毛和实现雪地效果

    0x00 前言 发现最近没有了写长篇大论的激情,可能是到了冬天了吧.所以这篇小文只是简单介绍下如何在Unity中利用shader很简单的实现雪地效果以及毛皮效果,当然虽然标题写在了一起,但其实这是俩事 ...

  8. js获取url地址栏参数

    前端开发中经常会遇到需要获取url地址栏参数问题 方法如下: function getQueryStringByName(name){ var src = "www.baidu.com?na ...

  9. cocos2d-x安卓应用启动调用过程简析

    调用org.cocos2dx.cpp.AppActivity AppActivity是位于proj.android/src下的开发者类(即开发者自定义的类),它继承自org.cocos2dx.lib. ...

  10. 万能动态库调用工具IDMA(InvokeDllMethodsAdvance)

    万能动态库调用工具IDMA 开发者:马兆瑞     QQ/微信:624762543 百度云下载链接:https://pan.baidu.com/s/1skW5W4H CSDN下载链接:http://d ...