首页 > mybatis分页练手

mybatis分页练手

最近碰到个需求,要做个透明的mybatis分页功能,描述如下:

目标:搜索列表的Controller action要和原先保持一样,并且返回的json需要有分页信息,如:

@ResponseBody
@RequestMapping(value="/search", method={RequestMethod.POST})
public List search(@RequestBody SearchProjectCommand command)
{List 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 records;public int getTotalCount() {return totalCount;}public void setTotalCount(int totalCount) {this.totalCount = totalCount;}public List getRecords() {return records;}public int getTotalPage() {return totalPage;}public void setTotalPage(int totalPage) {this.totalPage = totalPage;}public void setRecords(List 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=new ThreadLocal();
 3     private static ThreadLocal totalCount=new ThreadLocal();
 4     private static ThreadLocal totalPage=new ThreadLocal();
 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) 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

更多相关:

  • 在.Net Framework中,配置文件一般采用的是XML格式的,.NET Framework提供了专门的ConfigurationManager来读取配置文件的内容,.net core中推荐使用json格式的配置文件,那么在.net core中该如何读取json文件呢?1、在Startup类中读取json配置文件1、使用Confi...

  •   1 public class FrameSubject extends JFrame {   2    3   …………..   4    5   //因为无法使用多重继承,这儿就只能使用对象组合的方式来引入一个   6    7   //java.util.Observerable对象了。   8    9   DateSub...

  • 本案例主要说明如何使用NSwag 工具使用桌面工具快速生成c# 客户端代码、快速的访问Web Api。 NSwagStudio 下载地址 比较强大、可以生成TypeScript、WebApi Controller、CSharp Client  1、运行WebApi项目  URL http://yourserver/swagger 然后...

  •   在绑定完Action的所有参数后,WebAPI并不会马上执行该方法,而要对参数进行验证,以保证输入的合法性.   ModelState 在ApiController中一个ModelState属性用来获取参数验证结果.   public abstract class ApiController : IHttpController,...

  • 1# 引用  C:AVEVAMarineOH12.1.SP4Aveva.ApplicationFramework.dll C:AVEVAMarineOH12.1.SP4Aveva.ApplicationFramework.Presentation.dll 2# 引用命名空间, using Aveva.Applicati...

  • /*判断屏幕宽高比是否为16:9*/ function isScreen16to9() {return window.screen.height / window.screen.width === 9 / 16; }...

  • /*关闭、刷新、跳转、离开当前网页前提示*/ onbeforeunload = function () {return false; };  ...

  • let json = {/**判断JSON格式*/ isJSON: function (str) {if (typeof str == "string") {try {var obj = JSON.parse(str);if (typeof obj == "object" && obj) {return true;} else {...

  •   项目结构   index.js //必须要安装否则就别想运行了❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤❤ //npm i body-parser -D & cnpm i express & cnpm i node-xlsx & cnp...

  • 一、递归 函数    为什么要有函数,提高代码的可读性,避免重复的代码,提高代码的复用性      在函数中能用return的不要print 1、递归的最大深度997 def foo(n):print(n)n+=1foo(n) foo(1) 递归的最大深度 2、修改递归的最大深度     由此我们可以看出,未报错之前能看到的最大数...

  •         Apache POI是一个开源的利用Java读写Excel,WORD等微软OLE2组件文档的项目。        我的需求是对Excel的数据进行导入或将数据以Excel的形式导出。先上简单的测试代码:package com.xing.studyTest.poi;import java.io.FileInputSt...

  • 要取得[a,b)的随机整数,使用(rand() % (b-a))+ a; 要取得[a,b]的随机整数,使用(rand() % (b-a+1))+ a; 要取得(a,b]的随机整数,使用(rand() % (b-a))+ a + 1; 通用公式:a + rand() % n;其中的a是起始值,n是整数的范围。 要取得a到b之间的...

  • 利用本征图像分解(Intrinsic Image Decomposition)算法,将图像分解为shading(illumination) image 和 reflectance(albedo) image,计算图像的reflectance image。 Reflectance Image 是指在变化的光照条件下能够维持不变的图像部分...

  • 题目:面试题39. 数组中出现次数超过一半的数字 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。 你可以假设数组是非空的,并且给定的数组总是存在多数元素。 示例 1: 输入: [1, 2, 3, 2, 2, 2, 5, 4, 2] 输出: 2 限制: 1 <= 数组长度 <= 50000 解题: cl...

  • 题目:二叉搜索树的后序遍历序列 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。 参考以下这颗二叉搜索树:      5     /    2   6   /  1   3示例 1: 输入: [1,6,3,2,5] 输出...