mybatis源码(四)

通过解析mybatis的配置文件,获得了SqlSessionFactory对象。有了工厂对象,就可以创建SqlSession了。通过Sqlsession,可以对mapper接口进行代理,最终由代理对象完成拼接sql、进行参数赋值、执行sql、结果集映射等操作。在这个过程中,mybatis四大对象也会参与其中,完成各自的职责。mybatis还提供了一级、二级缓存的功能,来提高执行效率;提供了插件机制,供开发人员扩展。

//加载并解析配置文件
SqlSessionFactory sqlSessionFactory =
        new SqlSessionFactoryBuilder().build(inputStream);
        
//通过工厂创建SqlSession
//DefaultSqlSessionFactory#openSession()
SqlSession sqlSession = sqlSessionFactory.openSession();

复制代码

1 SqlSession对象的创建流程

DefaultSqlSessionFactory#openSession()

public class DefaultSqlSessionFactory implements SqlSessionFactory {
    private final Configuration configuration;

 
    public SqlSession openSession() {
      return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
    }
   
    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);
        //每个SqlSession分配一个Executor
        final Executor executor = configuration.newExecutor(tx, execType);
        //使用Configuration和Executor创建SqlSession
        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();
      }
    }
}
复制代码

在SqlSession的创建流程中,我们看到mybatis四大对象之一的Executor

mybatis的四大对象:

  • ParameterHandler:处理SQL的参数对象
  • ResultSetHandler:处理SQL的返回结果集
  • StatementHandler:数据库的处理对象,用于执行SQL语句
  • Executor:MyBatis的执行器,用于执行增删改查操作

1.1 Executor的创建流程

Configuration#newExecutor()

public class Configuration {

    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分配一级缓存,一级缓存的作用域是SqlSession
        executor = new SimpleExecutor(this, transaction);
      }
      if (cacheEnabled) {//全局二级缓存是否开启
        /**
         * 全局二级缓存默认开启 但还需要每个mapper手动开启二级缓存
         */
        executor = new CachingExecutor(executor);
      }

      //代理
      executor = (Executor) interceptorChain.pluginAll(executor);
      return executor;
    }

}
复制代码

SimpleExecutor

public class SimpleExecutor extends BaseExecutor {

  public SimpleExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }
}
复制代码
public abstract class BaseExecutor implements Executor {
     
    //一级缓存
    protected PerpetualCache localCache;

    protected BaseExecutor(Configuration configuration, Transaction transaction) {
      this.transaction = transaction;
      this.deferredLoads = new ConcurrentLinkedQueue<>();
      //每个Executor分配一级缓存,一级缓存的作用域是SqlSession
      this.localCache = new PerpetualCache("LocalCache");
      this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
      this.closed = false;
      this.configuration = configuration;
      this.wrapper = this;
    }

}
复制代码

InterceptorChain#pluginAll

public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<>();
    
  //层层包装
  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }
}
复制代码

2 mapper接口的代理

有了SqlSession对象,就可以创建mapper接口的代理了

DefaultSqlSession#getMapper

public class DefaultSqlSession implements SqlSession {

  private final Configuration configuration;
  
  
    public <T> T getMapper(Class<T> type) {
      return configuration.<T>getMapper(type, this);
    }
}
复制代码

Configuration#getMapper

public class Configuration {

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

}
复制代码

MapperRegistry#getMapper

public class MapperRegistry {

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

  public MapperRegistry(Configuration config) {
    this.config = config;
  }

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    /**
     * 加载配置文件的把接口放到knownMappers中
     */
    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);
    }
  }
}
复制代码
public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
  
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

  protected T newInstance(MapperProxy<T> mapperProxy) {
    /**
     * 使用jdk动态代理生成代理对象
     * 关注 mapperProxy.invoke()方法
     *
     *  MapperProxy<T> implements InvocationHandler
     */
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
}
复制代码

mapper接口的代理对象就生成了,注意Proxy.newProxyInstance方法的InvocationHandler类型的参数-MapperProxy,目标方法执行的时候,会进入到MapperProxy#invoke中。

3 MapperProxy.invoke

