MybatisPlus两个limit引发的思考(下)——mapper接口的初始化及PaginationInterceptor是如何拼接limit的
mapper接口的初始化
我们还是要先了解下MapperFactoryBean:
-
是一个工厂bean,用于创建Mybatis Mapper代理实例,该代理对象实现了指定的Mapper接口。继承了SqlSessionDaoSupport类并实现了FactoryBean接口。
-
它覆盖了getObject()方法,该方法返回了一个代理对象,该代理对象实现了指定的Mapper接口,该代理实例会自动执行相应的SQL语句并将结果映射到Java对象中。
-
可以配置mapper接口所需要的SqlSessionTemplate和DataSource等信息,以便进行数据库操作。
下面我们来看看具体是怎么实例化的,是怎么通过MapperFactoryBean来创建最终的代理MybatisMapperProxy的。
这里我在MapperFactoryBean的构造器上打了个断点:
源头上是从ApplicationContext#refresh的registerBeanPostProcessor进行注册BeanPostProcessor时会检查所有的bean definitions,如果可以进行实例化就会进行实例化。
MapperFactoryBean已经实例化了,但不是最终的bean。
我们在AbstractAutowireCapableBeanFactory#createBean打个断点:
这里可以看到MapperFactoryBean已经完成了实例化并注入了属性,下面要对bean进行初始化。
完成MapperFactoryBean的初始化之后会调用MapperFactoryBean#getObject创建MybatisMapperProxy:
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
SqlSessionTemplate#getMapper:
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
MybatisConfiguration#getMapper:
@Override
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mybatisMapperRegistry.getMapper(type, sqlSession);
}
MybatisMapperProxyFactory:
public T newInstance(SqlSession sqlSession) {
final MybatisMapperProxy<T> mapperProxy = new MybatisMapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MybatisMapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}
可以看到最终使用MybatisMapperProxy对mapper接口进行了代理,使用的是jdk动态代理,MybatisMapperProxy继承了InvocationHandler:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (method.isDefault()) {
if (privateLookupInMethod == null) {
return invokeDefaultMethodJava8(proxy, method, args);
} else {
return invokeDefaultMethodJava9(proxy, method, args);
}
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MybatisMapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
时序图
简单的描述下整个过程:
- 这里是MapperScannerConfigurer#postProcessBeanDefinitionRegistry中的processPropertyPlaceHolder寻找PropertyResourceConfigurer类型的bean触发的
从这里可以看到并非一定由寻找PropertyResourceConfigurer类型的bean触发,寻找其他类型的bean同样也会触发,如果实在spring容器初始化没有发生寻找没有触发也没事,发生依赖时也会触发MapperFactoryBean实例化&初始化,可能不会直接从factoryBeanInstanceCache中获取。
- 调用MapperFactoryBean的构造器进行实例化
- 在初始化的时候调用MapperFactoryBean#getObject进而调用MybatisMapperProxyFactory#newInstantce,使用sqlSessionTemplate和mapperInterface生成代理bean。
PaginationInterceptor如何生效的
直接在PaginationInterceptor#intercept上打个断点:
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
// SQL 解析
this.sqlParser(metaObject);
// 先判断是不是SELECT操作 (2019-04-10 00:37:31 跳过存储过程)
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
if (SqlCommandType.SELECT != mappedStatement.getSqlCommandType()
|| StatementType.CALLABLE == mappedStatement.getStatementType()) {
return invocation.proceed();
}
// 针对定义了rowBounds,做为mapper接口方法的参数
BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
Object paramObj = boundSql.getParameterObject();
// 判断参数里是否有page对象 #1
IPage<?> page = ParameterUtils.findPage(paramObj).orElse(null);
/*
* 不需要分页的场合,如果 size 小于 0 返回结果集
*/
if (null == page || page.getSize() < 0) {
return invocation.proceed();
}
if (this.limit > 0 && this.limit <= page.getSize()) {
//处理单页条数限制
handlerLimit(page);
}
String originalSql = boundSql.getSql();
Connection connection = (Connection) invocation.getArgs()[0];
if (page.isSearchCount() && !page.isHitCount()) {
SqlInfo sqlInfo = SqlParserUtils.getOptimizeCountSql(page.optimizeCountSql(), countSqlParser, originalSql);
this.queryTotal(sqlInfo.getSql(), mappedStatement, boundSql, page, connection);
if (page.getTotal() <= 0) {
return null;
}
}
DbType dbType = this.dbType == null ? JdbcUtils.getDbType(connection.getMetaData().getURL()) : this.dbType;
IDialect dialect = Optional.ofNullable(this.dialect).orElseGet(() -> DialectFactory.getDialect(dbType));
String buildSql = concatOrderBy(originalSql, page);
// #2
DialectModel model = dialect.buildPaginationSql(buildSql, page.offset(), page.getSize());
Configuration configuration = mappedStatement.getConfiguration();
List<ParameterMapping> mappings = new ArrayList<>(boundSql.getParameterMappings());
Map<String, Object> additionalParameters = (Map<String, Object>) metaObject.getValue("delegate.boundSql.additionalParameters");
model.consumers(mappings, configuration, additionalParameters);
metaObject.setValue("delegate.boundSql.sql", model.getDialectSql());
metaObject.setValue("delegate.boundSql.parameterMappings", mappings);
return invocation.proceed();
}
- 从BoundSql中获得IPage参数
- 这里会多拼接一个limit,最后赋值给了metaObject:
@Override
public DialectModel buildPaginationSql(String originalSql, long offset, long limit) {
String sql = originalSql + " LIMIT " + FIRST_MARK + StringPool.COMMA + SECOND_MARK;
return new DialectModel(sql, offset, limit).setConsumerChain();
}
转载自:https://juejin.cn/post/7246694803815530557