【三】MyBatis-Spring最全源码详解之SQL执行流程

目录

1. JdkDynamicAopProxy#invoke

2. ReflectiveMethodInvocation.proceed()

3. invokeJoinpoint

3.1 SqlCommand

3.2 MethodSignature

4 MapperMethod#execute

4.1 代理方法执行

4.2 目标方法执行

4.2.1 准备查询事宜

4.2.2 动态SQL替换

4.3 prepareStatement

4.4 执行和返回

5. 附录:项目文档


前面两篇系列文章已经详细介绍了Mapper接口的扫描过程和对应bean的创建过程。本文重点介绍mybatis执行SQL的过程。


DaoService的selectByName方法,拿到的personMapper对象已经是上文介绍的那个superMapperProxy对象。执行personMapper.selectByName(personName)方法,入参personName是"Hodey",其实就是执行的superMapperProxy的InvocationHandler.invoke(Object proxy, Method method, Object[] args)方法。

public String selectByName(String personName){
    PersonInfo personInfo = personMapper.selectByName(personName);
    return personInfo.toString();
}

我们进入JdkDynamicAopProxy的invoke方法(代理类为什么是JdkDynamicAopProxy,请参见上一篇文章)

1. JdkDynamicAopProxy#invoke

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    MethodInvocation invocation;
    Object oldProxy = null;
    boolean setProxyContext = false;

    TargetSource targetSource = this.advised.targetSource;
    Object target = null;

     // 如果原始方法是equals(),hashcode(),则直接执行代理类的对应方法返回。
     // 如果原始方法所在类实现了DecoratingProxy.class接口,做相应的处理后返回类对象
     // 如果原始方法是Advised.class的实现类,那就直接返回对应的代理结果。
     // 以上4条并不重要!读不懂不影响后续的理解。
    try {
        if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
            return equals(args[0]);
        }
        else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
            return hashCode();
        }
        else if (method.getDeclaringClass() == DecoratingProxy.class) {
            return AopProxyUtils.ultimateTargetClass(this.advised);
        }
        else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
                method.getDeclaringClass().isAssignableFrom(Advised.class)) {
            return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
        }

        Object retVal;
        // 主流程不会进入该分支
        if (this.advised.exposeProxy) {
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }
        
        target = targetSource.getTarget();
        Class targetClass = (target != null ? target.getClass() : null);

        // 根据配置,为给定方法获取一个拦截表。
        // persistenceExceptionTranslationInterceptor
        List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

        if (chain.isEmpty()) {
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
        }
        else {
            // ReflectiveMethodInvocation是Spring AOP执行回调的载体。它会将代理对象,目标对象,目标方法,目标方法的入参,目标对象/方法所在类类型和拦截列表封装在一起。
            invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            // 重点在这里
            retVal = invocation.proceed();
        }
        
        Class returnType = method.getReturnType();
        if (retVal != null && retVal == target &&
                returnType != Object.class && returnType.isInstance(proxy) &&
                !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
            retVal = proxy;
        }
        return retVal;
    }
    finally {
        if (target != null && !targetSource.isStatic()) {
            targetSource.releaseTarget(target);
        }
        if (setProxyContext) {
            AopContext.setCurrentProxy(oldProxy);
        }
    }
} 
  

TargetSource targetSource就是Mapper的代理对象MapperProxy@5773。try语句块中的前4个if..else分支不重要,读不懂没关系。先往下走,this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass)根据配置,为给定方法method获取一个拦截表。这里获取到的chain中只有一个拦截器persistenceExceptionTranslationInterceptor,这个拦截器就是为代理targetSource而生的。接下来的invocation对象仅仅是将proxy, target, method, args, targetClass, chain这些参数用ReflectiveMethodInvocation类型封装起来。retVal = invocation.proceed()才是重点需要介绍的方法,它处理完成后会得到SQL查询的结果。最后通过method.getReturnType()拿到所需的查询对象类型,将得到的结果转换成该返回值类型后返回上层完成结果的输出。

2. ReflectiveMethodInvocation.proceed()

