数据库分页插件

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
@Intercepts({
@Signature(
type = Executor.class, // 目标类
method = "query", // 目标方法
args ={MappedStatement.class,
Object.class, RowBounds.class, ResultHandler.class}
)
})
public class MySqlPagingPlugin implements Interceptor {
private static final Integer MAPPED_STATEMENT_INDEX = 0;
private static final Integer PARAMETER_INDEX = 1;
private static final Integer ROW_BOUNDS_INDEX = 2;

@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
RowBounds rb = (RowBounds) args[ROW_BOUNDS_INDEX];
// 无需分页
if (rb == RowBounds.DEFAULT) {
return invocation.proceed();
}
// 将原 RowBounds 参数设为 RowBounds.DEFAULT,关闭 MyBatis 内置的分页机制
args[ROW_BOUNDS_INDEX] = RowBounds.DEFAULT;
MappedStatement ms = (MappedStatement) args[MAPPED_STATEMENT_INDEX];
BoundSql boundSql = ms.getBoundSql(args[PARAMETER_INDEX]);
// 获取 SQL 语句,拼接 limit 语句
String sql = boundSql.getSql();
String limit = String.format(
"LIMIT %d,%d", rb.getOffset(), rb.getLimit());
sql = sql + " " + limit;
// 创建一个 StaticSqlSource,并将拼接好的 sql 传入
SqlSource sqlSource = new StaticSqlSource(
ms.getConfiguration(), sql, boundSql.getParameterMappings());
// 通过反射获取并设置 MappedStatement 的 sqlSource 字段
Field field = MappedStatement.class.getDeclaredField("sqlSource");
field.setAccessible(true);
field.set(ms, sqlSource);
// 执行被拦截方法
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}

上面的分页插件通过 RowBounds 参数获取分页信息,并生成相应的 limit 语句。之后拼
接 sql,并使用该 sql 作为参数创建StaticSqlSource。最后通过反射替换MappedStatement 对象中的 sqlSource 字段。

测试

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
public interface StudentDao {
List<Student> findByPaging(@Param("id") Integer id, RowBounds rb);
}

<mapper namespace="xyz.coolblog.chapter7.dao.StudentDao">
<select id="findByPaging"
resultType="xyz.coolblog.chapter7.model.Student">
SELECT
`id`, `name`, `age`
FROM
student
WHERE
id > #{id}
</select>
</mapper>

public class PluginTest {
private SqlSessionFactory sqlSessionFactory;

@Before
public void prepare() throws IOException {
String resource = "chapter7/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
inputStream.close();
}

@Test
public void testPlugin() {
SqlSession session = sqlSessionFactory.openSession();
try {
StudentDao studentDao = session.getMapper(StudentDao.class);
studentDao.findByPaging(1, new RowBounds(20, 10));
} finally {
session.close();
}
}
}

image.png
SQL 语句中包含了 LIMIT 字样,这说明插件生效了