1. mybatis插件

在mybatis中使用插件,需要先实现接口Interceptor:

1
2
3
4
5
6
7
8
9
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}

Interceptor中有三个方法:

  • intercept方法:是插件的核心方法,它将直接覆盖你所拦截的对象原有的方法。intercept里有个参数Invocation对象,通过它可以反射调度原料对象的方法。
  • plugin方法:target是被拦截对象,plugin方法的作用是给被拦截对象生成一个代理对象并返回。
  • setProperties方法:允许在plugin中配置所需要的参数,该方法在插件初始化的时候就被调用一次,然后把插件对象存到配置中,以便后续取出。

2. PageHelper

PageHelper会使用ThreadLocal获取到同一线程中的变量信息,各个线程之间的Threadlocal不会相互干扰,也就是Thread1中的ThreadLocal1之后获取到Tread1中的变量的信息,不会获取到Thread2中的信息所以在多线程环境下,各个Threadlocal之间相互隔离,可以实现,不同thread使用不同的数据源或不同的Thread中执行不同的SQL语句,所以,PageHelper利用这一点通过拦截器获取到同一线程中的预编译好的SQL语句之后将SQL语句包装成具有分页功能的SQL语句,并将其再次赋值给下一步操作,所以实际执行的SQL语句就是有了分页功能的SQL语句。

startPage源码:

1
2
3
4
5
6
7
8
9
10
/**
* 开始分页
*
* @param pageNum 页码
* @param pageSize 每页显示数量
* @param count 是否进行count查询
*/
public static Page startPage(int pageNum, int pageSize, boolean count) {
return startPage(pageNum, pageSize, count, null);
}

最终执行的代码:

1
2
3
4
5
6
7
public static Page startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
Page page = new Page(pageNum, pageSize, count);
page.setReasonable(reasonable);
page.setPageSizeZero(pageSizeZero);
SqlUtil.setLocalPage(page);
return page;
}

其中SqlUtil.setLocalPage是:

1
2
3
public static void setLocalPage(Page page) {
LOCAL_PAGE.set(page);
}

LOCAL_PAGE是:

1
private static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();

PageHelper就是使用ThreadLocal存储了Page对象,在这个对象中有我们设置的pageNum、pageSize,也会查询返回的pages、total。

3. PageHelper执行过程

1
Page page = PageHelper.startPage(pageNumber, pageSize, true);

配置好PageHelper后,在代码中使用PageHelper后,LOCAL_PAGE会存入分页信息。

在PageHelper下的第一个查询的方法会被PageHelper拦截住,然后重新组装sql语句。具体组装的源码为:

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
56
57
58
59
60
61
62
63
64
65
66
67
68
private Object _processPage(Invocation invocation) throws Throwable {
final Object[] args = invocation.getArgs();
RowBounds rowBounds = (RowBounds) args[2];
if (SqlUtil.getLocalPage() == null && rowBounds == RowBounds.DEFAULT) {
if (OrderByHelper.getOrderBy() != null) {
OrderByHelper.processIntercept(invocation);
}
return invocation.proceed();
} else {
//获取原始的ms
MappedStatement ms = (MappedStatement) args[0];
//判断并处理为PageSqlSource
if (!isPageSqlSource(ms)) {
processMappedStatement(ms, parser);
}
//忽略RowBounds-否则会进行Mybatis自带的内存分页
args[2] = RowBounds.DEFAULT;
//分页信息
Page page = getPage(rowBounds);
//pageSizeZero的判断
if ((page.getPageSizeZero() != null && page.getPageSizeZero()) && page.getPageSize() == 0) {
COUNT.set(null);
//执行正常(不分页)查询
Object result = invocation.proceed();
//得到处理结果
page.addAll((List) result);
//相当于查询第一页
page.setPageNum(1);
//这种情况相当于pageSize=total
page.setPageSize(page.size());
//仍然要设置total
page.setTotal(page.size());
//返回结果仍然为Page类型 - 便于后面对接收类型的统一处理
return page;
}
//简单的通过total的值来判断是否进行count查询
if (page.isCount()) {
COUNT.set(Boolean.TRUE);
//替换MS
args[0] = msCountMap.get(ms.getId());
//查询总数
Object result = invocation.proceed();
//还原ms
args[0] = ms;
//设置总数
page.setTotal((Integer) ((List) result).get(0));
if (page.getTotal() == 0) {
return page;
}
}
//pageSize>0的时候执行分页查询,pageSize<=0的时候不执行相当于可能只返回了一个count
if (page.getPageSize() > 0 &&
((rowBounds == RowBounds.DEFAULT && page.getPageNum() > 0)
|| rowBounds != RowBounds.DEFAULT)) {
//将参数中的MappedStatement替换为新的qs
COUNT.set(null);
BoundSql boundSql = ms.getBoundSql(args[1]);
args[1] = parser.setPageParameter(ms, args[1], boundSql, page);
COUNT.set(Boolean.FALSE);
//执行分页查询
Object result = invocation.proceed();
//得到处理结果
page.addAll((List) result);
}
//返回结果
return page;
}
}

在实际查询中打印出sql为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ooo Using Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@25428560]
==> Preparing: SELECT count(*) FROM translator t LEFT JOIN account a ON a.translator_id = t.id AND a.delete_flag = 1 WHERE t.delete_flag = 1 AND t.account_status != 0 AND t.account_status != 3
==> Parameters:
<== Columns: count(*)
<== Row: 5
<== Total: 1
ooo Using Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@25428560]
==> Preparing: SELECT a.name as name, a.account_id as account_id, a.nick_name as nick_name, t.sex as sex, a.phone_number as phone_number, t.id as id, t.translator_level as translator_level, t.account_status as account_status FROM translator t LEFT JOIN account a on a.translator_id= t.id AND a.delete_flag = 1 WHERE t.delete_flag = 1 and t.account_status != 0 and t.account_status != 3 limit ?,?
==> Parameters: 0(Integer), 3(Integer)
<== Columns: name, account_id, nick_name, sex, phone_number, id, translator_level, account_status
<== Row: 121212@163.com, 1, 王五, 0, 13310101001, 1, null, 4
<== Row: 222222@qq.com, 9, 张三, 1, 18811221122, 2, null, 4
<== Row: lisi, 12, lisi, 0, 13899988223, 4, 1, 4
<== Total: 3

不加PageHelper打印出的sql为:

1
2
3
4
5
6
7
8
9
10
ooo Using Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@30e836ca]
==> Preparing: SELECT a.name as name, a.account_id as account_id, a.nick_name as nick_name, t.sex as sex, a.phone_number as phone_number, t.id as id, t.translator_level as translator_level, t.account_status as account_status FROM translator t LEFT JOIN account a on a.translator_id= t.id AND a.delete_flag = 1 WHERE t.delete_flag = 1 and t.account_status != 0 and t.account_status != 3
==> Parameters:
<== Columns: name, account_id, nick_name, sex, phone_number, id, translator_level, account_status
<== Row: 121212@163.com, 1, 王五, 0, 13310101001, 1, null, 4
<== Row: 222222@qq.com, 9, 张三, 1, 18811221122, 2, null, 4
<== Row: lisi, 12, lisi, 0, 13899988223, 4, 1, 4
<== Row: wangmazi, 15, wangmazi, 0, 17722818182, 18, null, 1
<== Row: xiaohua, 14, xiaohua, 1, 16322818182, 19, 0, 4
<== Total: 5