public Object proceed() throws Throwable {
    // 这里其实就是找到当前需要哪个拦截器去执行
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        // 重点在这里
        return invokeJoinpoint();
    }
    Object interceptorOrInterceptionAdvice =
            this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    // 因为persistenceExceptionTranslationInterceptor没有实现InterceptorAndDynamicMethodMatcher,故会走else分支
    // PersistenceExceptionTranslator是异常处理器用于处理DataAccessException 。如果后续流程中出现了异常,那么该拦截器可以进行相应的异常转换处理。    
    if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {        
        InterceptorAndDynamicMethodMatcher dm =
                (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
        Class targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
        if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
            return dm.interceptor.invoke(this);
        }
        else {
            return proceed();
        }
    }
    else {
        // PersistenceExceptionTranslationInterceptor.invoke处理完成后又回走进该方法,但是那个时候currentInterceptorIndex=0了。
        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
    }
}

process方法比较绕,处理过程也很“奇葩”。从整体上看作者是使用了责任链模式+观察者模式的实现。下面我们一起来分析这个奇葩的实现:

this.currentInterceptorIndex默认值是-1。假如interceptorsAndDynamicMethodMatchers.size=1。那就会先去执行this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex)即get(0)的那个dm.interceptor.invoke(this)。然后调用最后一行((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this)。实际执行的是PersistenceExceptionTranslationInterceptor的invoke方法。

// org.springframework.dao.support.PersistenceExceptionTranslationInterceptor#invoke
public Object invoke(MethodInvocation mi) throws Throwable {
    try {
        return mi.proceed();
    }
    // 这估计是我第一次在系列文章中对异常进行保留和注释
    // 只要后续过程中有RuntimeException ex发生,那么PersistenceExceptionTranslationInterceptor就能进行相应的处理
    catch (RuntimeException ex) {
        // Let it throw raw if the type of the exception is on the throws clause of the method.
        if (!this.alwaysTranslate && ReflectionUtils.declaresException(mi.getMethod(), ex.getClass())) {
            throw ex;
        } 
        else {
            PersistenceExceptionTranslator translator = this.persistenceExceptionTranslator;
            if (translator == null) {
                translator = detectPersistenceExceptionTranslators(this.beanFactory);
                this.persistenceExceptionTranslator = translator;
            }
            throw DataAccessUtils.translateIfNecessary(ex, translator);
        }
    }
}

该方法在正常情况下啥也不做,直接调用mi.proceed()方法回到上面的proceed方法中。因为proceed方法是第二次进入了,currentInterceptorIndex的值从-1变为了0。这样使得this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1成立,所以流程会进入invokeJoinpoint()方法。PersistenceExceptionTranslationInterceptor的目的仅仅是尝试catch下层返回的数据库操作异常,然后将其异常信息进行转化后再向上抛出。

3. invokeJoinpoint

protected Object invokeJoinpoint() throws Throwable {
    // target = MapperProxy对象
    // method = com.Hodey.analysemvc.dao.mapper.PersonMapper#selectByName
    // arguments = "Hodey"
    return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
}

public static Object invokeJoinpointUsingReflection(@Nullable Object target, Method method, Object[] args)
        throws Throwable {

    ReflectionUtils.makeAccessible(method);
    return method.invoke(target, args);
}

// 最终会执行到这个org.apache.ibatis.binding.MapperProxy#invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 重点在这里
    return mapperMethod.execute(sqlSession, args);
}

最终调用的invoke方法是Mapper代理对象MapperProxy的invoke方法。第一次调用selectByName方法时cachedMapperMethod方法会创建一个MapperMethod对象用于封装{mapperInterface(mapper接口类型), method(调用方法selectByName),mybatis的配置信息},并且额外创建了SqlCommand对象和MethodSignature对象。

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

3.1 SqlCommand