执行流程:
1、如果是Object类的方法,直接执行目标方法
2、如果是接口的默认方法,会把默认方法绑定到代理对象 再调用
3、开发人员写的执行sql的方法,先从缓存中拿mybatis的方法装饰对象,如果没有就使用目标方法对象创建
4、创建MapperMethod对象,构造器里有初始化操作
 4.1 创建SqlCommond对象,该对象包含一些和 SQL 相关的信息
 4.2 创建 MethodSignature 对象,从类名中可知,该对象包含了被拦截方法的一些信息

5、执行MapperMethod#execute



复制代码

MapperProxy

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    /**
     * 代理以后,所有Mapper的方法调用时,都会调用这个invoke方法  (但并不是所有方法都要被代理,所以需要排除一些方法)
     */
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        /**
         * 调用的是否是Object默认的方法 并且没有重写
         *
         */
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        /**
         *  是否是接口的默认方法
         *  如果是默认方法 会把默认方法绑定到代理对象 再调用
         */
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    /**
     * 先从缓存中拿,缓存中没有就创建 创建MapperMethod时会有一系列初始化动作
     */
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    /**
     * 我们写的一般会走这里
     */
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  }

  private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
      throws Throwable {
    final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
        .getDeclaredConstructor(Class.class, int.class);
    if (!constructor.isAccessible()) {
      constructor.setAccessible(true);
    }
    final Class<?> declaringClass = method.getDeclaringClass();
    /**
     * 类似于反射调用方法操作类 更轻量级一些 构造方法句柄
     *
     * 如果是默认方法 会把默认方法绑定到代理对象 再调用
     */
    return constructor
        .newInstance(declaringClass,
            MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
                | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
        .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
  }

  //是否是默认方法
  private boolean isDefaultMethod(Method method) {
    return (method.getModifiers()
        & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
        && method.getDeclaringClass().isInterface();
  }
}
复制代码

3.1 MapperMethod的创建流程

public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    /**
     *  创建 SqlCommand 对象,该对象包含一些和 SQL 相关的信息
     */
    this.command = new SqlCommand(config, mapperInterface, method);
    /**
     *  创建 MethodSignature 对象,从类名中可知,该对象包含了被拦截方法的一些信息
     */
    this.method = new MethodSignature(config, mapperInterface, method);
  }
}
复制代码

3.2 SqlCommand的创建流程

public static class SqlCommand {

  private final String name;
  private final SqlCommandType type;

  public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
    final String methodName = method.getName();
    final Class<?> declaringClass = method.getDeclaringClass();
    /**
     * 拿到启动时创建好的MappedStatement
     */
    MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
        configuration);
    /**
     * 检测当前方法是否有对应的MappedStatement
     */
    if (ms == null) {
      //检测当前方法是否有 @Flush 注解
      if(method.getAnnotation(Flush.class) != null){
        // 设置 name 和 type 变量
        name = null;
        type = SqlCommandType.FLUSH;
      } else {
        /*
         * 若 ms == null 且方法无 @Flush 注解,此时抛出异常。
         * 这个异常比较常见,大家应该眼熟吧
         */
        throw new BindingException("Invalid bound statement (not found): "
            + mapperInterface.getName() + "." + methodName);
      }
    } else {
      // 设置 name 和 type 变量
      name = ms.getId();
      type = ms.getSqlCommandType();
      if (type == SqlCommandType.UNKNOWN) {
        throw new BindingException("Unknown execution method for: " + name);
      }
    }
  }
}
复制代码

3.3 MethodSignature的创建流程

public static class MethodSignature {
  private final ParamNameResolver paramNameResolver;

  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();
    }
    // 检测返回值类型是否是 void、集合或数组、Cursor、Map 等
    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.returnsOptional = Optional.class.equals(this.returnType);
    // 解析 @MapKey 注解,获取注解内容
    this.mapKey = getMapKey(method);
    this.returnsMap = this.mapKey != null;
    /*
     * 获取 RowBounds 参数在参数列表中的位置,如果参数列表中
     * 包含多个 RowBounds 参数,此方法会抛出异常
     */
    this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
    // 获取 ResultHandler 参数在参数列表中的位置
    this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
    /**
     * 解析参数列表  关注
     */
    this.paramNameResolver = new ParamNameResolver(configuration, method);
  }
}
复制代码

