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"
      }
    ]
  }
}

  

关键点:

  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类:

 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> pagingCommand=new ThreadLocal<PagingCommand>();
 3     private static ThreadLocal<Integer> totalCount=new ThreadLocal<Integer>();
 4     private static ThreadLocal<Integer> totalPage=new ThreadLocal<Integer>();
 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<Object>) 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 }

 


完成。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值