首先看下
com\github\pagehelper\pagehelper-spring-boot-autoconfigure\1.2.10\pagehelper-spring-boot-autoconfigure-1.2.10.jar!\META-INF\spring.factories这个文件,这个是springboot自动配置,指向一个配置类
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.github.pagehelper.autoconfigure.PageHelperAutoConfiguration
那么就看下这个配置类吧,这个配置类的主要内容,就是遍历所有的sqlSessionFactory,然后获取配置添加interceptor
/**
* 自定注入分页插件
*
* @author liuzh
*/
@Configuration
@ConditionalOnBean(SqlSessionFactory.class)
@EnableConfigurationProperties(PageHelperProperties.class)
@AutoConfigureAfter(MybatisAutoConfiguration.class)
public class PageHelperAutoConfiguration {
@Autowired
private List<SqlSessionFactory> sqlSessionFactoryList;
@Autowired
private PageHelperProperties properties;
/**
* 接受分页插件额外的属性
*
* @return
*/
@Bean
@ConfigurationProperties(prefix = PageHelperProperties.PAGEHELPER_PREFIX)
public Properties pageHelperProperties() {
return new Properties();
}
@PostConstruct
public void addPageInterceptor() {
PageInterceptor interceptor = new PageInterceptor();
Properties properties = new Properties();
//先把一般方式配置的属性放进去
properties.putAll(pageHelperProperties());
//在把特殊配置放进去,由于close-conn 利用上面方式时,属性名就是 close-conn 而不是 closeConn,所以需要额外的一步
properties.putAll(this.properties.getProperties());
interceptor.setProperties(properties);
//遍历所有的sqlSessionFactory,然后获取配置添加interceptor
for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
sqlSessionFactory.getConfiguration().addInterceptor(interceptor);
}
}
}
接着看下PageInterceptor类,直接看其plugin方法(之前说过创建Executer时执行的pluginAll方法)
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
然后就是Plugin.wrap(target, this);方法了,org.apache.ibatis.plugin.Plugin#wrap
public static Object wrap(Object target, Interceptor interceptor) {
/**
* 获取interceptor的注解内容
* PageInterceptor注解
* @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}),
* }
* )
*/
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
/**
* 【获取被包装的对象所实现的所有的接口,比如Executer或者ParameterHandler】 与 signatureMap的keySet 交集----》》》这里signatureMap的key只有一个Executor.class
*/
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
// 如果交集不为空,则创建代理对象,创建代理对象时所用的InvocationHandler是Plugin类,那就要看Plugin的invoke方法了
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
//为空,就直接返回原对象
return target;
}
org.apache.ibatis.plugin.Plugin#invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//获取到当前接口的实现类需要拦截的方法
//这里的method有两个,就是注解中配置的那两个方法
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
//如果是需要拦截的方法,就执行PageInterceptor.intercept()方法
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);
}
}
紧接着就是要看下com.github.pagehelper.PageInterceptor#intercept
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 个参数时
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();
}
}
com.github.pagehelper.util.ExecutorUtil#pageQuery 这里需要关注两点
1.参数配置 parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey);
2.sql语句的修改 String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);
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);
}
}
参数配置涉及到不同数据库不同的处理,这里就直接看mysql的
com.github.pagehelper.dialect.helper.MySqlDialect#processPageParameter
public Object processPageParameter(MappedStatement ms, Map<String, Object> paramMap, Page page, BoundSql boundSql, CacheKey pageKey) {
//在参数中添加偏移量和查询数量
paramMap.put(PAGEPARAMETER_FIRST, page.getStartRow());
paramMap.put(PAGEPARAMETER_SECOND, page.getPageSize());
//处理pageKey
pageKey.update(page.getStartRow());
pageKey.update(page.getPageSize());
//处理参数配置
if (boundSql.getParameterMappings() != null) {
List<ParameterMapping> newParameterMappings = new ArrayList<ParameterMapping>(boundSql.getParameterMappings());
if (page.getStartRow() == 0) {
newParameterMappings.add(new ParameterMapping.Builder(ms.getConfiguration(), PAGEPARAMETER_SECOND, Integer.class).build());
} else {
newParameterMappings.add(new ParameterMapping.Builder(ms.getConfiguration(), PAGEPARAMETER_FIRST, Integer.class).build());
newParameterMappings.add(new ParameterMapping.Builder(ms.getConfiguration(), PAGEPARAMETER_SECOND, Integer.class).build());
}
MetaObject metaObject = MetaObjectUtil.forObject(boundSql);
metaObject.setValue("parameterMappings", newParameterMappings);
}
return paramMap;
}
同样的sql的修改也涉及到不同数据库不同的处理,这里就直接看mysql的
com.github.pagehelper.dialect.helper.MySqlDialect#getPageSql 也不难看懂,直接拼接字符串(当然之前也涉及到order by操作)
public String getPageSql(String sql, Page page, CacheKey pageKey) {
StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
sqlBuilder.append(sql);
if (page.getStartRow() == 0) {
sqlBuilder.append(" LIMIT ? ");
} else {
sqlBuilder.append(" LIMIT ?, ? ");
}
return sqlBuilder.toString();
}