盘点 MyBatis 自定义插件的使用及 PageHel

总文档 :文章目录

Github : github.com/black-ant

一 . 前言

文章目的 :

  • 了解 MyBatis 插件的用法
  • 了解 主要的源码逻辑
  • 了解 PageHelper 的相关源码

这是一篇概括性文档 , 用于后续快速使用相关功能 , 整体难度较低.

二 . 流程

2.1 基础用法

基础拦截器类

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
java复制代码@Intercepts(
{@org.apache.ibatis.plugin.Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class DefaultInterceptor implements Interceptor {

private Logger logger = LoggerFactory.getLogger(this.getClass());


@Override
public Object intercept(Invocation invocation) throws Throwable {
logger.info("------> this is in intercept <-------");
return invocation.proceed();
}

@Override
public Object plugin(Object target) {
logger.info("------> this is in plugin <-------");
return Plugin.wrap(target, this);
}

@Override
public void setProperties(Properties properties) {
logger.info("------> this is in setProperties <-------");
}
}

基础配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码@Configuration
public class PluginsConfig {

@Autowired
private List<SqlSessionFactory> sqlSessionFactoryList;

@PostConstruct
public void addPageInterceptor() {
DefaultInterceptor interceptor = new DefaultInterceptor();
// 此处往 SqlSessionFactory 中添加
for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
sqlSessionFactory.getConfiguration().addInterceptor(interceptor);
}
}
}

这里可以看到 , 拦截到的参数如下 :

mybatis_plugin001.jpg

2.2 功能详解

整个拦截过程有几个主要的组成部分 :

Interceptor 拦截器接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码
M- Interceptor#intercept(Invocation invocation) : 拦截方法
M- plugin : 调用 Plugin#wrap(Object target, Interceptor interceptor) 方法,执行代理对象的创建
M- setProperties : 从 properties 获取一些需要的属性值

// 这里可以看到 , 其中强制实现的方法只有 intercept


public interface Interceptor {

Object intercept(Invocation invocation) throws Throwable;

default Object plugin(Object target) {
return Plugin.wrap(target, this);
}

default void setProperties(Properties properties) {
}

}

InterceptorChain 拦截器链

InterceptorChain 是一个拦截器链 , 用于执行相关的Interceptor 操作 , 其中有一个拦截器集合

1
2
3
4
5
6
7
8
java复制代码// 主要的大纲如此所示
F- List<Interceptor> ArrayList
M- addInterceptor : 添加拦截器
- 在 Configuration 的 #pluginElement(XNode parent) 方法中被调用
- 创建 Interceptor 对象,并调用 Interceptor#setProperties(properties) 方法
- 调用 Configuration#addInterceptor(interceptorInstance) 方法
- 添加到 Configuration.interceptorChain 中
M- pluginAll : 应用所有拦截器到指定目标对象

注解 @Intercepts@Signature

这是过程中主要涉及的2个注解 :

  • @Intercepts : 定义拦截器 , Intercepts 中可以包含一个 Signature 数组
  • @Signature : 定义类型
    • type : 拦截器处理的类
    • method : 拦截的方法
    • args : 方法参数 (重载的原因)

2.3 源码跟踪

Step 1 : 资源的加载

资源的加载主要是对对应方法的代理逻辑 , 从前文我们可以看到 , 一个 plugin 操作 , 主要包含2个步骤 :

1
2
3
4
5
6
7
8
9
10
11
java复制代码
// 步骤一 : 声明拦截器对象
@Intercepts(
{@org.apache.ibatis.plugin.Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})


// 步骤二 : sqlSessionFactory 中添加拦截器对象
sqlSessionFactory.getConfiguration().addInterceptor(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
java复制代码C01- Configuration
F01_01- InterceptorChain interceptorChain
M01_01- addInterceptor
- interceptorChain.addInterceptor(interceptor)
?- 可以看到 , 这里往 InterceptorChain 添加了 interceptor

// M01_01 源代码
public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}



public class InterceptorChain {

private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}

// 可以看到 , 这里将拦截器加到了拦截器链中
// PS : 但是此处未完全完成 , 仅仅只是添加 , 具体的操作会在上面 pluginAll 中完成
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}

public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}

}

Step 2 : plugin 的构建

Step 1 中已经完成了相关 interceptors 的添加 , 这个环节需要通过 Interceptor 构建对应的 Plugin

