Mybatis随笔

Mybatis随笔(六) MapperProxy & Execu

2020-03-24  本文已影响0人  sunyelw

上文已经知道了Mybatis 通过JDK动态代理获取到包含SQL方法的实体接口的代理对象 MapperProxy,接下来继续看下SQL方法如何执行。


1、MapperProxy#invoke流程

@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 {
            return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
}

最典型的两个Object方法

我们的SQL方法明显属于非 Object 方法

private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
        return methodCache.computeIfAbsent(method, m -> {
            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 MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
            }
        });
    } catch (RuntimeException re) {
        Throwable cause = re.getCause();
        throw cause == null ? re : cause;
    }
}

这里使用了一个缓存 methodCache

这个缓存是一个 Map<Method, MapperMethodInvoker> 类型,是一个方法到方法调用处理类的映射,保证同一个接口的同一个方法第二次调用时可以直接从缓存中获取到调用对象,减少调用时长。

现在接口可以使用 default 关键字来拥有实现方法,而我们的SQL执行方法是没有实现的,所以走的是 PlainMethodInvoker 类的 invoke 方法
注意这里传入了一个 MapperMethod 作为 PlainMethodInvoker 的构造入参

return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));

public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
}

入参三个

通过上面三个参数构造了MapperMethod的两个属性

public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
    return mapperMethod.execute(sqlSession, args);
}

调用的是 MapperMethod 的 execute 方法,继续往下跟

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 {
      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;
}

我们发现 MapperMethod就是一个类,上无老下无小,而这个 execute 方法就是对 SqlSession 的一个封装(增删改查),我们随便找一个方法跟进去看看

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);
}

sqlSession # selectOne 跟到最后执行

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();
    }
}

发现最后还是调用 executor 来执行具体的处理逻辑.

executor 什么时候初始化的?怎么初始化的?

回过头来找下 executor 的构建,发现只在 DefaultSqlSession 构造函数中进行赋值,而构造函数只有两处调用均在 DefaultSqlSessionFactory 中

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
        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();
    }
}

具体看下 configuration.newExecutor(tx, execType) 的实现

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) interceptorChain.pluginAll(executor);
    return executor;
}

这里前两行我就有一个疑问:
执行完第一行的 executorType,无论如何都不会是个 null,那么第二行的三目表达式肯定是取其自身,第二行的意义何在?

然后根据 executorType 来创建对应类型的 Executor

public enum ExecutorType {
    SIMPLE,REUSE,BATCH
}

SIMPLE 该类型的执行器没有特别的行为。它为每个语句的执行创建一个新的预处理语句。
REUSE 该类型的执行器会复用预处理语句。
BATCH 该类型的执行器会批量执行所有更新语句,如果 SELECT 在多个更新中间执行,将在必要时将多条更新语句分隔开来,以方便理解。

这里面还藏着一个设计模式 - 装饰器模式

if (cacheEnabled) {
  executor = new CachingExecutor(executor);
}

看下构造函数和随便一个查询

private final Executor delegate;
private final TransactionalCacheManager tcm = new TransactionalCacheManager();

public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
}

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

CachingExecutor 持有了一个 Executor,从上面的逻辑来看,这个 Executor 是 BatchExecutor 、ReuseExecutor、SimpleExecutor 之间的一个,然后在实际调用时先走一遍缓存再去执行持有的 Executor 中的方法,就像是在外部套了一层“衣服”,是为装饰器模式。

CachingExecutor 就是 Mybatis 大名鼎鼎的二级缓存


Executor 的具体使用,下一篇等你。

上一篇 下一篇

猜你喜欢

热点阅读