group : com.baomidou
version:3.5.2.2-SNAPSHOT
因为我在使用的时候发现分页不生效,得分析一下找到原因。
我的分页不生效。
com.baomidou.mybatisplus.extension.plugins.pagination的Page对象。
代码如下:
public void getAll(){
// 为什么我的分页实效了?
Page<SkyworthUser> objects =Page.of(1,10);
IPage<SkyworthUser> all = userInfoMapper.getAll(objects);
System.out.println(all.getRecords().size());
}
public interface UserInfoMapper extends BaseMapper<SkyworthUser> {
IPage<SkyworthUser> getAll(IPage<SkyworthUser> page);
void updateId(@Param("id") String id,@Param("flag") int flag);
}
<select id="getAll" resultType="com.qhyu.cloud.model.SkyworthUser">
Select * from skyworth_user
</select>
首先根据我们前几章的梳理,按照我的理解来画出整体流程图,然后分析那个地方可能出现问题。
首先这张图的细节在本章节后续会详细讲解,当前我的分页查询不生效的原因,我基本断定是没有将MybatisPlusInterceptor引入到项目中,然后需要将PaginationInnerInterceptor添加到MybatisPlusInterceptor的interceptors的属性中。
@ComponentScan("com.qhyu.cloud")
@MapperScan(value = "com.qhyu.cloud.mapper.**")
// spring会把META-INFO中的东西扫起来,注入到容器,我们用的spring,所以手动import进来
@Import(MybatisPlusAutoConfiguration.class)
public class StartConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
官网中其实也有说明,只是我误认为这玩意引入就自己配置好了。得多看官网!
首先我这边整理了一个流程图,整个分页的流程其实是比较绕的,我也不确定能否描述清楚,接下来就进入主题。
以下是DefualtResultSetHandler的断点信息,拿到的栈信息可以很快的了解调用流程。
handleResultSets:222, DefaultResultSetHandler (org.apache.ibatis.executor.resultset)
query:65, PreparedStatementHandler (org.apache.ibatis.executor.statement)
query:79, RoutingStatementHandler (org.apache.ibatis.executor.statement)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invoke:64, Plugin (org.apache.ibatis.plugin)
query:-1, $Proxy42 (com.sun.proxy)
doQuery:63, SimpleExecutor (org.apache.ibatis.executor)
queryFromDatabase:325, BaseExecutor (org.apache.ibatis.executor)
query:156, BaseExecutor (org.apache.ibatis.executor)
query:109, CachingExecutor (org.apache.ibatis.executor)
willDoQuery:136, PaginationInnerInterceptor (com.baomidou.mybatisplus.extension.plugins.inner)
intercept:75, MybatisPlusInterceptor (com.baomidou.mybatisplus.extension.plugins)
invoke:62, Plugin (org.apache.ibatis.plugin)
query:-1, $Proxy41 (com.sun.proxy)
selectList:151, DefaultSqlSession (org.apache.ibatis.session.defaults)
selectList:145, DefaultSqlSession (org.apache.ibatis.session.defaults)
selectList:140, DefaultSqlSession (org.apache.ibatis.session.defaults)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invoke:427, SqlSessionTemplate$SqlSessionInterceptor (org.mybatis.spring)
selectList:-1, $Proxy36 (com.sun.proxy)
selectList:224, SqlSessionTemplate (org.mybatis.spring)
executeForIPage:122, MybatisMapperMethod (com.baomidou.mybatisplus.core.override)
execute:85, MybatisMapperMethod (com.baomidou.mybatisplus.core.override)
invoke:156, MybatisMapperProxy$PlainMethodInvoker (com.baomidou.mybatisplus.core.override)
invoke:95, MybatisMapperProxy (com.baomidou.mybatisplus.core.override)
getAll:-1, $Proxy37 (com.sun.proxy)
getAll:43, UserServiceImpl (com.qhyu.cloud.service.impl)
pageTest:33, MybatisQhyuApplication
main:28, MybatisQhyuApplication
首先我们的mapper接口的请求会进入到MybatisMapperProxy的invoke方法。第三章我们说过,mapper会被创建为一个MybatisMapperProxy的代理对象。
method.getDeclaringClass()方法是用于获取定义某个方法的类的class对象,比如我们的hashcode方法和equals方法都是属于Object的,所以此处就是要让这些方法直接执行,因为我们的sql执行是需要走mybatis的逻辑。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
// getDeclaringClass 方法是用于获取定义某个方法的类的 Class 对象。
// 如果方法是在当前类中定义的,则返回当前类的 Class 对象;如果方法是在父类或接口中定义的,则返回相应的父类或接口的 Class 对象。
// 巧妙🤏
return method.invoke(this, args);
} else {
// cachedInvoker会组装PlainMethodInvoker或者DefaultMethodInvoker
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
接下来就到了cachedInvoker方法,该方法返回的是一个MapperMethodInvoker。
默认方法是公共非抽象实例方法,才会进入到if的逻辑里面,因为当前是支持接口里面定义默认方法的,所以这里特殊处理一下。
后续的方法就比较好理解了,返回了一个PlainMethodInvoker对象,构造函数是MybatisMapperMethod、Method和Configuration。
achedInvoker(method).invoke(proxy, method, args, sqlSession)执行之后就进入了PlainMethodInvoker的invoke方法,从而执行MybatisMapperMethod的execute方法。此处比较好理解。
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return CollectionUtils.computeIfAbsent(methodCache, method, m -> {
// 这个是因为接口可以有默认方法,所以做特殊处理。
// 所以正常的mapper接口都是会走PlainMethodInvoker。
if (m.isDefault()) {
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
return new PlainMethodInvoker(new MybatisMapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
execute方法就是根据sqlCommandType来判断走什么分支,当前我们的方法是分页的Select方法,所以会进入到case SELECT逻辑。
我们返回的是IPage类型的对象,最终会进入到executeForIPage(sqlSession, args)逻辑中。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
// TODO 这里下面改了
if (IPage.class.isAssignableFrom(method.getReturnType())) {
result = executeForIPage(sqlSession, args);
// TODO 这里上面改了
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
这个方法会拿到传递的IPage参数对象,给param属性,然后会把这个参数传递下去,需要注意的是result对象就是param对象。此处涉及到最后的Records的填充和total的信息。
我们看到的sqlSession.selectList方法其实就是最终返回的信息了,因为这个方法返回的内容就是我们需要的,有分页信息也有查询的信息。所以核心就是sqlSession.selectList方法。
private <E> Object executeForIPage(SqlSession sqlSession, Object[] args) {
IPage<E> result = null;
for (Object arg : args) {
if (arg instanceof IPage) {
result = (IPage<E>) arg;
break;
}
}
Assert.notNull(result, "can't found IPage for args!");
Object param = method.convertArgsToSqlCommandParam(args);
List<E> list = sqlSession.selectList(command.getName(), param);
result.setRecords(list);
return result;
}
执行这个方法时候使用的是sqlSessionProxy属性,这个sqlSessionProxy就是一个代理对象,这些内容第三章我们说过。
public <E> List<E> selectList(String statement, Object parameter) {
return this.sqlSessionProxy.selectList(statement, parameter);
}
此处的逻辑就是sqlSessionProxy调用selectList方法的时候进入SqlSessionInterceptor的invoke方法,然后生成sqlsession,用于DefaultSqlsession的selectList方法的调用。
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
注释:从Spring事务管理器获取一个SqlSession,如果需要的话,创建一个新的SqlSession。它尝试从当前事务中获取SqlSession,如果当前没有事务,则创建一个新的SqlSession。然后,如果Spring事务处于活动状态并且配置了SpringManagedTransactionFactory作为事务管理器,则将SqlSession与事务进行同步。
简单来说,这段注释描述了一个过程,该过程在使用Spring进行数据库事务管理时,从事务管理器中获取或创建一个SqlSession,并确保它与当前的Spring事务进行同步。
我们在MybatisAutoConfiguration 这章节就知道SqlSessionTemplate中的sqlSessionFactory为DefaultSqlSessionFactory对象,所以sessionFactory.openSession(executorType)的时候是进入DefaultSqlSessionFactory的openSession方法
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;
}
LOGGER.debug(() -> "Creating a new SqlSession");
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
这个方法就是为了OpenSession、创建事物等,返回的是DefaultSqlSession对象。其中executor涉及到了责任链模式。Mybatis源码分析专栏里面也有关于责任链模式的描述。
既然返回的是DefaultSqlSession对象,那么SqlSessionInterceptor的invoke方法中method.invoke(sqlSession, args)就是调用的DefaultSqlSession的SelectList方法,并把参数带过去。
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);
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();
}
}
这里传递的参数如下:
statement:com.qhyu.cloud.mapper.UserInfoMapper.getAll
prameter:Ipage对象,就是分页查询到的参数对象。
重点就是executor的调用了,因为这个selectList返回的很明显就是我们要查询的数据。
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
statement:com.qhyu.cloud.mapper.UserInfoMapper.getAll是为了获取MappedStatement对象信息。
又因为我们配置了MybatisPlusInterceptor,而且executor在构建责任链的时候又创建了Plugin的代理对象,所以executor会进入到Plugin的invoke方法中。
signatureMap属性值是在构建责任链的时候获取的,所以如果想知道signatureMap里面是什么需要查看wrap方法。
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);
}
}
创建代理对象的时候,用到了局部变量signatureMap,这个值就是从getSignatureMap方法中获取而来。
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) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
我们可以从MybatisPlusInterceptor这个类的头上看到Intercepts注解和里面的Signature信息,根据方法我们可以知道应该就是把Signature信息组装成map返回回去。本意就是获取注解内的Signature信息。
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
// issue #251
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
Signature[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
for (Signature sig : sigs) {
Set<Method> methods = MapUtil.computeIfAbsent(signatureMap, sig.type(), k -> new HashSet<>());
try {
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
} catch (NoSuchMethodException e) {
throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
}
}
return signatureMap;
}
MybatisPlusInterceptor的注解信息如下:
@Intercepts(
{
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
@Signature(type = StatementHandler.class, method = "getBoundSql", args = {}),
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@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 MybatisPlusInterceptor implements Interceptor {
接着来阅读getSignatureMap方法的源码。
前面的逻辑比较清晰,就是获取到Intercepts注解的信息,拿到Signature数组信息,创建一个Map,key为Class、Vaule为一个Method集合。
这里面有个lambda表达式,我们来分析一下。
// 分析这个表达式
Set<Method> methods = MapUtil.computeIfAbsent(signatureMap, sig.type(), k -> new HashSet<>());
public static <K, V> V computeIfAbsent(Map<K, V> map, K key, Function<K, V> mappingFunction) {
V value = map.get(key);
if (value != null) {
return value;
}
return map.computeIfAbsent(key, mappingFunction);
}
@Override
public V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
if (mappingFunction == null)
throw new NullPointerException();
int hash = hash(key);
Node<K,V>[] tab; Node<K,V> first; int n, i;
int binCount = 0;
TreeNode<K,V> t = null;
Node<K,V> old = null;
if (size > threshold || (tab = table) == null ||
(n = tab.length) == 0)
n = (tab = resize()).length;
if ((first = tab[i = (n - 1) & hash]) != null) {
if (first instanceof TreeNode)
old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
else {
Node<K,V> e = first; K k;
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
old = e;
break;
}
++binCount;
} while ((e = e.next) != null);
}
V oldValue;
if (old != null && (oldValue = old.value) != null) {
afterNodeAccess(old);
return oldValue;
}
}
V v = mappingFunction.apply(key);
if (v == null) {
return null;
} else if (old != null) {
old.value = v;
afterNodeAccess(old);
return v;
}
else if (t != null)
t.putTreeVal(this, tab, hash, key, v);
else {
tab[i] = newNode(hash, key, v, first);
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
}
++modCount;
++size;
afterNodeInsertion(true);
return v;
}
一开始我们创建了一个signatureMap,里面是没有东西的,所以就会执行map.computeIfAbsent(key, mappingFunction)方法,也就是代码中的最后面一个方法(HashMap类的)。
参数:key为Signature中的属性type,mappingFunction就是k -> new HashSet<>()。
这段表达式的意思就是:如果signatureMap中有这个type类型的key就直接返回,否则就创建一个hashSet。用于装载Signature中的method参数。
所以signatureMap就是用于获取自定义的一些Interceptor上的注解信息,用于判断是否需要走窃听方法。
这就到了MybatisPlusInterceptor的逻辑了。interceptors全局变量的值就是我们在最前面配置的PaginationInnerInterceptor。
开始会判断target是否是Executor类型,在构建责任链的时候target为CachingExecutor,其delegate为SimpleExecutor。所以此次会进入到if的逻辑,其中核心的就是进入Select逻辑执行interceptors的遍历,从而执行PaginationInnerInterceptor的willDoQuery、beforeQuery方法。
@Setter
private List<InnerInterceptor> interceptors = new ArrayList<>();
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget();
Object[] args = invocation.getArgs();
if (target instanceof Executor) {
final Executor executor = (Executor) target;
Object parameter = args[1];
boolean isUpdate = args.length == 2;
MappedStatement ms = (MappedStatement) args[0];
if (!isUpdate && ms.getSqlCommandType() == SqlCommandType.SELECT) {
RowBounds rowBounds = (RowBounds) args[2];
ResultHandler resultHandler = (ResultHandler) args[3];
BoundSql boundSql;
if (args.length == 4) {
boundSql = ms.getBoundSql(parameter);
} else {
// 几乎不可能走进这里面,除非使用Executor的代理对象调用query[args[6]]
boundSql = (BoundSql) args[5];
}
for (InnerInterceptor query : interceptors) {
if (!query.willDoQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql)) {
return Collections.emptyList();
}
query.beforeQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);
}
CacheKey cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
return executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
} else if (isUpdate) {
for (InnerInterceptor update : interceptors) {
if (!update.willDoUpdate(executor, ms, parameter)) {
return -1;
}
update.beforeUpdate(executor, ms, parameter);
}
}
} else {
// StatementHandler
final StatementHandler sh = (StatementHandler) target;
// 目前只有StatementHandler.getBoundSql方法args才为null
if (null == args) {
for (InnerInterceptor innerInterceptor : interceptors) {
innerInterceptor.beforeGetBoundSql(sh);
}
} else {
Connection connections = (Connection) args[0];
Integer transactionTimeout = (Integer) args[1];
for (InnerInterceptor innerInterceptor : interceptors) {
// 所以我这里感觉实现这个逻辑也可以打印sql信息
innerInterceptor.beforePrepare(sh, connections, transactionTimeout);
}
}
}
return invocation.proceed();
}
willDoQuery方法中会组装select count语句,核心的点就是executor执行query,此方法的executor是MybatisPlusInterceptor类中的intercept方法中从invocation.getTarget()中获取的,就是CachingExecutor的query方法,并将countSql和参数等都传递过去。
public boolean willDoQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
IPage<?> page = ParameterUtils.findPage(parameter).orElse(null);
if (page == null || page.getSize() < 0 || !page.searchCount()) {
return true;
}
BoundSql countSql;
MappedStatement countMs = buildCountMappedStatement(ms, page.countId());
if (countMs != null) {
countSql = countMs.getBoundSql(parameter);
} else {
countMs = buildAutoCountMappedStatement(ms);
// 在这里进行了优化,同时count sql在这里被组装好 也就是select count(*)
String countSqlStr = autoCountSql(page, boundSql.getSql());
PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
countSql = new BoundSql(countMs.getConfiguration(), countSqlStr, mpBoundSql.parameterMappings(), parameter);
PluginUtils.setAdditionalParameter(countSql, mpBoundSql.additionalParameters());
}
// 生成cacheKey 应该就是后续如果使用缓存的时候直接从cacheKey中获取,查询的时候会设置CacheKey
CacheKey cacheKey = executor.createCacheKey(countMs, parameter, rowBounds, countSql);
// 这里会到Executor的逻辑里面去。executor是一个属性值,不是一个代理对象。这里会执行一遍sql。
List<Object> result = executor.query(countMs, parameter, rowBounds, resultHandler, cacheKey, countSql);
long total = 0;
if (CollectionUtils.isNotEmpty(result)) {
// 个别数据库 count 没数据不会返回 0
Object o = result.get(0);
if (o != null) {
total = Long.parseLong(o.toString());
}
}
page.setTotal(total);
return continuePage(page);
}
不管Cache的事情,都是会执行delegate.query方法,我们知道delegate其实就是SimpleExecutor,要知道SimpleExecutor继承了BaseExecutor哈。所以执行的其实是BaseExecutor的query方法。
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
这个基础的执行器执行sql是在queryFromDatabase方法中。
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
核心逻辑又到了doQuery方法,其中的cache什么的我们暂且不管,后续缓存章节的时候再详细分析。
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
在这个方法中声明了一个处理器,StatementHandler,我们在Intercepts的type属性中见过。
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
创建了一个RoutingStatementHandler,并且还是为其创建了责任链。所以我们知道这个handler执行的时候还是会进入Plugin的invoke逻辑进行验证,通过才会执行intercept。
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
创建完handler之后继续执行prepareStatement方法,我们知道prepare方法是可以进入到interceptor逻辑的,从Intercepts注解中可以得知。
所以此时的prepare方法会进入到Plugin,并且通过验证进入intercept逻辑。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
只是此时的MybatisPlusInterceptor中会进入的else的逻辑,如下所示:
// 省略部分代码
else {
// StatementHandler
final StatementHandler sh = (StatementHandler) target;
// 目前只有StatementHandler.getBoundSql方法args才为null
if (null == args) {
for (InnerInterceptor innerInterceptor : interceptors) {
innerInterceptor.beforeGetBoundSql(sh);
}
} else {
Connection connections = (Connection) args[0];
Integer transactionTimeout = (Integer) args[1];
for (InnerInterceptor innerInterceptor : interceptors) {
// 所以我这里感觉实现这个逻辑也可以打印sql信息
innerInterceptor.beforePrepare(sh, connections, transactionTimeout);
}
}
}
return invocation.proceed();
args是不为空的,进入else逻辑,执行beforePrepare,是个空方法没有实现,所以invocation.proceed就进入了RoutingStatementHandler的prepare方法。
其属性delegate就是PreparedStatementHandler
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
return delegate.prepare(connection, transactionTimeout);
}
PreparedStatementHandler继承了BaseStatementHandler,实际执行就是BaseStatementHandler的prepare方法。
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
instantiateStatement实力化一个Statement。connection为ConnectionLogger
此处返回的Object,构建了一个PreparedStatementLogger。返回之前调用了ConnectionLogger的invoke方法,打印一些debug信息。
例如:10:29:40.874 [main] DEBUG com.qhyu.cloud.mapper.UserInfoMapper.getAll_mpCount - ==> Preparing: SELECT COUNT(*) AS total FROM skyworth_user
public Object invoke(Object proxy, Method method, Object[] params)
throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
if ("prepareStatement".equals(method.getName()) || "prepareCall".equals(method.getName())) {
if (isDebugEnabled()) {
debug(" Preparing: " + removeExtraWhitespace((String) params[0]), true);
}
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else if ("createStatement".equals(method.getName())) {
Statement stmt = (Statement) method.invoke(connection, params);
stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else {
return method.invoke(connection, params);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
执行完成之后返回到SimpleExecutor的prepareStatement,因为之前我们在此处进去了,所以执行完成之后回到这里。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
继续执行parameterize(stmt)方法,也同样会进入到Plugin的invoke方法进行验证,因为MybatisPlusInterceptor没有相关的method信息,所以此处不会进入Interceptor的intercept的逻辑,继而直接执行RoutingStatementHandler的parameterize方法。
再往外层返回就到了SimpleExecutor的doQuey方法了。此时返回获取的stmt是PreparedStatementLogger,一看就是为了调用前打印sql相关信息的。
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 到这里出来,继续执行后面的query逻辑
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
继续执行RoutingStatementHandler的query逻辑,实际执行的是delegate.query,也就是PreparedStatementHandler的Query方法。
传入的参数statement是PreparedStatementLogger,所以就是执行PreparedStatementLogger的invoke方法
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
打印sql的参数信息和返回信息的。
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
if (EXECUTE_METHODS.contains(method.getName())) {
if (isDebugEnabled()) {
debug("Parameters: " + getParameterValueString(), true);
}
clearColumnInfo();
if ("executeQuery".equals(method.getName())) {
ResultSet rs = (ResultSet) method.invoke(statement, params);
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
} else {
return method.invoke(statement, params);
}
} else if (SET_METHODS.contains(method.getName())) {
if ("setNull".equals(method.getName())) {
setColumn(params[0], null);
} else {
setColumn(params[0], params[1]);
}
return method.invoke(statement, params);
} else if ("getResultSet".equals(method.getName())) {
ResultSet rs = (ResultSet) method.invoke(statement, params);
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
} else if ("getUpdateCount".equals(method.getName())) {
int updateCount = (Integer) method.invoke(statement, params);
if (updateCount != -1) {
debug(" Updates: " + updateCount, false);
}
return updateCount;
} else {
return method.invoke(statement, params);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
默认的返回处理类,最终将数据进行返回。
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
将直接返回到MybatisPlusInterceptor的query.beforeQuery的方法。
很显然,后续还是会执行一遍整体流程,因为分页的时候先查询的总数,然后才去查询数据进行组装返回。
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget();
Object[] args = invocation.getArgs();
if (target instanceof Executor) {
final Executor executor = (Executor) target;
Object parameter = args[1];
boolean isUpdate = args.length == 2;
MappedStatement ms = (MappedStatement) args[0];
if (!isUpdate && ms.getSqlCommandType() == SqlCommandType.SELECT) {
RowBounds rowBounds = (RowBounds) args[2];
ResultHandler resultHandler = (ResultHandler) args[3];
BoundSql boundSql;
if (args.length == 4) {
boundSql = ms.getBoundSql(parameter);
} else {
// 几乎不可能走进这里面,除非使用Executor的代理对象调用query[args[6]]
boundSql = (BoundSql) args[5];
}
for (InnerInterceptor query : interceptors) {
if (!query.willDoQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql)) {
return Collections.emptyList();
}
query.beforeQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);
}
CacheKey cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
return executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
// 省略部分代码
因为分页的执行流程比较长,所以没有将整体流程全部都走一遍,因为后续的逻辑其实差不多,本章节的调用链路很深很深,有时候进去了容易出不来,所以在刚开始开的时候可以不像博主这样,了解一下内部是干嘛的就行,跳过看主体的方式是最快理解源码逻辑的。