先来看一下调用链 :

  • C- SqlSessionTemplate # selectList : 发起 Select 请求
  • C- SqlSessionInterceptor # invoke : 构建一个 Session 代理
  • C- SqlSessionUtils # getSqlSession : 获取 Session 对象
  • C- DefaultSqlSessionFactory # openSessionFromDataSource
  • C- Configuration # newExecutor

整体来说 ,从 getSqlSession 开始关注即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {

notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}

// 核心节点 , 开启 Session
session = sessionFactory.openSession(executorType);

registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

return session;
}

DefaultSqlSessionFactory 构建一个 Session , 同时调用 Plugin 生成逻辑

此处构建 Session 的同时 , 最终调用 Plugin warp 构建 Plugin

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
java复制代码C- DefaultSqlSessionFactory
// 打开的起点 : 打开 session 的时候 ,
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);

// 此处构建 Executor 并且放入 Session
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}


// 调用拦截链
C- Configuration
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 此处调用拦截链 , 构建 Executor 对象
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}

// 拦截链处理
C- InterceptorChain
public Object pluginAll(Object target) {
// 此处是在构建拦截器链 , 返回的是最后的拦截器处理类
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}


C- Plugin
// 调用 Plugin wrap , 生成了一个新的 plugin , 该 plugin 包含对应的 interceptor 的 拦截方法
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
// 核心 , 对方法做了代理 , 同时为代理类传入了 Plugin
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}

Step 3 : invoke 执行

拦截器的实现是基于 Proxy 代理实现的 , 在上文看到了代理的生成 , 这里看一下代理的调用 :

以 Query 为例 :

当调用 Executor 中 Query 方法时 , 会默认调用代理类 , 上文说了 Execute 的构建 , 那么他在整个逻辑中是作为什么角色的?

Executor 作用 : Executor 是 Mybatis 中的顶层接口 , 定义了主要的数据库操作方法

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
java复制代码public interface Executor {

ResultHandler NO_RESULT_HANDLER = null;

int update(MappedStatement ms, Object parameter) throws SQLException;

<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;

List<BatchResult> flushStatements() throws SQLException;

void commit(boolean required) throws SQLException;

void rollback(boolean required) throws SQLException;

CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);

boolean isCached(MappedStatement ms, CacheKey key);

void clearLocalCache();

void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);

Transaction getTransaction();

void close(boolean forceRollback);

boolean isClosed();

void setExecutorWrapper(Executor executor);

}

execute 的调用 :

前面构建 Session 的时候 , 已经为其定义了Plugin , 以下为 Plugin 的主要流程

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
java复制代码// 1 . 选择合适的 plugin
C- DefaultSqlSession
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

// 2 . 中间通过 proxy 代理


// 3 . plugin 中调用拦截器
C05- Plugin
F05_01- private final Interceptor interceptor;
M05_01- invoke(Object proxy, Method method, Object[] args)
- 获取可以拦截的 method
- 判断当前的 method 是否在可拦截的


// M05_01 : invoke@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
// 此处调用了拦截器
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}

// 4 . 调用 interceptor
public Object intercept(Invocation invocation) throws Throwable {
// 此处调用代理方法的实际逻辑
return invocation.proceed();
}

三 . 扩展 PageHelper

以下为 PageHelper 中如何使用相关的拦截器方法的 :

PageHelper 中主要有 2个拦截器 :

  • QueryInterceptor : 查询操作插件
  • PageInterceptor : 分页操作插件

主要看一下 PageInterceptor 拦截器 :