public SqlCommand(Configuration configuration, Class mapperInterface, Method method) {
    // selectByName
    final String methodName = method.getName();
    // interface PersonMapper
    final Class declaringClass = method.getDeclaringClass();
    // 获取在configuration.mappedStatements map中缓存的MappedStatement对象
    MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
    configuration);
    if (ms == null) {
        if (method.getAnnotation(Flush.class) != null) {
            name = null;
            type = SqlCommandType.FLUSH;
        } 
    } else {
    	//com.Hodey.analysemvc.dao.mapper.PersonMapper.selectByName
        name = ms.getId();
        // SELECT
        type = ms.getSqlCommandType();
    }
}

resolveMappedStatement方法是从代理对象的configuration.mappedStatements中获取MappedStatement对象ms。然后使用ms初始化SqlCommand成员属性name=com.Hodey.analysemvc.dao.mapper.PersonMapper.selectByName, type=SELECT。

3.2 MethodSignature

public MethodSignature(Configuration configuration, Class mapperInterface, Method method) {
    Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
    if (resolvedReturnType instanceof Class) {
        this.returnType = (Class) resolvedReturnType;
    } else if (resolvedReturnType instanceof ParameterizedType) {
        this.returnType = (Class) ((ParameterizedType) resolvedReturnType).getRawType();
    } else {
        this.returnType = method.getReturnType();
    }
    this.returnsVoid = void.class.equals(this.returnType);
    this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
    this.returnsCursor = Cursor.class.equals(this.returnType);
    this.mapKey = getMapKey(method);
    this.returnsMap = this.mapKey != null;
    this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
    this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
    // 参数解析器
    this.paramNameResolver = new ParamNameResolver(configuration, method);
}

MethodSignature对象主要是封装参数和返回类型的。比如返回值类型returnType设置为PersonInfo。paramNameResolver是一个参数解析器,它维护了一个SortedMap map,它用来存放入参。比如:

aMethod(@Param("M") int a, @Param("N") int b) -----> map{{0, "M"}, {1, "N"}}
aMethod(int a, int b) -----> {{0, "0"}, {1, "1"}}

4 MapperMethod#execute

4.1 代理方法执行

创建完成后将其缓存到MapperProxy对象的methodCache map中,key是方法名selectByName。然后调用mapperMethod.execute(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 {
          //来看最简单的执行一条语句
          Object param = method.convertArgsToSqlCommandParam(args);
          // statement:com.Hodey.analysemvc.dao.mapper.PersonMapper.selectByName
          // parameter:Hodey
          // sqlSession: sqlSessionTemplate
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }    
    return result;
 }

根据MethodSignature对象的属性类型,选择不同的执行策略执行。method.convertArgsToSqlCommandParam(args)会根据arges入参利用paramNameResolver.getNamedParams转换成Sql命令参数。然后执行result = sqlSession.selectOne(command.getName(), param)。sqlSession是sqlSessionTemplate,入参是:

statement:com.Hodey.analysemvc.dao.mapper.PersonMapper.selectByName
parameter:Hodey

sqlSession.selectOne方法内部实际调用的是代理对象的sqlSessionProxy的selectOne(statement, parameter)方法。sqlSessionProxy的SqlSessionInterceptor,所以我们进入SqlSessionTemplate.SqlSessionInterceptor#invoke方法

public  T selectOne(String statement, Object parameter) {
    return this.sqlSessionProxy. selectOne(statement, parameter);
}


//org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    SqlSession sqlSession = getSqlSession(
      SqlSessionTemplate.this.sqlSessionFactory,
      SqlSessionTemplate.this.executorType,
      SqlSessionTemplate.this.exceptionTranslator);
    try {
        // 执行org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne(java.lang.String, java.lang.Object)
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
            sqlSession.commit(true);
        }
        return result;
    } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
            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) {
          //如果开启了事务,那就减去sqlSessionHolder的引用计数。如果没有开启事务,那就直接关闭创建的DefaultSqlSesstion对象
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
    }
}

