mybatis源碼分析(三):mybaits是如何執(zhí)行一條sql語句的

mybatis代理對(duì)象的創(chuàng)建過程

在上一遍 mybatis源碼分析(二):mybatis在執(zhí)行SQL語句之前都做了什么 中我們通過源碼分析看到mybatis是如何構(gòu)建SqlSessionFactory和SqlSession的。其中我們看到SqlSession在構(gòu)建的過程中構(gòu)建了一個(gè)Executor,在Executor里包含了大量的數(shù)據(jù)庫操作,于是猜測(cè)Executor就是mybatis執(zhí)行SQL語句的核心組件。

這篇文章我們將繼續(xù)深入mybatis源碼來研究一下mybatis是如何執(zhí)行一條sql語句的,同時(shí)mybatis又是如何處理輸入?yún)?shù)和輸出結(jié)果的。

首先我們寫一段測(cè)試代碼:

執(zhí)行sql語句的mapper接口:


public interface DistrictMapper {

    District getById(Integer id);
}

mapper對(duì)應(yīng)的sql xml文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="me.binf.mybatistudy.dao.DistrictMapper">

    <select id="getById" resultType="me.binf.mybatistudy.entity.District">
        select * from district where id = #{id}
    </select>

</mapper>

測(cè)試用例:

    @Test
    public void testMyBatis() throws IOException {
        //1、得到 SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        //2、得到 sqlSession ,代表和數(shù)據(jù)庫一次回話
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //3、得到真正操作數(shù)據(jù)庫的Mapper
        DistrictMapper mapper = sqlSession.getMapper(DistrictMapper.class);
        //4.執(zhí)行方法方法并得到結(jié)果
        District district = mapper.getById(1);
        System.out.println(district);
        //5.關(guān)閉sqlSession
        sqlSession.close();
    }

上面三段代碼我們上一篇文章已經(jīng)分析過了,今天我們主要是Debug這兩段代碼:

    //3、得到真正操作數(shù)據(jù)庫的Mapper
    DistrictMapper mapper = sqlSession.getMapper(DistrictMapper.class);
    //4.執(zhí)行方法方法并得到結(jié)果
    District district = mapper.getById(1);

要知道DistrictMapper是一個(gè)接口,接口是沒辦法執(zhí)行的。

DistrictMapper mapper = sqlSession.getMapper(DistrictMapper.class);

那么這段代碼一定是mybatis為我們生成了一個(gè)代理對(duì)象,我們進(jìn)去看一下。

public class DefaultSqlSession implements SqlSession {
  @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }
  ......
}

public class Configuration {

  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
  ......
}

public class MapperRegistry {

  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

 @SuppressWarnings("unchecked")
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
......
}

我把mybatis生成代理對(duì)象核心的三個(gè)方法摘錄下來了,可以看到mybatis并沒有直接生成代理對(duì)象,而是使用MapperProxyFactory來生成DistrictMapper的代理對(duì)象。而MapperProxyFactory是mybatis在構(gòu)建SqlSessionFactory的時(shí)候就已經(jīng)創(chuàng)建好,并且放入到MapperRegistry這個(gè)類的knownMappers屬性Map中的。

那么我們可以再來回溯一下MapperProxyFactory的創(chuàng)建過程:

public class XMLMapperBuilder extends BaseBuilder {

  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      //用xml的namespace構(gòu)建mapper
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

  private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        // ignore, bound type is not required
      }
      if (boundType != null && !configuration.hasMapper(boundType)) {
        // Spring may not know the real resource name so we set a flag
        // to prevent loading again this resource from the mapper interface
        // look at MapperAnnotationBuilder#loadXmlResource
        configuration.addLoadedResource("namespace:" + namespace);
        configuration.addMapper(boundType);
      }
    }
  }

 public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }
......
}
public class MapperRegistry {

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
......
}

可以看到mybatis創(chuàng)建MapperProxyFactory過程是:

  1. 加載mybatis配置文件對(duì)應(yīng)的所有mapper.xml路徑
  2. 解析mapper.xml里所有的namespace標(biāo)簽,根據(jù)標(biāo)簽生成對(duì)應(yīng)接口的Class對(duì)象
  3. 把Class對(duì)象作為key,創(chuàng)建MapperProxyFactory對(duì)象作為value,然后放進(jìn)knownMappers里面

同時(shí)我們還可以看到mybatis在初始化階段不光解析了mapper.xml里namespace標(biāo)簽,還解析了ResultMaps、CacheRefs、Statements具體有什么左右我們后面再研究。可以看到mybatis這么設(shè)計(jì)的好處是可以在項(xiàng)目啟動(dòng)階段就可以幫你檢查出xml和接口對(duì)應(yīng)關(guān)系是否正確,同時(shí)提前把MapperProxyFactory構(gòu)建好緩存起來還可以加快后面程序運(yùn)行的效率。