3.4 ParamNameResolver的创建流程

ParamNameResolver:方法参数转为sql参数

public class ParamNameResolver {

  private static final String GENERIC_NAME_PREFIX = "param";
  
 
    public ParamNameResolver(Configuration config, Method method) {
      /**
       * 获取参数类型列表
       */
      final Class<?>[] paramTypes = method.getParameterTypes();
      /**
       * 获取参数注解
       */
      final Annotation[][] paramAnnotations = method.getParameterAnnotations();
      final SortedMap<Integer, String> map = new TreeMap<>();
      int paramCount = paramAnnotations.length;
      // get names from @Param annotations
      for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
        // 检测当前的参数类型是否为 RowBounds 或 ResultHandler
        if (isSpecialParameter(paramTypes[paramIndex])) {
          // skip special parameters
          continue;
        }
        String name = null;
        for (Annotation annotation : paramAnnotations[paramIndex]) {
          if (annotation instanceof Param) {
            hasParamAnnotation = true;
            name = ((Param) annotation).value();
            break;
          }
        }
        // name 为空,表明未给参数配置 @Param 注解
        if (name == null) {
          // @Param was not specified.
          // 检测是否设置了 useActualParamName 全局配置
          if (config.isUseActualParamName()) {
            /*
             * 通过反射获取参数名称。此种方式要求 JDK 版本为 1.8+,
             * 且要求编译时加入 -parameters 参数,否则获取到的参数名
             * 仍然是 arg1, arg2, ..., argN
             */
            name = getActualParamName(method, paramIndex);
          }
          if (name == null) {
            /*
             * 使用 map.size() 返回值作为名称,思考一下为什么不这样写:
             *   name = String.valueOf(paramIndex);
             * 因为如果参数列表中包含 RowBounds 或 ResultHandler,这两个参数
             * 会被忽略掉,这样将导致名称不连续。
             *
             * 比如参数列表 (int p1, int p2, RowBounds rb, int p3)
             *  - 期望得到名称列表为 ["0", "1", "2"]
             *  - 实际得到名称列表为 ["0", "1", "3"]
             */
            // use the parameter index as the name ("0", "1", ...)
            // gcode issue #71
            name = String.valueOf(map.size());
          }
        }
        // 存储 paramIndex 到 name 的映射
        map.put(paramIndex, name);
      }
      names = Collections.unmodifiableSortedMap(map);
    }

}
复制代码

3.5 MapperMethod#execute

public class MapperMethod {

    //包含一些和 SQL 相关的信息
    private final SqlCommand command;
    //包含了被拦截方法的一些信息
    private final MethodSignature method;

    /**
     * 执行sql
     * @param sqlSession
     * @param args
     * @return
     */
    public Object execute(SqlSession sqlSession, Object[] args) {
      Object result;

      /**
       *
       *  MapperMethod的构造方法中会创建SqlCommond 和 MethodSignature对象
       *
       * 可以看到执行时就是4种情况,insert|update|delete|select,分别调用SqlSession的4大类方法
       */
      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:
          /**
           * 如果方法返回值为 void,但参数列表中包含 ResultHandler,表明使用者
           * 想通过 ResultHandler 的方式获取查询结果,而非通过返回值获取结果
           */
          if (method.returnsVoid() && method.hasResultHandler()) {
            executeWithResultHandler(sqlSession, args);
            result = null;

          } else if (method.returnsMany()) {
            /**
             *  如果返回多条记录
             */
            result = executeForMany(sqlSession, args);
          } else if (method.returnsMap()) {
            /**
             *  执行查询操作,并将结果封装在 Map 中返回
             */
            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());
      }
      // 如果方法的返回值为基本类型,而返回值却为 null,此种情况下应抛出异常
      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;
    }
}
复制代码

这里我们分析executeForMany这个分支,其他分支类似

4 执行查询

executeForMany

public class MapperMethod {