拦截器代码

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
java复制代码@Intercepts(
{
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
}
)
public class PageInterceptor implements Interceptor {
private volatile Dialect dialect;
private String countSuffix = "_COUNT";
protected Cache<String, MappedStatement> msCountMap = null;
private String default_dialect_class = "com.github.pagehelper.PageHelper";

@Override
public Object intercept(Invocation invocation) throws Throwable {
try {
Object[] args = invocation.getArgs();

// 获取请求的四个参数
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
RowBounds rowBounds = (RowBounds) args[2];
ResultHandler resultHandler = (ResultHandler) args[3];
Executor executor = (Executor) invocation.getTarget();
CacheKey cacheKey;
BoundSql boundSql;
//由于逻辑关系,只会进入一次
if (args.length == 4) {
//4 个参数时
// 获取绑定 SQL : select id, type_code, type_class, type_policy, type_name, supplier_id, supplier_name from sync_type
boundSql = ms.getBoundSql(parameter);
cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
} else {
//6 个参数时
cacheKey = (CacheKey) args[4];
boundSql = (BoundSql) args[5];
}
checkDialectExists();

List resultList;
//调用方法判断是否需要进行分页,如果不需要,直接返回结果
if (!dialect.skip(ms, parameter, rowBounds)) {
//判断是否需要进行 count 查询
if (dialect.beforeCount(ms, parameter, rowBounds)) {
//查询总数
Long count = count(executor, ms, parameter, rowBounds, resultHandler, boundSql);
//处理查询总数,返回 true 时继续分页查询,false 时直接返回
if (!dialect.afterCount(count, parameter, rowBounds)) {
//当查询总数为 0 时,直接返回空的结果
return dialect.afterPage(new ArrayList(), parameter, rowBounds);
}
}
resultList = ExecutorUtil.pageQuery(dialect, executor,
ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
} else {
//rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
return dialect.afterPage(resultList, parameter, rowBounds);
} finally {
dialect.afterAll();
}
}

/**
* Spring bean 方式配置时,如果没有配置属性就不会执行下面的 setProperties 方法,就不会初始化
* <p>
* 因此这里会出现 null 的情况 fixed #26
*/
private void checkDialectExists() {
if (dialect == null) {
synchronized (default_dialect_class) {
if (dialect == null) {
setProperties(new Properties());
}
}
}
}

private Long count(Executor executor, MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql) throws SQLException {
String countMsId = ms.getId() + countSuffix;
Long count;
//先判断是否存在手写的 count 查询
MappedStatement countMs = ExecutorUtil.getExistedMappedStatement(ms.getConfiguration(), countMsId);
if (countMs != null) {
count = ExecutorUtil.executeManualCount(executor, countMs, parameter, boundSql, resultHandler);
} else {
countMs = msCountMap.get(countMsId);
//自动创建
if (countMs == null) {
//根据当前的 ms 创建一个返回值为 Long 类型的 ms
countMs = MSUtils.newCountMappedStatement(ms, countMsId);
msCountMap.put(countMsId, countMs);
}
count = ExecutorUtil.executeAutoCount(dialect, executor, countMs, parameter, boundSql, rowBounds, resultHandler);
}
return count;
}

@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}

@Override
public void setProperties(Properties properties) {
//缓存 count ms
msCountMap = CacheFactory.createCache(properties.getProperty("msCountCache"), "ms", properties);
String dialectClass = properties.getProperty("dialect");
if (StringUtil.isEmpty(dialectClass)) {
dialectClass = default_dialect_class;
}
try {
Class<?> aClass = Class.forName(dialectClass);
dialect = (Dialect) aClass.newInstance();
} catch (Exception e) {
throw new PageException(e);
}
dialect.setProperties(properties);

String countSuffix = properties.getProperty("countSuffix");
if (StringUtil.isNotEmpty(countSuffix)) {
this.countSuffix = countSuffix;
}
}

}

补充 :ExecutorUtil.executeAutoCount 相关逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码/**
* 执行自动生成的 count 查询
**/
public static Long executeAutoCount(Dialect dialect, Executor executor, MappedStatement countMs,
Object parameter, BoundSql boundSql,
RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);
//创建 count 查询的缓存 key
CacheKey countKey = executor.createCacheKey(countMs, parameter, RowBounds.DEFAULT, boundSql);
// 调用方言获取 count sql
// SELECT count(0) FROM sync_type
String countSql = dialect.getCountSql(countMs, boundSql, parameter, rowBounds, countKey);
//countKey.update(countSql);
BoundSql countBoundSql = new BoundSql(countMs.getConfiguration(), countSql, boundSql.getParameterMappings(), parameter);
//当使用动态 SQL 时,可能会产生临时的参数,这些参数需要手动设置到新的 BoundSql 中
for (String key : additionalParameters.keySet()) {
countBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
}
//执行 count 查询
Object countResultList = executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql);
Long count = (Long) ((List) countResultList).get(0);
return count;
}

