学习来源 通过自动回复机器人学Mybatis—加强版
使用MyBatis实现动态插入 LIMIT
至sql语句,进行分页数据的查询
当一个项目中出现多个sql的LIMIT
查询时就可以考虑是否可以将其封装一下,就传入对应的分段参数:起始index和长度,就可以了。这里可以用MyBatis的拦截器解决
在配置文件中写不带 LIMIT
的sql语句,但是规定,拦截器会拦截所有以 ByPage
结尾的id,根据调用dao方法时传入的分页参数为其添加 LIMIT
语句。
准备
使用到表结构是前面 学习笔记:javaEE基础10 MyBatis
文章中使用的 ContentAss
,java bean如下
1 2 3 4 5 6 7
| package com.example.microapp.bean; public class ContentAss { private int id; private String content; private TagAss tag; ....... }
|
配置的查询语句如下,根据指定的tagid查询对应的content
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.microapp.dao.IContentDao"> <resultMap id="ContentResultAss" type="com.example.microapp.bean.ContentAss"> <id column="content_id" jdbcType="INTEGER" property="id"/> <result column="content" jdbcType="VARCHAR" property="content"/> <association property="tag" resultMap="Tag.TagAssResult"/> </resultMap> <select id="queryContentListByPage" parameterType="java.util.Map" resultMap="ContentResultAss"> SELECT a.id content_id, a.content, b.id, b.name FROM content a LEFT JOIN tag b ON a.tagid = b.id <where> a.tagid = #{content.tag.id} </where> </select> </mapper>
|
如果tagid=3时,不加长度限制的话结果如下,太多了
在包entity下建立一个Page类,专门存储分类信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package com.example.microapp.entity; public class Page {
private int startIdx;
private int lengh;
private int totalLength; ...... }
|
为了解决需要同时传入查询参数tagid和分页信息,但是MyBatis查询时只能传入一个参数的问题,可以将将他们都放入一个HashMap中,将那个HashMap作为参数传入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @Test public void queryContentListByPage() throws Exception { Map<String, Object> map = new HashMap<>(); ContentAss content = new ContentAss();
Page page = new Page(); page.setStartIdx(0); page.setLengh(4);
TagAss tag = new TagAss(); tag.setId(3); content.setTag(tag); map.put("content", content); map.put("page", page);
List<ContentAss> resultList= ContentDaoImpl.getInstance().queryContentListByPage(map); resultList.forEach((v) -> { System.out.println(v); }); System.out.println(page); }
|
编写拦截器
基础配置过程和前一篇文章一样:实现 Interceptor
接口,在主配置文件中注册,这里就不再写了;下面为实现的分页拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| @Intercepts(@Signature(type = StatementHandler.class, method = "prepare", args ={Connection.class, Integer.class} )) public class ExamplePlugin implements Interceptor{ @Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler statementHandler = (StatementHandler)invocation.getTarget(); MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement"); String id = mappedStatement.getId();
if(id.matches(".+ByPage$")) { BoundSql boundSql = statementHandler.getBoundSql(); String sql = boundSql.getSql();
Connection connection = (Connection)invocation.getArgs()[0]; String countSql = "SELECT COUNT(*) FROM (" + sql + ")a"; PreparedStatement countStatement = connection.prepareStatement(countSql); ParameterHandler parameterHandler = (ParameterHandler)metaObject.getValue("delegate.parameterHandler"); parameterHandler.setParameters(countStatement); ResultSet rs = countStatement.executeQuery(); Map<?,?> parameter = (Map<?,?>)boundSql.getParameterObject(); Page page = (Page)parameter.get("page"); if(rs.next()) { page.setTotalLength(rs.getInt(1)); }
String pageSql = sql + " LIMIT " + page.getStartIdx() + "," + page.getLengh(); metaObject.setValue("delegate.boundSql.sql", pageSql); } Object result = invocation.proceed(); return result; }
@Override public Object plugin(Object target) { return Plugin.wrap(target, this); }
@Override public void setProperties(Properties properties) {
} }
|
要实现动态插入 LIMIT
,首先要获取当前sql的id以判断是否为需要处理的sql语句,其次要获得传入的sql语句,最后还需要查询总的数据长度赋给Page的totalLength属性,这里就需要使用到 MetaObject
对象了。
第一行已经通过 invocation.getTarget()
获得了实现了 StatementHandler
接口的 RoutingStatementHandler
,从调试信息可以看出,这里里面就有需要的id号,sql语句以及传入的参数
但是,delegate是私有属性,只能通过 MetaObject
获得
1 2 3 4
| MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY,SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement");
|
接下来就好办了,所有需要的值都可以取到
这样子就可以根据获取的id名判断当前sql语句是否需要修改了,但是如果要进行sql查询的话还需要得到 java.sql.Connection
;在之前的注解中第三个参数里已经传入了 Connection
类了,所以这里只需要用过 invocation.getArgs()[0]
取到,再做一个类型转换就可以了;后面的就比较好理解了,普通的JDBC代码
第一个sql语句就是拦截器中的查询总长度的sql语句,第三行就是经过拦截器处理过的sql语句,语句最后加上了LIMIT
;最终结果如下