    //包含一些和 SQL 相关的信息
    private final SqlCommand command;
    //包含了被拦截方法的一些信息
    private final MethodSignature method;
  
    private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
      List<E> result;
      //方法参数转换为sql参数
      Object param = method.convertArgsToSqlCommandParam(args);
      //mybatis提供的逻辑分页,该分页操作是对ResultSet结果集进行分页,也就是人们常说的逻辑分页,而非物理分页。
      /**
       * 物理分页:直接从数据库中拿出我们需要的数据,例如在Mysql中使用limit。
       * 逻辑分页:从数据库中拿出所有符合要求的数据,然后再从这些数据中拿到我们需要的分页数据。
       *
       * 物理分页每次都要访问数据库,逻辑分页只访问一次。
       * 物理分页占用内存少,逻辑分页相对较多。
       * 物理分页数据每次都是最新的,逻辑分页有可能滞后。
       */
      if (method.hasRowBounds()) {
        RowBounds rowBounds = method.extractRowBounds(args);
        result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
      } else {
        //走这里
        //DefaultSqlSession
        result = sqlSession.<E>selectList(command.getName(), param);
      }
      // issue #510 Collections & arrays support
      if (!method.getReturnType().isAssignableFrom(result.getClass())) {
        if (method.getReturnType().isArray()) {
          return convertToArray(result);
        } else {
          return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
        }
      }
      return result;
    }
    
   
    public Object convertArgsToSqlCommandParam(Object[] args) {
      //参数名解析器
      return paramNameResolver.getNamedParams(args);
    }

}
复制代码

executeForMany首先将方法参数转换成了sql参数,看一下转换流程

4.1 方法参数转为sql参数

mybatis传参:

  • 传入单个参数

mapper层

User getUserInfo(Integer userId);
复制代码

mapper.xml

select userId
from users
where userId=#{userId};

  • 传入多个参数  userId,sex  使用索引对应值

mapper层

User getUserInfo(Integer userId,String sex);
复制代码

mapper.xml

select userId
from users
where userId=#{0} and sex=#{1};

  • 传入多个参数  userId,sex 使用注解@Param 

mapper层

User getUserInfo(@Param("userId")Integer userId,@Param("sex")String sex);
复制代码

mapper.xml

<select id="getUserInfo" resultType="com.demo.elegant.pojo.User">
    select userId
    from users
    where userId=#{userId} and sex=#{sex};
</select>

复制代码
  • 传入多个参数   使用实体类传入

mapper层

User getUserInfo(User User);
复制代码

mapper.xml

<select id="getUserInfo"  parameterType="User"  resultType="com.demo.elegant.pojo.User">
    select userId
    from users
    where userId=#{userId} and sex=#{sex};
</select>
复制代码
  • 传入多个参数, 使用Map类传入

mapper层

User getUserInfo(Map map);
复制代码

mapper.xml

<select id="getUserInfo"  parameterType="Map"  resultType="com.demo.elegant.pojo.User">
    select userId
    from users
    where userId=#{userId} and sex=#{sex};
</select>
复制代码

ParamNameResolver.getNamedParams:结合创建ParamNameResolver对象时有一系列初始化动作。

public class ParamNameResolver {

    