getSqlSession方法会获取或者创建一个SqlSession对象,默认会返回一个DefaultSqlSession对象。这个对象中封装了configuration,executor,dirty,autoCommit属性。其中executor分为3种:SimpleExecutor,ReuseExecutor和BatchExecutor。SimpleExecutor是默认的,每次执行完成后都会关闭Statement对象;ReuseExecutor与SimpleExecutor的区别是每次执行完成后都不会关闭Statement对象;BatchExecutor是批处理执行器,可以缓存多个Statement后一并发送处理拿回结果。可以在配置文件中添加mybatis.executor-type=reuse类切换mybatis的执行器。如果开启了缓存(默认开启),那么不管是哪种执行器,在封装到DefaultSqlSession之前都会被包装成CachingExecutor,使新创建的executor被缓存起来方便下次使用。

在getSqlSession方法中还创建了一个SqlSessionHolder holder对象。它和事务相关,用于在事务中获取defaultSqlSession对象。如果有事务存在,那么他会和defaultSqlSession绑定,事务的引用计数会+1。如果下次SQL执行还在这个事务中,则会通过holder对象获取到正在执行的事务所对应的defaultSqlSession,事务引用计数会再次+1。如果下一次SQL执行开启了新事务(或者没有事务),那么则会重新创建defaultSqlSession和对应的SqlSessionHolder holder。

4.2 目标方法执行

4.2.1 准备查询事宜

接下来就到了method.invoke(sqlSession, args),即原始目标方法sqlSession.selectOne(command.getName(), param)执行了。

public  T selectOne(String statement, Object parameter) {
    List list = this.selectList(statement, parameter);
    if (list.size() == 1) {
        return list.get(0);
    } else {
        return null;
    }
}

public  List selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {        
        MappedStatement ms = configuration.getMappedStatement(statement);
        // ms中啥都有, parameter是入参Hodey, rowBounds是limit的值,这里没有就是默认值{0, 2147483647},没有结果就返回null
        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();
    }
}

入参statement是com.Hodey.analysemvc.dao.mapper.PersonMapper.selectByName,在configuration中获取之前缓存好的MappedStatement对象。上一篇文章《【二】MyBatis-Spring最全源码详解之Mapper的自动注入》已经介绍了MappedStatement对象被缓存的时机,这里就不再赘述了。

接下来执行executor.query方法。

public  List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 缓存一下执行脚本和参数,这里也还没替换动态sql
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    // 拿到这些信息就可以执行了。跳过中途一些啰啰嗦嗦的流程,直接进入到干货所在地org.apache.ibatis.executor.BaseExecutor#queryFromDatabase
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

public BoundSql getBoundSql(Object parameterObject) {
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    // 要替换的参数表
    List parameterMappings = boundSql.getParameterMappings();
    // 如果没有placeholder或者入参列表没有值,那就不用替换或者说没法替换了。
    if (parameterMappings == null || parameterMappings.isEmpty()) {
        boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }
    // 这里检查嵌套的result maps,没有进行动态sql替换
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
      String rmId = pm.getResultMapId();
      if (rmId != null) {
        ResultMap rm = configuration.getResultMap(rmId);
        if (rm != null) {
          hasNestedResultMaps |= rm.hasNestedResultMaps();
        }
      }
    }
    return boundSql;
}

ms.getBoundSql方法会新建一个BoundSql对象。它内部存放了动态SQL的基本信息。比如:

sql = "select * from person where name=?;"
parameterMappings = 参数信息,比如参数名personName,参数类型String等等
parameterObject = "Hodey"
additionalParameters = 空对象

随后createCacheKey方法缓存执行脚本和参数。然后执行另外一个重载的query方法,我们跳过中途一些啰啰嗦嗦的流程,最终执行到

public  List 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 list;
    try {
      queryStack++;
      // 尝试拿到缓存的结果
      list = resultHandler == null ? (List) 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;
 }

首先尝试看看之前是否有执行过相同的SQL语句,如果有则不用去数据库查询了。否则需要执行queryFromDatabase方法去真正的盘上查数据。

4.2.2 动态SQL替换

private  List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List 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;
}

queryFromDatabase方法进来就将cacheKey作为key,一个占位对象EXECUTION_PLACEHOLDER作为值加入了localCache中缓存起来,即所谓的mybatis一级缓存。再查询完成后,再将结果替换掉EXECUTION_PLACEHOLDER。执行查询的方法是在list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql)中,走进去瞧瞧。

