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,空闲时间不足,只能断断续续写点东西. 一.子弹效果 子弹只做了一种,扇形发射,可以增加扇形大小,子弹的 ...
随机推荐
- SQL Server 通过SQL脚本启动Broker并设置兼容性
SQL Server数据库中通过SQL启用Broker 并建立相关队列和服务 兼容代码使其可以在2000库中执行不报错 针对的是2008版本, 如果是其他版本可以改相关版本号和兼容性标记 /***** ...
- Maven中settings.xml的配置项说明精讲
1.Maven的配置文件(Maven的安装目录/conf/settings.xml ) 和 Maven仓库下(默认的Maven仓库的是用户家目录下的.m2文件,可以另行制定)的settings.xml ...
- ssh相关原理学习与常见错误总结
欢迎和大家交流技术相关问题: 邮箱: jiangxinnju@163.com 博客园地址: http://www.cnblogs.com/jiangxinnju GitHub地址: https://g ...
- 【JDK1.8】JDK1.8集合源码阅读——TreeMap(一)
一.前言 在前面两篇随笔中,我们提到过,当HashMap的桶过大的时候,会自动将链表转化成红黑树结构,当时一笔带过,因为我们将留在本章中,针对TreeMap进行详细的了解. 二.TreeMap的继承关 ...
- mybatis逆向工程之生成文件解释
一.mapper接口中的方法解析 mapper接口中的函数及方法 方法 功能说明 int countByExample(UserExample example) thorws SQLException ...
- linux服务器上Apache配置多域名
一, 打开httpd.conf 二 找到如下三个位置配置如下 DocumentRoot "/data" #以下这个配置是紧挨着的,有两个 <Directory "/ ...
- select2 取值 遍历 设置默认值
select2 取值 遍历 设置默认值 本章内容主要介绍Select2 的初始化,获取选中值,设置默认值,三个方法.Select2 美化了单选框,复选框和下拉框,特别是下拉框多选的问题.但同时,Sel ...
- Spring4 快速入门
Spring4 快速入门 1 Spring简介 1.1 Spring是什么? Spring 是一个 IOC 和 AOP 容器的开源框架,为简化企业级应用而生. IOC(Inversion of Con ...
- 一个高性能异步socket封装库的实现思路 (c#)
前言 socket是软件之间通讯最常用的一种方式.c#实现socket通讯有很多中方法,其中效率最高就是异步通讯. 异步通讯实际是利用windows完成端口(IOCP)来处理的,关于完成端口实现原理, ...
- 集合、set以及HASH
集合的数据结构数据结构就是内存中保存输出数据的形式,不同的数据结构会有不同的特征.堆栈结构:先进后出 代表类(stack):应用场景:java中的方法运行时所占用的空间就是这种结构.队列结构:先进先出 ...