    public Object getNamedParams(Object[] args) {
      /**
       * 创建当前类ParamNameResolver的对象时,会去遍历方法上的参数 判断形参上是否有@Param注解 有的话加入到names中
       * args;方法实参
       * names:方法形参上的名字(包括使用@Param注解中的value)
       */
      final int paramCount = names.size();
      if (args == null || paramCount == 0) {
        //没有参数
        return null;
      } else if (!hasParamAnnotation && paramCount == 1) {
        //只有一个参数 就算参数是对象或map也会进到此分支
        /**
         * 如果方法参数列表无 @Param 注解,且仅有一个非特别参数,则返回该参数的值。
         * 比如如下方法:
         *     List findList(RowBounds rb, String username)
         * names 如下:
         *     names = {1 : "username"}
         * 此种情况下,返回 args[names.firstKey()],即 args[1] -> username
         */
        return args[names.firstKey()];
      } else {
        //否则 返回一个ParamMap,修改参数名 参数名就是其位置
        final Map<String, Object> param = new ParamMap<>();
        int i = 0;
        for (Map.Entry<Integer, String> entry : names.entrySet()) {
          /**
           * 1、先加一个#{0}、#{1}、#{2}参数
           */
          param.put(entry.getValue(), args[entry.getKey()]);
          // add generic param names (param1, param2, ...)
          final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
          // ensure not to overwrite parameter named with @Param、
          //保证使用@Param标注的参数名不会被覆盖
          /**
           * 检测 names 中是否包含 genericParamName,什么情况下会包含?答案如下:
           *
           *   使用者显式将参数名称配置为 param1,即 @Param("param1")
           */
          if (!names.containsValue(genericParamName)) {
            /**
             *  2、再加一个#{param1}、#{param2}、#{param3}参数
             *
             *  //默认情况下它们将会以它们在参数列表中的位置来命名,比如:#{param1},#{param2}等。
             *  //如果你想改变参数的名称(只在多参数情况下) ,那么你可以在参数上使用@Param(“paramName”)注解。
             */
            param.put(genericParamName, args[entry.getKey()]);
          }
          i++;
        }
        return param;
      }
    }


}
复制代码

DefaultSqlSession#selectList

public class DefaultSqlSession implements SqlSession {

    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) {
      try {
        /**
         *  MappedStatement包含与当前sql包含的所有信息
         */
        MappedStatement ms = configuration.getMappedStatement(statement);
        /**
         * 默认情况下,executor 的类型为 CachingExecutor,SqlSessionFactory.openSession():创建Sqlsesison会分配一个Executor
         * 该类是一个装饰器类,用于给目标 Executor 增加二级缓存功能。
         * 那目标 Executor 是谁呢?默认情况下是 SimpleExecutor
         */
        //CachingExecutor 
        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();
      }
    }
}
复制代码

创建SqlSession对象时,先创建了一个SimpleExecutor,然后创建了一个CachingExecutor包装SimpleExecutor,故这里调用的是CachingExecutor#query()。

4.2 ${}拼接操作

CachingExecutor#query()

CachingExecutor的delegate属性就是创建SqlSession时传入CachingExecutor构造器入参的SimpleExecutor,装饰者模式。

public class CachingExecutor implements Executor {
    
    //delegate就是SimpleExecutor,装饰者,欧式
    private final Executor delegate;
 
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
      /**
       *  getBoundSql():如果是${} 参数在这里映射,直接替换成参数值   #{}在mybatis初始化就已经替换成? 了
       */
      BoundSql boundSql = ms.getBoundSql(parameterObject);
      /**
       * 创建cacheKey   一级缓存和二级缓存用的同一个key
       */
      CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
      //调用重载的方法 接4.3目录
      return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
    

    public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
      //BaseExecutor 
      return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
    }

}
复制代码

MappedStatement#getBoundSql

获取最终的sql,完成${}的拼接操作

public final class MappedStatement {

    public BoundSql getBoundSql(Object parameterObject) {
      /**
       *  调用 sqlSource 的 getBoundSql 获取 BoundSql
       *  SqlSource常用实现类:
       *  DynamicSqlSource
       *  RawSqlSource
       *  StaticSqlSource
       *
       * 当 SQL 配置中包含 ${}(不是 #{})占位符,或者包含 <if>、<where> 等标签时,会被认为是动态 SQL,
       * 此时使用 DynamicSqlSource 存储 SQL 片段。否则,使用 RawSqlSource 存储 SQL 配置信息
       *
       */

      // 只要有${} ==> DynamicSqlSource
      // 只要#{}没有${} ==> RowSqlSource
      /**
       * ${]在DynamicSqlSource.getBoundSql()中映射
       */
      BoundSql boundSql = sqlSource.getBoundSql(parameterObject);

      /**
       *  SELECT * FROM author WHERE name = #{name} AND age = #{age}
       *  这个 SQL 语句中包含两个 #{} 占位符,在解析mapper时这两个占位符会被解析成两个 ParameterMapping 对象
       *  ParameterMapping{property='name', mode=IN, javaType=class java.lang.String, jdbcType=null, ...}
       *  ParameterMapping{property='age', mode=IN, javaType=class java.lang.Integer, jdbcType=null, ...}
       *
       */
      List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
      if (parameterMappings == null || parameterMappings.isEmpty()) {
        /**
         * 创建新的 BoundSql,这里的 parameterMap 是 ParameterMap 类型。
         * 由<ParameterMap> 节点进行配置,该节点已经废弃,不推荐使用。默认情况下,
         * parameterMap.getParameterMappings() 返回空集合
         */
        boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
      }

      // check for nested result maps in parameter mappings (issue #30)
      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;
    }

}
复制代码