public  List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
    try {
        Configuration configuration = ms.getConfiguration();
        // 在里面创建StatementHandler。
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        // 完成动态sql的placeholder的参数替换
        stmt = prepareStatement(handler, ms.getStatementLog());
        return handler.query(stmt, resultHandler);
    } finally {
        closeStatement(stmt);
    }
}

doQuery方法会新创建一个StatementHandler handler对象。我们知道JDBC的Statement是用来执行SQL语句的载体。StatementHandler顾名思义是mybatis用来和JDBC打交道的。

public interface StatementHandler {
  Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;
  void parameterize(Statement statement) throws SQLException;
  void batch(Statement statement) throws SQLException;
  int update(Statement statement) throws SQLException;
   List query(Statement statement, ResultHandler resultHandler) throws SQLException;
   Cursor queryCursor(Statement statement) throws SQLException;
  BoundSql getBoundSql();
  ParameterHandler getParameterHandler();
}

【三】MyBatis-Spring最全源码详解之SQL执行流程_第1张图片

StatementHandler有三种:SimpleStatementHandler(没有预编译sql语句功能),PreparedStatementHandler(有预编译sql语句功能),CallableStatementHandler(存储过程相关)。默认是创建PreparedStatementHandler。然后执行prepareStatement方法。

4.3 prepareStatement

prepareStatement主要有2个重要功能:1.预编译SQL,提高重复或相似SQL语句的执行效率。2.规避SQL注入风险。

// SimpleExecutor的prepareStatement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // 获取数据库连接
    Connection connection = getConnection(statementLog);
    // 处理出数据库需要的Statement
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    // 返回是动态SQL的占位符已经被替换了,变成了最终可执行的SQL语句。
    return stmt;
}

最终在handler.parameterize方法中完成了动态SQL的替换。

public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // 拿出需要替换的参数:place holder是personName, 类型是String
    List parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          // personName
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) { //parameterObject = "Hodey"
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          	// value = "Hodey"
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          // typeHandler类型是String
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
          	// 这行代码将动态SQL替换为了最终需要执行的语句:select * from person where name='Hodey';
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (Exception e) {
            throw new XXX;
          }
        }
      }
    }
}

执行完prepareStatement方法,将动态SQL的参数替换后回到doQuery方法,就调用handler.query(stmt, resultHandler);真正开始要去数据库中查询数据了。

4.4 执行和返回

public  List query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 下面交给数据库进行查询
    ps.execute();
    return resultSetHandler. handleResultSets(ps);
}

ps.execute()方法会就调用数据库的方法实现SQL语句。结果会封装在ps对象中返回。getFirstResultSet方法是从stmt中获取原始的返回值。然后封装到ResultSetWrapper对象中。该对象中封装了数据库的返回结果,和对应在数据库中的属性。比如:columeNames依次存放的值是:id,name,age。 分别对应查询结果在person表中的列名。classNames依次存放的是:java.lang.Integer, java.lang.String, java.lang.Integer。 分别对应查询结果类型。jdbcTypes依次存放的是:INTEGER, VARCHAR, INTEGER。 分别对应查询结果在person表中的字段类型。之后新建一个DefaultResultHandler来对结果进行整合,将结果按照用户自定义的类型返回。如果结果只有1个,就返回单个对象,否则以List的数据结构返回。数据库结构如下

【三】MyBatis-Spring最全源码详解之SQL执行流程_第2张图片

执行完成返回结果的过程中,当返回到doQuery方法时会关闭prepareStatement对象和持有的jdbc连接。返回到SqlSessionInterceptor#invoke方法后,会执行事务的提交sqlSession.commit(true)。最后会在finally方法中执行closeSqlSession方法,会执行session.close()方法,将sql执行过程中创建的DefaultSqlSession对象关掉。这也是就是直接导致所谓的mybatis一级缓存失效的本质原因!为什么mybatis-spring会主动关闭这个session对象呢?