mybatis是如何為執(zhí)行SQL語句做準(zhǔn)備的

MapperProxyFactory對(duì)象已經(jīng)有了,那么接下來就是mybatis如何執(zhí)行SQL語句了。

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

 ......
}

動(dòng)態(tài)代理newProxyInstance核心參數(shù)是InvocationHandler,而MapperProxy就是mybatis為InvocationHandler提供的實(shí)現(xiàn)類。我們主要去看MapperProxy類的invoke()方法,這個(gè)方法就是mybatis執(zhí)行sql語句的入口。

public class MapperProxy<T> implements InvocationHandler, Serializable {
 @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);
    }
  }

  private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      return MapUtil.computeIfAbsent(methodCache, 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;
    }
  }

  private static class PlainMethodInvoker implements MapperMethodInvoker {
    private final MapperMethod mapperMethod;

    public PlainMethodInvoker(MapperMethod mapperMethod) {
      super();
      this.mapperMethod = mapperMethod;
    }

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

......
}

可以看到mybatis緩存很多,調(diào)用invoke方法的時(shí)候mybatis會(huì)把目標(biāo)方法對(duì)象給緩存起來,每一個(gè)目標(biāo)方法對(duì)應(yīng)一個(gè)私有靜態(tài)類PlainMethodInvoker再去執(zhí)行。

public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

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

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

PlainMethodInvoker再去調(diào)用MapperMethodexecute方法去執(zhí)行SQL語句,mybatis的MapperMethod方法相當(dāng)于整個(gè)框架的分揀車間的作用,分揀過程大概做了這幾件事:
1.MapperMethod實(shí)例化方法會(huì)把目標(biāo)方法的所有信息包裝成一個(gè)SqlCommand對(duì)象
2.根據(jù)目標(biāo)方法執(zhí)行的SQL類型做判斷,通過不同類型去執(zhí)行對(duì)應(yīng)的SQL邏輯
3.根據(jù)查詢方法的返回值類型去調(diào)用不同查詢方法從而可以把查詢結(jié)果包裝成目標(biāo)方法定義的返回類型
4.根據(jù)入?yún)⒌念愋秃蛡€(gè)數(shù)把入?yún)b成框架方便解析的包裝類型

這里可以看到mybatis的一個(gè)核心組件:ParamNameResolver參數(shù)解析器。

public class ParamNameResolver {
  public static final String GENERIC_NAME_PREFIX = "param";

  public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
    if (args == null || paramCount == 0) {
      return null;
    } else if (!hasParamAnnotation && paramCount == 1) {
      Object value = args[names.firstKey()];
      return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
    } else {
      final Map<String, Object> param = new ParamMap<>();
      int i = 0;
      for (Map.Entry<Integer, String> entry : names.entrySet()) {
        param.put(entry.getValue(), args[entry.getKey()]);
        // add generic param names (param1, param2, ...)
        final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
        // ensure not to overwrite parameter named with @Param
        if (!names.containsValue(genericParamName)) {
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      return param;
    }
  }


public static Object wrapToMapIfCollection(Object object, String actualParamName) {
    if (object instanceof Collection) {
      ParamMap<Object> map = new ParamMap<>();
      map.put("collection", object);
      if (object instanceof List) {
        map.put("list", object);
      }
      Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
      return map;
    } else if (object != null && object.getClass().isArray()) {
      ParamMap<Object> map = new ParamMap<>();
      map.put("array", object);
      Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
      return map;
    }
    return object;
  }
}

解析參數(shù)的邏輯并不復(fù)雜,mybatis會(huì)判斷你定義的目標(biāo)方法參數(shù)的個(gè)數(shù)和類型:

1.如果只有一個(gè)參數(shù),并且參數(shù)不是集合類型或者是數(shù)組類型就直接返回參數(shù)值

2.如果只有一個(gè)參數(shù),但是參數(shù)的類型是集合類型或者數(shù)組類型,就會(huì)把參數(shù)放到map里面用collection(如果參數(shù)是List類型還會(huì)再多放一個(gè)list名字的key和value)或者array作為key,值作為value

3.如果有多個(gè)參數(shù),就會(huì)把參數(shù)名和參數(shù)值分別作為key和value放到map里面再返回,值得注意的是mybatis還會(huì)在map里再給你放一個(gè)別名的key,也就是param+參數(shù)位置如過參數(shù)是兩個(gè)就是param1,param2分別對(duì)應(yīng)第一個(gè)和第二個(gè)參數(shù)

看到這里我們就可以得出一個(gè)結(jié)論,在mybatis的mapper.xml里面動(dòng)態(tài)取值,不光可以用#{參數(shù)名}這種方式還可以用#{param1}這種方式。

MapperMethod類在分揀完目標(biāo)方法后就正式調(diào)用sqlSession來執(zhí)行SQL了,我們看一下sqlSession是如何執(zhí)行的。這里執(zhí)行的是selectOne()方法。從源碼中可以看到mybatis做查詢的時(shí)候除了selectCursor方法,其余的查詢最終都會(huì)用selectList方法去執(zhí)行:

public class DefaultSqlSession implements SqlSession {

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

mybatis執(zhí)行SQL語句的過程

可以看到executor正是我們上一篇分析出來的mybatis執(zhí)行SQL語句的核心組件。也就是說mybatis前面都在準(zhǔn)備食材,現(xiàn)在才正式下鍋炒菜。

那么mybatis是如何炒菜的呢?我們繼續(xù)看一下:

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

Executor是一個(gè)接口,首先要搞清楚誰是實(shí)現(xiàn)類,我們上一篇分析的是如果沒有指定executorType,那么默認(rèn)的Executor就是SimpleExecutor。

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

其實(shí)不是,因?yàn)槟J(rèn)cacheEnabled是true,也就是說executor是CachingExecutor,而CachingExecutor再把SimpleExecutor包裝起來。也就是說mybatis在這個(gè)地方使用了一個(gè)簡單的裝飾器模式,使SimpleExecutor增強(qiáng)了一個(gè)緩存功能。

我們?cè)賮砜匆幌?strong>CachingExecutor是如何使用緩存的:

