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"
}
]
}
}
关键点:
- 针对Controller方法的aop
- Mybatis interceptor && PagingContext保存分页信息
- 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分页练手的更多相关文章
- 简单的ssm练手联手项目
简单的ssm练手联手项目 这是一个简单的ssm整合项目 实现了汽车的品牌,价格,车型的添加 ,修改,删除,所有数据从数据库中拿取 使用到了jsp+mysql+Mybatis+spring+spring ...
- springmvc+spring+mybatis分页查询实例版本3,添加条件检索
在第二个版本上添加了姓名模糊查询,年龄区间查询;自以为easy,结果发现mybatis的各种参数写法基本搞混或是忘了,zuo啊,直接上代码,然后赶紧把mybatis整理一遍再研究自己的项目,应该还会有 ...
- SSM 使用 mybatis 分页插件 pagehepler 实现分页
使用分页插件的原因,简化了sql代码的写法,实现较好的物理分页,比写一段完整的分页sql代码,也能减少了误差性. Mybatis分页插件 demo 项目地址:https://gitee.com/fre ...
- 20个Java练手项目,献给嗜学如狂的人
给大家推荐一条由浅入深的JAVA学习路径,首先完成 Java基础.JDK.JDBC.正则表达式等基础实验,然后进阶到 J2SE 和 SSH 框架学习.最后再通过有趣的练手项目进行巩固. JAVA基础 ...
- Java学习路径及练手项目合集
Java 在编程语言排行榜中一直位列前排,可知 Java 语言的受欢迎程度了. 实验楼上的[Java 学习路径]中将首先完成 Java基础.JDK.JDBC.正则表达式等基础实验,然后进阶到 J2SE ...
- python实现列表页数据的批量抓取练手练手的
python实现列表页数据的批量抓取,练手的,下回带分页的 #!/usr/bin/env python # coding=utf-8 import requests from bs4 import B ...
- 去哪找Java练手项目?
经常有读者在微信上问我: 在学编程的过程中,看了不少书.视频课程,但是看完.听完之后感觉还是不会编程,想找一些项目来练手,但是不知道去哪儿找? 类似的问题,有不少读者问,估计是大部分人的困惑. 练手项 ...
- Python学习路径及练手项目合集
Python学习路径及练手项目合集 https://zhuanlan.zhihu.com/p/23561159
- Cocos2d-Lua (练手) 微信打飞机
学习下lua,目前入门级,使用版本为 v3.3 Final For Win,空闲时间不足,只能断断续续写点东西. 一.子弹效果 子弹只做了一种,扇形发射,可以增加扇形大小,子弹的 ...
随机推荐
- Visual Studio 2017开发环境的安装
Visual Studio 2017是微软为了配合.NET战略推出的IDE开发环境,同时也是目前开发C#程序最新的工具,本节以Visual Studio 2017社区版的安装为例讲解具体的安装步骤. ...
- SDN/NFV趋势思考点滴
一.为什么控制器.网管OSS融合? 1.云化是趋势:传统网络架构管理规模达到瓶颈:微服务架构通过扩充多实例解决管理规模问题.2.NFV是趋势:设备运营商传统网元在云化,以软件形式提供VNF:3.运维体 ...
- Python中的九九乘法表(for循环)
用for循环写出的九九乘法表(包括函数的调用) #方向一 for i in range(1,10): for j in range(1,i+1): d = i * j ...
- 多个Fragment的分开管理方案
当项目里有多个Fragment的时候 我们希望让Fragment有个分类 并且展示的时候不会混淆在一起 例如:项目中导航栏有三个按钮 每个按钮对应一种分类的布局,每个分类的布局中有多个Fragm ...
- Python 面向对象(一) 基础
Python 中一切皆对象 什么是面向对象? 面向对象就是将一些事物的共有特征抽象成类,从类来创建实例. 类class 可以理解为模版 比如人类,都具有身高.体重.年龄.性别.籍贯...等属性,但属性 ...
- socket.io 入门篇(二)
本文原文地址:https://www.limitcode.com/detail/5922f1ccb1d4fe074099d9cd.html 前言 上篇我们介绍了 socket.io 基本使用方法,本篇 ...
- unity3d开发环境配置
1. 首先先下载软件包:http://pan.baidu.com/s/1imYVv 4.2版本2.下载完后,解压会看到两个文件(运行第二个安装包) 3.准备安装,这里直接上图了. 这里全选,里面包括 ...
- ThinkPHP中处理Layout模板的问题
ThinkPHP中的模板引擎内置了布局模板功能支持,可以方便实现嵌套. 其中有两种布局方式,一种为以布局模板为入口的布局方式,但是需要开启LAYOUT_ON 参数(默认不开启) 并且设置布局入口文件名 ...
- slurm-16.05.3任务调度系统部署与测试(1)
1.概述2.同步节点时间3.下载并解压文件4.编译安装munge-0.5.125.配置munge6.编译安装slurm-16.05.37.配置slurm8.配置MySQL数据库环境9.启动slur ...
- WebStorm10 控制台中文乱码解决方案
工作时发现无论是使用ctrl+F搜索还是查看提交的注释中文都是口,看的本小白十分蛋疼菊紧,所以抽时间找了方法去搞定它. 首先点击左上角的File,选择Setting 然后选择Appearance &a ...