学习笔记:javaEE基础-13-MyBatis分页式查询

学习来源 通过自动回复机器人学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();

// 设置分页信息,起始坐标为0,长度为4
Page page = new Page();
page.setStartIdx(0);
page.setLengh(4);

// 设置tagid信息
TagAss tag = new TagAss();
tag.setId(3);
content.setTag(tag);
map.put("content", content);
map.put("page", page);

// 将map作为参数传入
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,便于获得statementHandler中的mappedStatement
MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());

// 获取statementHandler中的值
MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement");
String id = mappedStatement.getId();

// 判断当前sql语句是否要进行处理
if(id.matches(".+ByPage$")) {
// 获取解析的sql数据
BoundSql boundSql = statementHandler.getBoundSql();
// 原始的SQL语句
String sql = boundSql.getSql();


// 获取Connection对象,以便进行sql操作
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));
}

// 改造后带分页查询的SQL语句
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());
// 获取statementHandler中的值
MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement");

接下来就好办了,所有需要的值都可以取到

这样子就可以根据获取的id名判断当前sql语句是否需要修改了,但是如果要进行sql查询的话还需要得到 java.sql.Connection;在之前的注解中第三个参数里已经传入了 Connection 类了,所以这里只需要用过 invocation.getArgs()[0] 取到,再做一个类型转换就可以了;后面的就比较好理解了,普通的JDBC代码

第一个sql语句就是拦截器中的查询总长度的sql语句,第三行就是经过拦截器处理过的sql语句,语句最后加上了LIMIT;最终结果如下