BaseExecutor#createCacheKey

创建缓存的key,这个key由一级缓存和二级缓存共用

       /**
       * update():计算hashcode
       * 判断是否的同一条sql的依据:
       * 1、sql id :namespace + id
       * 2、如果使用mybatis分页功能 起始位置和查询的条数相同
       * 3、sql
       * 4、传的参数
       */
复制代码

BaseExecutor

public abstract class BaseExecutor implements Executor {

    public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
      if (closed) {
        throw new ExecutorException("Executor was closed.");
      }
      CacheKey cacheKey = new CacheKey();
      /**
       * update():计算hashcode
       * 判断是否的同一条sql的依据:
       * 1、sql id :namespace + id
       * 2、如果使用mybatis分页功能 起始位置和查询的条数相同
       * 3、sql
       * 4、传的参数
       */
      cacheKey.update(ms.getId());
      cacheKey.update(rowBounds.getOffset());
      cacheKey.update(rowBounds.getLimit());
      /**
       *  boundSql.getSql():此时的sql ${}已被替换成具体参数值 #{}还是?
       */
      cacheKey.update(boundSql.getSql());
      List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
      TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
      // mimic DefaultParameterHandler logic
      for (ParameterMapping parameterMapping : parameterMappings) {
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) {
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          cacheKey.update(value);
        }
      }
      if (configuration.getEnvironment() != null) {
        // issue #176
        cacheKey.update(configuration.getEnvironment().getId());
      }
      return cacheKey;
    }
}
复制代码

4.3 二级缓存

CachingExecutor#query()

1、当前namespace是否开启二级缓存。如果没开启,调用BaseExecutor.query方法(SimpleExecutor继承了BaseExecutor,自然也有了该query方法)
2、如果开启了二级缓存
  2.1 判断是否需要刷新缓存
  2.2 根据cacheKey尝试从二级缓存中获取数据
  2.3 命中缓存则返回缓存中的结果
  2.4 缓存中没有的话则调用BaseExecutor.query查数据库
复制代码
public class CachingExecutor implements Executor {
 
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
        throws SQLException {

      /**
       *  MappedStatement是全局共享的 保存在Configuration中
       */
      /**
       * 二级缓存
       */
      Cache cache = ms.getCache();
      /**
       * 若映射文件中未配置缓存或参照缓存,此时 cache = null
       */
      if (cache != null) {
        //当前namespace开启了二级缓存

        /**
         * 是否刷新缓存 默认增删改刷新 查询不刷新
         */
        flushCacheIfRequired(ms);
        if (ms.isUseCache() && resultHandler == null) {
          ensureNoOutParams(ms, boundSql);
          /**
           * tcm:缓存事物管理器
           */
          List<E> list = (List<E>) tcm.getObject(cache, key);
          if (list == null) {
            /**
             * // 若缓存未命中,则调用被装饰类的 query 方法
             */
            //BaseExecutor
            list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
            //
            tcm.putObject(cache, key, list); // issue #578 and #116
          }
          return list;
        }
      }
      //namespace未开启二级缓存 走这里
      //BaseExecutor.query()
      return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
}
复制代码

4.4 一级缓存

BaseExecutor#query()

先尝试从一级缓存中获取,缓存命中则返回缓存中的数据;否则,查询数据库

public abstract class BaseExecutor implements Executor {

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

复制代码

查询数据库将在下文中进行介绍,涉及主要到除了Executor之外的其他三大对象、 PreparedStatment 为 ? 赋值,执行sql、处理数据库返回的结果集等。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享