补充 : pageQuery 查询

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
java复制代码public static  <E> List<E> pageQuery(Dialect dialect, Executor executor, MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql, CacheKey cacheKey) throws SQLException {
//判断是否需要进行分页查询
if (dialect.beforePage(ms, parameter, rowBounds)) {
//生成分页的缓存 key
CacheKey pageKey = cacheKey;
//处理参数对象 , 此处生成分页的参数
parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey);
//调用方言获取分页 sql
String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);
BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);

Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);
//设置动态参数
for (String key : additionalParameters.keySet()) {
pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
}
//执行分页查询
return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
} else {
//不执行分页的情况下,也不执行内存分页
return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
}
}

因为源码的注释程度很高 , 所以基本上不需要做额外的标注了 , 整体的流程就是 :

  • Step 1 : 拦截器 intercept方法定义整体逻辑
  • Step 2 : count 方法决定他是否分页
  • Step 3 :pageQuery 调用方言进行事情SQL的拼接

整体中有几点值得关注 :

  • 只生成分页的 row 和 pageKey 等 , 在最终通过方言组合 , 以适应多种数据库结构
  • 核心还是调用 executor.query 原生方法

简述一下 PageHepler 的绑定流程

核心处理类为 AbstractHelperDialect , 先从创建开始 :

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
java复制代码PageHelper.startPage(page, size);
List<SyncType> allOrderPresentList = syncTypeDAO.findAll();

// Step 1 : startPage 核心代码
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
Page<E> page = new Page<E>(pageNum, pageSize, count);
page.setReasonable(reasonable);
page.setPageSizeZero(pageSizeZero);
//当已经执行过orderBy的时候
Page<E> oldPage = getLocalPage();
if (oldPage != null && oldPage.isOrderByOnly()) {
page.setOrderBy(oldPage.getOrderBy());
}
setLocalPage(page);
return page;
}

此处最核心的就是 setLocalPage , 会使用 ThreadLocal 保持线程参数
protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();

// Step 2 : 拦截器中的参数获取
// 可以看到第一句就是从 getLocalPage - ThreadLocal 中获取
C- AbstractHelperDialect # processParameterObject

public Object processParameterObject(MappedStatement ms, Object parameterObject, BoundSql boundSql, CacheKey pageKey) {
//处理参数
Page page = getLocalPage();
//如果只是 order by 就不必处理参数
if (page.isOrderByOnly()) {
return parameterObject;
}
Map<String, Object> paramMap = null;
if (parameterObject == null) {
paramMap = new HashMap<String, Object>();
} else if (parameterObject instanceof Map) {
//解决不可变Map的情况
paramMap = new HashMap<String, Object>();
paramMap.putAll((Map) parameterObject);
} else {
paramMap = new HashMap<String, Object>();
//动态sql时的判断条件不会出现在ParameterMapping中,但是必须有,所以这里需要收集所有的getter属性
//TypeHandlerRegistry可以直接处理的会作为一个直接使用的对象进行处理
boolean hasTypeHandler = ms.getConfiguration().getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass());
MetaObject metaObject = MetaObjectUtil.forObject(parameterObject);
//需要针对注解形式的MyProviderSqlSource保存原值
if (!hasTypeHandler) {
for (String name : metaObject.getGetterNames()) {
paramMap.put(name, metaObject.getValue(name));
}
}
//下面这段方法,主要解决一个常见类型的参数时的问题
if (boundSql.getParameterMappings() != null && boundSql.getParameterMappings().size() > 0) {
for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
String name = parameterMapping.getProperty();
if (!name.equals(PAGEPARAMETER_FIRST)
&& !name.equals(PAGEPARAMETER_SECOND)
&& paramMap.get(name) == null) {
if (hasTypeHandler
|| parameterMapping.getJavaType().equals(parameterObject.getClass())) {
paramMap.put(name, parameterObject);
break;
}
}
}
}
}
return processPageParameter(ms, paramMap, page, boundSql, pageKey);
}

剩下逻辑也比较清晰 , 也不涉及太多此处逻辑了 , 有需求再看更多

总结

这应该是写的最简单的一篇源码分析了 , 除了本身结构不复杂以外 , 相关源码的注释也很清晰 , 基本上没有什么分析的需求.

拦截器的使用 :

  • 准备拦截器类
  • sqlSessionFactory.getConfiguration().addInterceptor(interceptor) 添加拦截器

PageHelper 核心 :

  • parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey) : 获取分页的参数
  • dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey) : 参数解析为 SQL
  • ThreadLocal 保存参数

后续我们再关注一下Mybatis 其他要点和 PageHelper 的整体涉及优势

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%