我的理解是:如果这里mybatis-spring放过了关闭session的时机。那么在返回结果给到用户的应用层后,还需要给用户商量:“兄弟,你看我都把结果给您了,您行行好帮我把session关了呗?”其实这个要求用户主动关闭session的约定是很难奏效,或者说是不符合用户编程习惯的。首先很多用户不知道mybatis-spring是否帮我关闭了session,其次如果在查询过程中有异常,那么到底是在创建DefaultSqlSession之前就发生了异常还是之后才发生的呢?如果发生在DefaultSqlSession创建之前就产生了异常,那么关闭一个本就不存在的session的后果又会是什么?如果异常在DefaultSqlSession对象创建之后发生,那么异常情况下没有回滚之前的状态,那就是一个错误的编程范例!所以mybatis-spring的作者直接帮用户关闭了session。奥卡姆的刮胡刀了解一下?

最终在一切正常的情况下,查询的数据就返回到用户的应用层程序了。到此SQL执行的流程分析结束。虽然本文以最简单的select语句进行查询,但是整个MyBatis-Spring所经历的流程却是大同小异的。整个流程同样适用于update,insert和delect等流程,这几个就留给读者自行分析了。


到此MyBatis-Spring的源码分析系列文章就写完了。感谢您的阅读,希望对您有所收获。

【一】MyBatis-Spring最全源码详解之@MapperScan到底在弄啥

https://blog.csdn.net/wuyuwei/article/details/88539725

【二】MyBatis-Spring最全源码详解之Mapper的自动注入

https://blog.csdn.net/wuyuwei/article/details/88547585
【三】MyBatis-Spring最全源码详解之SQL执行流程 本文

5. 附录:项目文档

【三】MyBatis-Spring最全源码详解之SQL执行流程_第3张图片

@MapperScan("com.Hodey.analysemvc.dao")
@SpringBootApplication
public class AnalyseMvcApplication {
 
	public static void main(String[] args) {
		SpringApplication.run(AnalyseMvcApplication.class, args);
	}
}
@Service
public class DaoService {
 
    @Autowired
    private PersonMapper personMapper;
 
    public String selectByName(String personName){
        PersonInfo personInfo = personMapper.selectByName(personName);
        return personInfo.toString();
    }
 
    public List selectAll(){
        List personInfoList = personMapper.selectAll();
        return personInfoList;
    }
}
@Repository
public interface PersonMapper {
 
    @Results(id = "personMap", value = {
            @Result(property = "id", column = "id"),
            @Result(property = "personName", column = "name"),
            @Result(property = "age", column = "age")
    })
 
    @Select("select * from person where name=#{personName}; ")
    public PersonInfo selectByName(String personName);
 
    @Select("select * from person;")
    @ResultMap(value = "personMap")
    public List selectAll();
}
public class PersonInfo {
 
    private int id;
 
    private String personName;
 
    private int age;
 
    public int getId() {
        return id;
    }
 
    public void setId(int id) {
        this.id = id;
    }
 
    public String getPersonName() {
        return personName;
    }
 
    public void setPersonName(String personName) {
        this.personName = personName;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
 
    @Override
    public String toString() {
        return "PersonInfo{" +
                "id=" + id +
                ", personName='" + personName + '\'' +
                ", age=" + age +
                '}';
    }
}
@RestController
@RequestMapping("/dao")
public class DaoContorller {
 
    @Autowired
    private DaoService daoService;
 
    @RequestMapping(value = "/getPersonName", method = RequestMethod.GET)
    public String getPersonName(@Param("personName") String personName){
        return daoService.selectByName(personName);
    }
 
    @RequestMapping(value = "/getAll", method = RequestMethod.GET)
    public List getAll(){
        return daoService.selectAll();
    }
}

application.properties

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/dev?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=******
spring.datasource.password=******

数据库:

执行结果:

PersonInfo{id=1, personName='Hodey', age=17}

 

 

 

 

 

你可能感兴趣的:(【三】MyBatis-Spring最全源码详解之SQL执行流程)