public class CachingExecutor implements Executor {

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

  public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
  }
  @Override
  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);
  }

  @Override
  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);
  }
......
}

CachingExecutor在這里操作的cache是二級(jí)緩存二級(jí)緩存策略我們放到后面再研究,這里我們知道二級(jí)緩存是存在MappedStatement這個(gè)組件里的,大概的邏輯就是查詢結(jié)果存在緩存里就直接返回,不存在再去調(diào)用SimpleExecutor去執(zhí)行SQL語句。

我們繼續(xù)往下看SimpleExecutor的執(zhí)行邏輯:

public abstract class BaseExecutor implements Executor {

  protected PerpetualCache localCache;
  protected PerpetualCache localOutputParameterCache;
  protected Configuration configuration;

@Override
  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;
  }

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

SimpleExecutor繼承了BaseExecutorquery()方法, BaseExecutor首先做的事情是從一級(jí)緩存localCache中去取值,取不到的話再去調(diào)用子類的doQuery方法去執(zhí)行SQL,拿到緩存結(jié)果后再放到localCache里面。localCache緩存使用是有一定條件的,這個(gè)我們也放到后面再講。

繼續(xù)來看doQuery方法:

public class SimpleExecutor extends BaseExecutor {
  @Override
  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);
    }
  }

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;
  }
......
}
public class Configuration {

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

}

doQuery()方法會(huì)先拿到一個(gè)默認(rèn)RoutingStatementHandler,再去連接數(shù)據(jù)庫。拿到數(shù)據(jù)庫連接后就是StatementHandler正式出場(chǎng)了。

先來看一下RoutingStatementHandler的構(gòu)造方法。

public class RoutingStatementHandler implements StatementHandler {

  private final StatementHandler delegate;

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }
  }
}

可以看到RoutingStatementHandler實(shí)際上還不是真正去調(diào)用JDBC執(zhí)行SQL語句的組件,mybatis在這里用了一個(gè)簡單的策略模式,真正去做事的StatementHandler是通過ms.getStatementType()來判斷出來的。

執(zhí)行SQL語句的StatementHandler有三個(gè)策略類:
1.SimpleStatementHandler 執(zhí)行簡單SQL語句的策略類
2.PreparedStatementHandler 執(zhí)行預(yù)編譯SQL語句的策略類
3.CallableStatementHandler 執(zhí)行存儲(chǔ)過程SQL語句的策略類

我們的demo使用的是預(yù)編譯的SQL語句,我們來看一下PreparedStatementHandler

public class PreparedStatementHandler extends BaseStatementHandler {
  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.handleResultSets(ps);
  }
......
}

到這里mybatis終于沒有什么騷操作了,直接使用JDBC的PreparedStatement執(zhí)行SQL語句,然后把數(shù)據(jù)庫的返回值交給ResultHandler來處理了。

ResultHandler顧名思義就是來處理返回值的,由于這篇篇幅太長了,mybatis是如何來處理返回值的我們放到下一篇來講??傊甿ybatis終于執(zhí)行SQL了,我們接下來總結(jié)一下整個(gè)過程。

  1. 從Configuration中獲得MapperProxyFactory
  2. MapperProxyFactory構(gòu)建定義接口的動(dòng)態(tài)代理對(duì)象
  3. 再就是動(dòng)態(tài)代理對(duì)象執(zhí)行查詢方法步驟:
mybatis執(zhí)行過程.jpg

本文涉及的源碼放在github上:
https://github.com/burgleaf/mybatis-study

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容