java源码-mybatis源码解读(3.4.3)

mybatis源码解读(3.4.3)

  • Last Published: 09 March 2020
  • Version: 3.5.4

简约步骤截图及理解

SqlSessionFactory获取

在这里插入图片描述

官网是这么说的 : 一旦被创建,SqlSessionFactory 应该在你的应用执行期间都存在。没有理由来处理或重 新创建它。

使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次。 这样的 操作将被视为是

非常糟糕的。 因此 SqlSessionFactory 的最佳范围是应用范围。 有很多方法可 以做到, 最简单的

就是使用单例模式或者静态单例模式。

SqlSessionFactory与SqlSession的获取:
在这里插入图片描述

SqlSession获取

在这里插入图片描述

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不能被共享,也是线程 不安全的。

因此最佳的范围是请求或方法范围。绝对不能将 SqlSession 实例的引用放在一个 类的静态字段甚至

是实例字段中。 也绝不能将 SqlSession 实例的引用放在任何类型的管理范 围中, 比如 Serlvet

架构中的 HttpSession。 如果你现在正用任意的 Web 框架, 要考虑 SqlSession 放在一个和 HTTP

请求对象相似的范围内。换句话说,基于收到的 HTTP 请求,你可以打开 了一个 SqlSession,然后返回

响应,就可以关闭它了。关闭 Session 很重要,你应该确保使 用 finally 块来关闭它。下面的示例就

是一个确保 SqlSession 关闭的基本模式:

SqlSession session = sqlSessionFactory.openSession();

try {

// do work

} finally {

session.close();

}

在你的代码中一贯地使用这种模式, 将会保证所有数据库资源都正确地关闭 (假设你没 有通过你自己的

连接关闭,这会给 MyBatis 造成一种迹象表明你要自己管理连接资源) 。
在这里插入图片描述

动态Dao的获取

在这里插入图片描述

sql的执行

我们知道Mapper,通过MapperProxy代理类执行他的接口方法,当mapper方法被调用的时候对应的MapperProxy会生成相应的MapperMethod并且会缓存起来,这样当多次调用同一个mapper方法时候只会生成一个MapperMethod,提高了时间和内存效率;
在这里插入图片描述
MapperProxy:

//这里会拦截Mapper接口的所有方法 
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) { //如果是Object中定义的方法,直接执行。如toString(),hashCode()等
      try {
        return method.invoke(this, args);//
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);  //其他Mapper接口定义的方法交由mapperMethod来执行
    return mapperMethod.execute(sqlSession, args);
  }

最后2句关键,我们执行所调用Mapper的每一个接口方法,最后返回的是MapperMethod.execute方法。每一个MapperMethod对应了一个mapper文件中配置的一个sql语句或FLUSH配置,对应的sql语句通过mapper对应的class文件名+方法名从Configuration对象中获得。

紧接着:

/**
 * MapperMethod代理Mapper所有方法
 */
public class MapperMethod {
  //一个内部封 封装了SQL标签的类型 insert update delete select
  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, method);
  }

  /**
   * 这个方法是对SqlSession的包装,对应insert、delete、update、select四种操作
   */
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;//返回结果
   //INSERT操作
    if (SqlCommandType.INSERT == command.getType()) {
      //处理参数
      Object param = method.convertArgsToSqlCommandParam(args);
      //调用sqlSession的insert方法 
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      //UPDATE操作 同上
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      //DELETE操作 同上
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      //如果返回void 并且参数有resultHandler  ,则调用 void select(String statement, Object parameter, ResultHandler handler);方法  
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        //如果返回多行结果,executeForMany这个方法调用 <E> List<E> selectList(String statement, Object parameter);   
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        //如果返回类型是MAP 则调用executeForMap方法 
        result = executeForMap(sqlSession, args);
      } else {
        //否则就是查询单个对象
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else {
        //接口方法没有和sql命令绑定
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    //如果返回值为空 并且方法返回值类型是基础类型 并且不是VOID 则抛出异常  
    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;
  }

  private Object rowCountResult(int rowCount) {
    final Object result;
    if (method.returnsVoid()) {
      result = null;
    } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
      result = rowCount;
    } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
      result = (long) rowCount;
    } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
      result = (rowCount > 0);
    } else {
      throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
    }
    return result;
  }

  private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
    MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
    if (void.class.equals(ms.getResultMaps().get(0).getType())) {
      throw new BindingException("method " + command.getName() 
          + " needs either a @ResultMap annotation, a @ResultType annotation," 
          + " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
    }
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
    } else {
      sqlSession.select(command.getName(), param, method.extractResultHandler(args));
    }
  }

  //返回多行结果 调用sqlSession.selectList方法 
  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      //如果参数含有rowBounds则调用分页的查询 
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
      //没有分页则调用普通查询 
      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;
  }

  private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
    Object collection = config.getObjectFactory().create(method.getReturnType());
    MetaObject metaObject = config.newMetaObject(collection);
    metaObject.addAll(list);
    return collection;
  }

  @SuppressWarnings("unchecked")
  private <E> E[] convertToArray(List<E> list) {
    E[] array = (E[]) Array.newInstance(method.getReturnType().getComponentType(), list.size());
    array = list.toArray(array);
    return array;
  }

  private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
    Map<K, V> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds);
    } else {
      result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey());
    }
    return result;
  }

  public static class ParamMap<V> extends HashMap<String, V> {

    private static final long serialVersionUID = -2212268410512043556L;

    @Override
    public V get(Object key) {
      if (!super.containsKey(key)) {
        throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet());
      }
      return super.get(key);
    }

  }

  //封装了具体执行的动作
  public static class SqlCommand {
    //xml标签的id 
    private final String name;
    //insert update delete select的具体类型 
    private final SqlCommandType type;

    public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) throws BindingException {
      //拿到全名 比如 org.mybatis.example.UserMapper.selectByPrimaryKey
      String statementName = mapperInterface.getName() + "." + method.getName();
      //MappedStatement对象,封装一个Mapper接口对应的sql操作 
      MappedStatement ms = null; 
      if (configuration.hasStatement(statementName)) {
        //从Configuration对象查找是否有这个方法的全限定名称,如果有则根据方法的全限定名称获取MappedStatement
        ms = configuration.getMappedStatement(statementName);
      } else if (!mapperInterface.equals(method.getDeclaringClass().getName())) { // issue #35
        //如果没有在Configuration对象中找到这个方法,则向上父类中获取全限定方法名 
        String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
        if (configuration.hasStatement(parentStatementName)) {
          ms = configuration.getMappedStatement(parentStatementName);
        }
      }
      if (ms == null) {
        throw new BindingException("Invalid bound statement (not found): " + statementName);
      }
      //这个ms.getId,其实就是我们在mapper.xml配置文件中配置一条sql语句设置的id属性的值
      name = ms.getId();
      //sql的类型(insert、update、delete、select)
      type = ms.getSqlCommandType();
      if (type == SqlCommandType.UNKNOWN) {
        //判断SQL标签类型 未知就抛异常
        throw new BindingException("Unknown execution method for: " + name);
      }
    }

    public String getName() {
      return name;
    }

    public SqlCommandType getType() {
      return type;
    }
  }

  /**
   * 方法签名,封装了接口当中方法的 参数类型 返回值类型 等信息
   */
  public static class MethodSignature {

    private final boolean returnsMany;//是否返回多条结果 
    private final boolean returnsMap; //返回值是否是MAP 
    private final boolean returnsVoid;//返回值是否是VOID 
    private final Class<?> returnType; //返回值类型 
    private final String mapKey;
    private final Integer resultHandlerIndex;//resultHandler类型参数的位置 
    private final Integer rowBoundsIndex; //rowBound类型参数的位置
    private final SortedMap<Integer, String> params;//用来存放参数信息
    private final boolean hasNamedParameters;  //是否存在命名参数 

    public MethodSignature(Configuration configuration, Method method) throws BindingException {
      this.returnType = method.getReturnType();
      this.returnsVoid = void.class.equals(this.returnType);
      this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
      this.mapKey = getMapKey(method);
      this.returnsMap = (this.mapKey != null);
      this.hasNamedParameters = hasNamedParams(method);
      this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
      this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
      this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));
    }

    /**
     * 创建SqlSession对象需要传递的参数逻辑
     * args是用户mapper所传递的方法参数列表, 如果方法没有参数,则返回null.  
     * 如果方法只包含一个参数并且不包含命名参数, 则返回传递的参数值。
     * 如果包含多个参数或包含命名参数,则返回包含名字和对应值的map对象、
     */
    public Object convertArgsToSqlCommandParam(Object[] args) {
      final int paramCount = params.size();
      if (args == null || paramCount == 0) {
        return null;
      } else if (!hasNamedParameters && paramCount == 1) {
        return args[params.keySet().iterator().next()];
      } else {
        final Map<String, Object> param = new ParamMap<Object>();
        int i = 0;
        for (Map.Entry<Integer, String> entry : params.entrySet()) {
          param.put(entry.getValue(), args[entry.getKey()]);
          // issue #71, add param names as param1, param2...but ensure backward compatibility
          final String genericParamName = "param" + String.valueOf(i + 1);
          if (!param.containsKey(genericParamName)) {
            param.put(genericParamName, args[entry.getKey()]);
          }
          i++;
        }
        return param;
      }
    }
 ------

在这里插入图片描述

MapperRegistry

MapperRegistry的功能就是注册和获取Mapper对象的代理。注册Mapper和获取Mapper都是在一个Map对象中存取Mapper的代理对象MapperProxyFactory。

SqlSession:

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

添加和获取Mapper实例都使用到了同一个类MapperRegistry,在Configuration中的声明如下:

protected MapperRegistry mapperRegistry = new MapperRegistry(this);



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

如何看出用的是同一个?可以debug看,也可以从SqlSessionFactory看起:

DefaultSqlSessionFactory:

SqlSessionFactory是创建SqlSession的工厂,但是创建过程中需要反复加载全局配置文件,这一点是十分耗时的,为了优化项目,最好通过单例模式来管理它,使它只能创建一个对象,配置文件加载一次就可以了。

 private final Configuration configuration;
...
...
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
        //这里返回的SqlSesion是 DefaultSqlSessionFactory的私有对象,而整个应用最好DefaultSqlSessionFactory是单例的
      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类里面,MapperRegistry是只创建一次的,所以可见,全局的MapperRegistry是公用一份的;

public class MapperRegistry {

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

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

  @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);
    }
  }
  
  public <T> boolean hasMapper(Class<T> type) {
    return knownMappers.containsKey(type);
  }

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

  /**
   * @since 3.2.2
   */
  public Collection<Class<?>> getMappers() {
    return Collections.unmodifiableCollection(knownMappers.keySet());
  }

  /**
   * @since 3.2.2
   */
  public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }

  /**
   * @since 3.2.2
   */
  public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
  }
}

MapperProxy MapperProxyFactory

现来看DefaultSqlSession的getMapper方法,由上面的分析我们可以知道,最后是通过MapperRegistry对象获得Mapper实例:

@SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
      //试图从MapperProxy得出 Mapper的代理工厂
    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);
    }
  }

来看看,knownMappers是什么东西:

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

是一个key 为 字节码 ,即对应参数 例如DeptDao.class的二进制对象,value 则为MapperProxyFactory

所以,接下来得要看这个getmapper 对应的addmapper方法:

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<T>(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在解析完xml后,绑定命名空间bindMapperForNamespace()方法(类: bindMapperForNamespace)

其实从以上就可以看出,一个xxxDao对应一个MapperProxyFactory;

//绑定到命名空间
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) {
      if (!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);
          //调用的归根结底还是MapperRegisty 的addMapper方法
        configuration.addMapper(boundType);
      }
    }
  }
}

我们在回过头来看getMapper是如何获得Mapper对象的:

1.先根据mapper.class类型来获取MapperProxyFactory

2.再调用MapperProxyFactory对象的newInstance方法获得Mapper。

我们看MapperProxyFactory类代码:

public T newInstance(SqlSession sqlSession) {  
  //创建一个Mapperxy对象,这个方法实现了JDK动态代理中的InvocationHandler接口
  final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);  
  return newInstance(mapperProxy);  
}  
  
protected T newInstance(MapperProxy<T> mapperProxy) {  
  //mapperInterface,说明Mapper接口被代理了,这样子返回的对象就是Mapper接口的子类,方法被调用时会被mapperProxy拦截,也就是执行mapperProxy.invoke()方法  
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);  
}

有上可见,最后返回的其实是一个MapperProxy对象,就是mybatis自己实现的JDK动态代理的一个代理类

所以

    List<Dept> deptList = deptDao.findAll();

以上代码可以直接到MapperProxy对象里面去看,调用的就是MapperProxy.invoke()方法;

再来看看MapperProxy,

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  //Mapper接口 
  private final Class<T> mapperInterface;

    /*
     * Mapper接口中的每个方法都会生成一个MapperMethod对象, methodCache维护着他们的对应关系 
     */  

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

  //这里会拦截Mapper接口的所有方法 
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) { //如果是Object中定义的方法,直接执行。如toString(),hashCode()等
      try {
        return method.invoke(this, args);//
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);  //其他Mapper接口定义的方法交由mapperMethod来执行
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

}

MapperMethod (内部命令模式)

现在一条龙下来了吧,就要来看看MapperMethod 对应的是什么层次的东西了.由上我们看见invoke方法返回的是一个mapperMethod.execute(sqlSession, args)方法

//这里会拦截Mapper接口的所有方法 
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) { //如果是Object中定义的方法,直接执行。如toString(),hashCode()等
      try {
        return method.invoke(this, args);//
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
      //这里查询以下缓存
    final MapperMethod mapperMethod = cachedMapperMethod(method);  //其他Mapper接口定义的方法交由mapperMethod来执行
    return mapperMethod.execute(sqlSession, args);
  }

最后2句关键,我们执行所调用Mapper的每一个接口方法,最后返回的是MapperMethod.execute方法。每一个MapperMethod对应了一个mapper文件中配置的一个sql语句或FLUSH配置,对应的sql语句通过mapper对应的class文件名+方法名从Configuration对象中获得。

而当执行MapperMethod的execute方法的时候,根据当前MapperMethod对应的mapper配置会执行Session的insert, update, delete, select, selectList, selectMap, selectCursor, selectOne或flushStatements方法。

这里只截取一部分的MapperMethod,这就是一个典型的命令模式了;

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

最后在这个里面的XXXexecuteFor方法调用又要回到SqlSession了

 private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
     //这里查看是否有分页参数
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
        //最后gogogo,sqlSession 参数
      result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
      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;
  }

再来看看:DefaultSqlSession类里面的selectList方法

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
        //真正执行方法的在 executor.query里面 但是我门得来看看 configuration是从那里来的
      List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
      return result;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

DefaultSqlSession类的成员executor是在构造函数里面给他赋值的。所以我们又要回头去查看一下是在什么时候实例化了DefaultSqlSession类。

DefaultSqlSession在SqlSessionFactory的实现类DefaultSqlSessionFactory中被创建:以下是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); 
      //在这里哦 调用的是configuration的 newExecutor 
    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();    
  }    
}    
    
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {    
  try {    
    boolean autoCommit;    
    try {    
      autoCommit = connection.getAutoCommit();    
    } catch (SQLException e) {    
      // Failover to true, as most poor drivers    
      // or databases won't support transactions    
      autoCommit = true;    
    }          
    final Environment environment = configuration.getEnvironment();    
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);    
    final Transaction  = transactionFactory.newTransaction(connection);
      //这里哦 ,configuration.newExecutor
    final Executor executor = configuration.newExecutor(tx, execType);    
    return new DefaultSqlSession(configuration, executor, autoCommit);    
  } catch (Exception e) {    
    throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);    
  } finally {    
    ErrorContext.instance().reset();    
  }    
}

然后再来看看Configuration的newExecutor方法:

Configuration的构造过程

这里还是得说一下:Configuration实例来在与读xml文件 算了,我截取出来:

SqlSessionFactoryBuilder.build

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        //构造出一个解析 xml的Builder类
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        // build xmlConfigBuilder的parse方法
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

接下来看XMLConfigBuilder:

//先到这个 
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {//然后适配器以下,委托给XPathParser 去解析xml文件
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }
	//看
  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
  //Configuration 就是这个时候new出来的
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }
....
    .....super是啥
 是 abstract   BaseBuilder
  public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
  }

好new出来一个XMLConfigBuilder之后呢? 调用它的parse()

 public Configuration parse() {
 //查看是否加载过,以免重复加载
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
     //让后解析,封装xml里的数据到configuration里面 封装之后的就不看了 不是很重要了    /configuration 这个parser 是XPathParser
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

回到 Configuration的newExecutor:

 public Executor newExecutor(Transaction  transaction, ExecutorType executorType) {
     //如果是空就调用defaultExecutorType protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
     //判断以下下 executorType 创建相应的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);
    }
     //缓存哦,缓存是否开启   protected boolean cacheEnabled = true; 默认开启
     //所以不管上面是个啥 都回开启这个 executor 然后把这个封装到CachingExecutor
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
     //放入执行链里面
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

接下来就要看下面的 Executor类了

Executor

贴一张调用栈:
在这里插入图片描述

每一个SqlSession对象都被分配一个Executor,主要负责connection获取和statement对象管理方案。

Mybatis对外统一提供了一个操作接口类Executor,提供的接口方法有update、query、flushStatements、commit、rollback等接口函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O2mUX78M-1586691066721)(…/%E6%BA%90%E7%A0%81/assets/1584509043528.png)]

简单执行器SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。(可以是Statement或PrepareStatement对象)

重用执行器ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。(可以是Statement或PrepareStatement对象)

批量执行器BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理的;BatchExecutor相当于维护了多个桶,每个桶里都装了很多属于自己的SQL,就像苹果蓝里装了很多苹果,番茄蓝里装了很多番茄,最后,再统一倒进仓库。(可以是Statement或PrepareStatement对象)

缓存执行器CachingExecutor:装饰设计模式典范,先从缓存中获取查询结果,存在就返回,不存在,再委托给Executor delegate去数据库取,delegate可以是上面任一的SimpleExecutor、ReuseExecutor、BatchExecutor。

无用执行器ClosedExecutor:毫无用处,读者可自行查看其源码,仅作为一种标识,和Serializable标记接口作用相当。

作用范围:以上这五个执行器的作用范围,都严格限制在SqlSession生命周期范围内。

BaseExecutor 是一个抽象类,其只实现了一些公共的封装,而把真正的核心实现都通过方法抽象出来给子类实现,如doUpdate(),doQuery();CachingExecutor 只是在Executor的基础上加入了缓存的功能,底层还是通过执行程序调用的,所以真正有作用的执行器只有SimpleExecutor ,ReuseExecutor 和批处理。它们都是自己实现的执行器核心功能,没有借助任何其它的执行程序实现,它们是实现不同也就注定了它们的功能也是不一样的。执行人是跟一个SqlSession 绑定在一起的,每一个SqlSession的都拥有一个新的执行官对象,由配置创建。

BaseExecutor源码,很重要哦

好像就是做了一层结果缓存的查询,先查询缓存

1、先查本地缓存,没有再去查数据库,注意这里的本地缓存是同一个session内的缓存,也就是同一个opensession内。
2、通过configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT来看,可以设置参数,将本地缓存去掉,不使用本地缓存。
清除本地缓存,做更新操作,具体更新操作在具体执行器中。


public abstract class BaseExecutor implements Executor {

  private static final Log log = LogFactory.getLog(BaseExecutor.class);
  //事务
  protected Transaction transaction;
  //执行器的包装对象
  protected Executor wrapper;
  //线程安全队列
  protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
  //本地缓存
  protected PerpetualCache localCache;
  protected PerpetualCache localOutputParameterCache;
  //mybatis的配置信息
  protected Configuration configuration;
  //查询堆栈
  protected int queryStack = 0;
  private boolean closed;

  protected BaseExecutor(Configuration configuration, Transaction transaction) {
    this.transaction = transaction;
    this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
    this.localCache = new PerpetualCache("LocalCache");
    this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
    this.closed = false;
    this.configuration = configuration;
    this.wrapper = this;
  }

  public Transaction getTransaction() {
    if (closed) throw new ExecutorException("Executor was closed.");
    return transaction;
  }

  public void close(boolean forceRollback) {
    try {
      try {
        rollback(forceRollback);
      } finally {
        if (transaction != null) transaction.close();
      }
    } catch (SQLException e) {
      // Ignore.  There's nothing that can be done at this point.
      log.warn("Unexpected exception on closing transaction.  Cause: " + e);
    } finally {
      transaction = null;
      deferredLoads = null;
      localCache = null;
      localOutputParameterCache = null;
      closed = true;
    }
  }

  public boolean isClosed() {
    return closed;
  }

   //SqlSession的update/insert/delete会调用此方法
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) throw new ExecutorException("Executor was closed.");
    //先清局部缓存,再更新,如何更新由子类实现,模板方法模式
    clearLocalCache();
    return doUpdate(ms, parameter);
  }

  public List<BatchResult> flushStatements() throws SQLException {
    return flushStatements(false);
  }

  public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
    if (closed) throw new ExecutorException("Executor was closed.");
    return doFlushStatements(isRollBack);
  }

  //SqlSession.selectList会调用此方法 
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);//得到绑定sql  
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);//创建缓存key
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);//查询  
 }

  @SuppressWarnings("unchecked")
  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.");
    //先清局部缓存,再查询,但仅仅查询堆栈为0才清,为了处理递归调用 
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;//加一,这样递归调用到上面的时候就不会再清局部缓存了 
      //根据cachekey从localCache去查 
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        //如果查到localCache缓存,处理localOutputParameterCache  
        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();
      }
       //清空延迟加载队列
      deferredLoads.clear(); // issue #601
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        //如果是statement,清本地缓存  
        clearLocalCache(); // issue #482
      }
    }
    return list;
  }

  //延迟加载 
  public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
    if (closed) throw new ExecutorException("Executor was closed.");
    DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
    if (deferredLoad.canLoad()) {//如果能加载则立即加载,否则加入到延迟加载队列中
        deferredLoad.load();
    } else {
        deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
    }
  }

  //创建缓存key 
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) throw new ExecutorException("Executor was closed.");
    CacheKey cacheKey = new CacheKey();
    //MyBatis 对于其 Key 的生成采取规则为:[mappedStementId + offset + limit + SQL + queryParams + environment]生成一个哈希码
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    for (int i = 0; i < parameterMappings.size(); i++) { // mimic DefaultParameterHandler logic
      ParameterMapping parameterMapping = parameterMappings.get(i);
      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);
      }
    }
    return cacheKey;
  }    

  public boolean isCached(MappedStatement ms, CacheKey key) {
    return localCache.getObject(key) != null;
  }

  public void commit(boolean required) throws SQLException {
    if (closed) throw new ExecutorException("Cannot commit, transaction is already closed");
    clearLocalCache();
    flushStatements();
    if (required) {
      transaction.commit();
    }
  }

  public void rollback(boolean required) throws SQLException {
    if (!closed) {
      try {
        clearLocalCache();
        flushStatements(true);
      } finally {
        if (required) {
          transaction.rollback();
        }
      }
    }
  }

  //清空本地缓存,一个map结构  
  public void clearLocalCache() {
    if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }

  protected abstract int doUpdate(MappedStatement ms, Object parameter)
      throws SQLException;

  protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
      throws SQLException;

  protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException;

  protected void closeStatement(Statement statement) {
    if (statement != null) {
      try {
        statement.close();
      } catch (SQLException e) {
        // ignore
      }
    }
  }

  //处理存储过程的out参数
  private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
    if (ms.getStatementType() == StatementType.CALLABLE) {
      final Object cachedParameter = localOutputParameterCache.getObject(key);
      if (cachedParameter != null && parameter != null) {
        final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
        final MetaObject metaParameter = configuration.newMetaObject(parameter);
        for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
          if (parameterMapping.getMode() != ParameterMode.IN) {
            final String parameterName = parameterMapping.getProperty();
            final Object cachedValue = metaCachedParameter.getValue(parameterName);
            metaParameter.setValue(parameterName, cachedValue);
          }
        }
      }
    }
  }

  //从数据库中查 
  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) {//如果是存储过程,OUT参数也加入缓存
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

  protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }
  
  public void setExecutorWrapper(Executor wrapper) {
    this.wrapper = wrapper;
  }
  
  //延迟加载  
  private static class DeferredLoad {

    private final MetaObject resultObject;
    private final String property;
    private final Class<?> targetType;
    private final CacheKey key;
    private final PerpetualCache localCache;
    private final ObjectFactory objectFactory;
    private final ResultExtractor resultExtractor;

    public DeferredLoad(MetaObject resultObject,
                        String property,
                        CacheKey key,
                        PerpetualCache localCache,
                        Configuration configuration,
                        Class<?> targetType) { // issue #781
      this.resultObject = resultObject;
      this.property = property;
      this.key = key;
      this.localCache = localCache;
      this.objectFactory = configuration.getObjectFactory();
      this.resultExtractor = new ResultExtractor(configuration, objectFactory);
      this.targetType = targetType;
    }
 
    //缓存中找到,且不为占位符,代表可以加载 
    public boolean canLoad() {
      return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
    }

    //加载
    public void load() {
      @SuppressWarnings( "unchecked" ) // we suppose we get back a List
      List<Object> list = (List<Object>) localCache.getObject(key);
      Object value = resultExtractor.extractObjectFromList(list, targetType);
      resultObject.setValue(property, value);
    }

  }

}

executor.query

以上是基本概念,接下来接着看:从上面的DefaultSqlSession.selectList中的executor.query看

dubug调用的时候它是默认调用CachingExecutor的,我们来看看它经历了些什么:

为什么会经过CachingExecutor 呢?

我门来找一下:我们找到了嘿嘿 还是

DefaultSqlSessionFactory的openSessionFromDataSource ->

final Executor executor = configuration.newExecutor(tx, execType);->

 if (cacheEnabled) {
     //默认开启 装饰者模式 把配置文件里的executor 封装进去
      executor = new CachingExecutor(executor);
    }

好了,接着看

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
      //显示得到MappedStatement 绑定的sql 
    BoundSql boundSql = ms.getBoundSql(parameterObject);
      //
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
//然后再调用 query()
@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
      //从MappedStatement里还是找缓存 不存在缓存
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, parameterObject, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
      //如果没有缓存对象,就委托给delegate对象去执行
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

我们接着看delegate 是个啥:

 private Executor delegate;
  public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
      //把CachingExecutor 引用传递进去,其实再delegate里面并没有怎么用到这个CachingExecutor
    delegate.setExecutorWrapper(this);
  }

是一个Executor,又得来看它的来龙去脉了:

请回忆到,或者往上翻一翻我就知道了:

原来是从配置文件里面读出来的相应的Executor:

这里有点疑惑了,SimpleExecutor里面没有query方法啊,别急

public class SimpleExecutor extends BaseExecutor //他是基础了BaseExecutor的 点进去看看
... 
    ...
//它是要调用父类的query方法的  BaseExecutor的 
   @SuppressWarnings("unchecked")
  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.");
    //先清局部缓存,再查询,但仅仅查询堆栈为0才清,为了处理递归调用 
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;//加一,这样递归调用到上面的时候就不会再清局部缓存了 
      //根据cachekey从localCache去查 
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        //如果查到localCache缓存,处理localOutputParameterCache  
        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();
      }
       //清空延迟加载队列
      deferredLoads.clear(); // issue #601
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        //如果是statement,清本地缓存  
        clearLocalCache(); // issue #482
      }
    }
    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 {
        // 看看这个doQuery 又是个啥
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);//清除占位符
    }
    localCache.putObject(key, list);//加入缓存
    if (ms.getStatementType() == StatementType.CALLABLE) {//如果是存储过程,OUT参数也加入缓存
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

//哇,是个抽象方法,那么就得留给SimpleExecutor了,终于到子类了
  protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException;

SimpleExecutor.query

 @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 里面有statement,来处理 
        //注意哦,这个wrapper就是CacheExecutor 
  
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

来看看 这个StatementHandler 是咋创建出来的? Configuration的

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    
   
    //它会根据Executor的类型去创建对应具体的statementHandler对象(SimpleStatementHandler,PreparedStatementHandler和CallableStatementHandler)。
     //然后放入interceptorChain 拦截器链里面
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

再点进去看一看:

public class RoutingStatementHandler implements StatementHandler {
	//注意这个delegate,我们拦截statementHandler的时候就要常常访问它了
  private final StatementHandler delegate;

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
	//这个new出来的对象放在delegate里面,等到再SimpleExecutor里面其实是委托给delegate去执行
      //又是一个显而易见的装饰者模式
    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());
    }

  }
    //Routing其实不做其他的事
     @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    return delegate.<E>query(statement, resultHandler);
  }

接着SimpleExecutor再来看看这个prepareStatement()方法干了些啥,注意哈,这个handler是RoutingStatementHandler

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

进入到BaseStatementHandler的prepare()

@Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
       //可见是调用的delegate.prepare,我们的是SimpleS,而Simple调用的是其抽象父类的模板方法
        //instantiateStatement 是调用子类的
      statement = instantiateStatement(connection);
      setStatementTimeout(statement, transactionTimeout);
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }

instantiateStatement():

@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
  if (mappedStatement.getResultSetType() != null) {
      //创建了Statement 
      //这个方法非常简单,我们可以看到它主要是根据上下文来预编译SQL,这是我们还没有设置参数。设置参数的任务是交由,statement接口的parameterize方法来实现的。
    return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
  } else {
    return connection.createStatement();
  }
}

所以现在是不是很明显了?得到StatementHandler了,有一点得提一下:4个Excecutor执行sql操作的最终都调用了StatementHandler 来执行

StatementHandler

在MyBatis实现了StatementHandler 的有四个类:
RoutingStatementHandler,这是一个封装类,它不提供具体的实现,只是根据Executor的类型,创建不同的类型StatementHandler。
SimpleStatementHandler,这个类对应于JDBC的Statement对象,用于没有预编译参数的SQL的运行。
PreparedStatementHandler 这个用于预编译参数SQL的运行。
CallableStatementHandler 它将实存储过程的调度。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JQkhAyLq-1586691066723)(assets/1584600319817.png)]

这里接着SimpleExecutor.query方法:就会去调用handler.query.而这个handler截取的是SimpleStatementHandler:

 @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    statement.execute(sql);
    return resultSetHandler.<E>handleResultSets(statement);
  }

由上面的一阵操作得到了statement,然后执行sql最后交给resultSetHandler去处理结果

当我们需要改变sql的时候,显然我们要在预编译SQL(prepare方法前加入修改的逻辑)。
当我们需要修改参数的时候我们可以在调用parameterize方法前修改逻辑。或者使用ParameterHandler来改造设置参数。
我们需要控制组装结果集的时候,也可以在query方法前后加入逻辑,或者使用ResultHandler来改造组装结果。
懂的这些方法,才能理解我需要拦截什么对象,如何处理插件,这是MyBatis的核心内容。

ParameterHandler

在StatementHandler使用prepare()方法后,接下来就是使用ParameterHandler来设置参数,让我们看看它的定义:

public class DefaultParameterHandler implements ParameterHandler { 
   
  private final TypeHandlerRegistry typeHandlerRegistry; 
   
  private final MappedStatement mappedStatement; 
  private final Object parameterObject;  //所有的参数值 
  private BoundSql boundSql; 
  private Configuration configuration; 
   
  public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { 
    this.mappedStatement = mappedStatement; 
    this.configuration = mappedStatement.getConfiguration(); 
    this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry(); 
    this.parameterObject = parameterObject; 
    this.boundSql = boundSql; 
  } 
   
  @Override 
  public Object getParameterObject() { 
    return parameterObject; 
  } 
   
  @Override 
  public void setParameters(PreparedStatement ps) { 
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); 
    //获取所有参数,ParameterMapping是java类型和jdbc类型的对应关系 
    List<ParameterMapping> 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; 
          //获取参数名称 
          String propertyName = parameterMapping.getProperty(); 
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params 
            //获取参数值 
            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); 
          } 
          //获取参数值对应的jdbc类型 
          TypeHandler typeHandler = parameterMapping.getTypeHandler(); 
          JdbcType jdbcType = parameterMapping.getJdbcType(); 
          if (value == null && jdbcType == null) { 
            jdbcType = configuration.getJdbcTypeForNull(); 
          } 
          try { 
            //设置参数值和jdbc类型的对应关系 
            typeHandler.setParameter(ps, i + 1, value, jdbcType); 
          } catch (TypeException e) { 
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); 
          } catch (SQLException e) { 
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); 
          } 

getParameterObject是获取参数,这个参数值就是你传递进来的值,可能是个实体、map或单个基本类型数据。

重点看setParameters(),首先它读取了ParameterObject参数对象,然后用typeHandler对参数进行设置,而typeHandler里面需要对jdbcType和javaType进行处理,然后就设置参数了。也很好理解。所以当我们使用TypeHandler的时候完全可以控制如何设置SQL参数。设置参数,其实就是你在sql语句中配置的java对象和jdbc类型对应的关系,例如#{id,jdbcType=INTEGER},id默认类型是javaType=class java.lang.Integer。

ResultHandler

ResultSetHandler负责处理两件事:

(1)处理Statement执行后产生的结果集,生成结果列表

(2)处理存储过程执行后的输出参数

ResultSetHandler的具体实现类是DefaultResultSetHandler,其实现的步骤就是将Statement执行后的结果集,按照Mapper文件中配置的ResultType或ResultMap来封装成对应的对象,最后将封装的对象返回。

SqlSessionManager

 String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
//        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//        Configuration configuration = sqlSessionFactory.getConfiguration();
//        SqlSession sqlSession = sqlSessionFactory.openSession();
//        DeptDao deptDao = sqlSession.getMapper(DeptDao.class);
        SqlSessionManager sqlSessionManager = SqlSessionManager.newInstance(inputStream);
        DeptDao deptDao = sqlSessionManager.getMapper(DeptDao.class);
        List<Dept> deptList = deptDao.findAll();
        System.out.println(deptList.toString());

SqlSessionFactory 和SqlSession都不见了

public class SqlSessionManager implements SqlSessionFactory, SqlSession {

  private final SqlSessionFactory sqlSessionFactory;
  private final SqlSession sqlSessionProxy;

  private ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<SqlSession>();

  private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
    this.sqlSessionFactory = sqlSessionFactory;
    this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[]{SqlSession.class},
        new SqlSessionInterceptor());
  }
...

与DefaultSqlSessionFactory不同的是,SqlSessionManager提供了一个本地线程变量(ThreadLocal localSqlSession),每当通过startManagedSession()获得session实例的时候,会在本地线程保存session实例。这是其一不同。

 public void startManagedSession() {
    this.localSqlSession.set(openSession());
  }

插件原理

https://www.cnblogs.com/xrq730/p/6984982.html

1、原理

Mybatis的拦截器实现机制跟上面最后优化后的代码非常的相似。它也有个代理类Plugin(就是上面的HWInvocationHandler)这个类同样也会实现了InvocationHandler接口,

当我们调用ParameterHandler,ResultSetHandler,StatementHandler,Executor的对象的时候,就会执行Plugin的invoke方法,Plugin在invoke方法中根据

@Intercepts的配置信息(方法名,参数等)动态判断是否需要拦截该方法.再然后使用需要拦截的方法Method封装成Invocation,并调用Interceptor的proceed方法。

这样我们就达到了拦截目标方法的结果。例如Executor的执行大概是这样的流程:

拦截器代理类对象->拦截器->目标方法
Executor.Method->Plugin.invoke->Interceptor.intercept->Invocation.proceed->method.invoke。
2、如何自定义拦截器?

1) Interceptor接口

首先Mybatis官方早就想到我们开发会有这样的需求,所以开放了一个org.apacheibatis.plugin.Interceptor这样一个接口。这个接口就是和上面Interceptor性质是一样的

public interface Interceptor {
  //当plugin函数返回代理,就可以对其中的方法进行拦截来调用intercept方法
  Object intercept(Invocation invocation) throws Throwable;
  //plugin方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理。
  Object plugin(Object target);
 //在Mybatis配置文件中指定一些属性
  void setProperties(Properties properties);
}

2)自定义拦截器

这里的ExamplePlugin和上面的LogInterceptor和TransactionInterceptor性质是一样的

@Intercepts({@Signature( type= Executor.class,  method = "update", args ={MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
  public Object intercept(Invocation invocation) throws Throwable {
    return invocation.proceed();
  }
  public Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }
  public void setProperties(Properties properties) {
  }
}

3)、全局xml配置

最后如果你使用的是Mybatis.xml也就是Mybatis本身单独的配置,你可以需要在这里配置相应的拦截器名字等。

如果你使用的是spring管理的Mybatis,那么你需要在Spring配置文件里面配置注册相应的拦截器。

这样一个自定义mybatis插件流程大致就是这样了。

3、Mybatis四大接口

竟然Mybatis是对四大接口进行拦截的,那我们要先要知道Mybatis的四大接口对象 Executor, StatementHandle, ResultSetHandler, ParameterHandler

1.Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) MyBatis的执行器,用于执行增删改查操作;
2.ParameterHandler (getParameterObject, setParameters) 处理SQL的参数对象;
3.ResultSetHandler (handleResultSets, handleOutputParameters) 处理SQL的返回结果集;
4.StatementHandler (prepare, parameterize, batch, update, query) 拦截Sql语法构建的处理

img

上图Mybatis框架的整个执行过程。

Mybatis Plugin 插件源码

经过上面的分析,再去看Mybastis Plugin 源码的时候就很轻松了。

img

这几个也就对应上面的几个,只不过添加了注解,来判断是否拦截指定方法。

1、拦截器链InterceptorChain
public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

  public Object pluginAll(Object target) {
    //循环调用每个Interceptor.plugin方法
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }
  
  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }
}

这个就和我们上面实现的是一样的。定义了拦截器链

2、Configuration

通过初始化配置文件把所有的拦截器添加到拦截器链中。

public class Configuration {

    protected final InterceptorChain interceptorChain = new InterceptorChain();
    //创建参数处理器
  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    //创建ParameterHandler
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    //插件在这里插入
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

  //创建结果集处理器
  public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
      ResultHandler resultHandler, BoundSql boundSql) {
    //创建DefaultResultSetHandler
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    //插件在这里插入
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

  //创建语句处理器
  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;
  }

  public Executor newExecutor(Transaction transaction) {
    return newExecutor(transaction, defaultExecutorType);
  }

  //产生执行器
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    //这句再做一下保护,囧,防止粗心大意的人将defaultExecutorType设成null?
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    //然后就是简单的3个分支,产生3种执行器BatchExecutor/ReuseExecutor/SimpleExecutor
    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);
    }
    //如果要求缓存,生成另一种CachingExecutor(默认就是有缓存),装饰者模式,所以默认都是返回CachingExecutor
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    //此处调用插件,通过插件可以改变Executor行为
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
}

从代码可以看出Mybatis 在实例化Executor、ParameterHandler、ResultSetHandler、StatementHandler四大接口对象的时候调用interceptorChain.pluginAll()方法插入

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-viOe1yLM-1586691150620)(assets/1584513934498.png)]

进去的。其实就是循环执行拦截器链所有的拦截器的plugin() 方法,mybatis官方推荐的plugin方法是Plugin.wrap() 方法,这个类就是我们上面的TargetProxy类。

3、Plugin

这里的Plugin就是我们上面的自定义代理类TargetProxy类

public class Plugin implements InvocationHandler {

    public static Object wrap(Object target, Interceptor interceptor) {
    //从拦截器的注解中获取拦截的类名和方法信息
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    //取得要改变行为的类(ParameterHandler|ResultSetHandler|StatementHandler|Executor)
    Class<?> type = target.getClass();
    //取得接口
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    //产生代理,是Interceptor注解的接口的实现类才会产生代理
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }
    
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //获取需要拦截的方法
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      //是Interceptor实现类注解的方法才会拦截处理
      if (methods != null && methods.contains(method)) {
        //调用Interceptor.intercept,也即插入了我们自己的逻辑
        return interceptor.intercept(new Invocation(target, method, args));
      }
      //最后还是执行原来逻辑
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }
    
    //取得签名Map,就是获取Interceptor实现类上面的注解,要拦截的是那个类(Executor,ParameterHandler,   ResultSetHandler,StatementHandler)的那个方法
  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    //取Intercepts注解,例子可参见ExamplePlugin.java
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    // issue #251
    //必须得有Intercepts注解,没有报错
    if (interceptsAnnotation == null) {
      throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());      
    }
    //value是数组型,Signature的数组
    Signature[] sigs = interceptsAnnotation.value();
    //每个class里有多个Method需要被拦截,所以这么定义
    Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
    for (Signature sig : sigs) {
      Set<Method> methods = signatureMap.get(sig.type());
      if (methods == null) {
        methods = new HashSet<Method>();
        signatureMap.put(sig.type(), methods);
      }
      try {
        Method method = sig.type().getMethod(sig.method(), sig.args());
        methods.add(method);
      } catch (NoSuchMethodException e) {
        throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
      }
    }
    return signatureMap;
  }
    
    //取得接口
  private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
    Set<Class<?>> interfaces = new HashSet<Class<?>>();
    while (type != null) {
      for (Class<?> c : type.getInterfaces()) {
        //拦截其他的无效
        if (signatureMap.containsKey(c)) {
          interfaces.add(c);
        }
      }
      type = type.getSuperclass();
    }
    return interfaces.toArray(new Class<?>[interfaces.size()]);
  }
}
4、Interceptor接口
public interface Interceptor {

  //拦截
  Object intercept(Invocation invocation) throws Throwable;
  //插入
  Object plugin(Object target);
  //设置属性(扩展)
  void setProperties(Properties properties);

}

Mybatis用到的设计模式

日志 适配器模式

设计模式经典实践-Mybatis源码解析

一级缓存&二级缓存

Mybatis缓存的作用

每当我们使用 MyBatis 开启一次和数据库的会话,MyBatis 会创建出一个 SqlSession 对象表示一次数据库会话。

在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,如果不采取一些措施的话,每一次查询都会查询一次数据库,而我们在极短的时间内做了完全相同的查询,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。

为了解决这一问题,减少资源的浪费,MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了。

mybatis的缓存有一级缓存和二级缓存。

##一级缓存

一级缓存是默认开启的,作用域是session级别的,缓存的key格式如下:

cache key: id + sql + limit + offset 

在commit之前,第一次查询结果换以key value的形式存起来,如果有相同的key进来,直接返回value,这样有助于减轻数据的压力。

相关源码:

org.apache.ibatis.executor.BaseExecutor#createCacheKey

public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (this.closed) {
        throw new ExecutorException("Executor was closed.");
    } else {
        CacheKey cacheKey = new CacheKey();
        cacheKey.update(ms.getId());
        cacheKey.update(rowBounds.getOffset());
        cacheKey.update(rowBounds.getLimit());
        cacheKey.update(boundSql.getSql());
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();

        for(int i = 0; i < parameterMappings.size(); ++i) {
            ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);
            if (parameterMapping.getMode() != ParameterMode.OUT) {
                String propertyName = parameterMapping.getProperty();
                Object value;
                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 = this.configuration.newMetaObject(parameterObject);
                    value = metaObject.getValue(propertyName);
                }

                cacheKey.update(value);
            }
        }

        if (this.configuration.getEnvironment() != null) {
            cacheKey.update(this.configuration.getEnvironment().getId());
        }

        return cacheKey;
    }
}

查询数据库并存入一级缓存的语句

org.apache.ibatis.executor.BaseExecutor#queryFromDatabase

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);

    List list;
    try {
        list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
        this.localCache.removeObject(key);
    }

    //将查询出来的结果存入一级缓存
    this.localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
     //如果是存储过程把参数存入localOutputParameterCache
        this.localOutputParameterCache.putObject(key, parameter);
    }

    return list;
}

并且当commit或者rollback的时候会清除缓存,并且当执行insert、update、delete的时候也会清除缓存。

相关源码:

org.apache.ibatis.executor.BaseExecutor#update

public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (this.closed) {
        throw new ExecutorException("Executor was closed.");
    } else {
        //删除一级缓存
        this.clearLocalCache();
        return this.doUpdate(ms, parameter);
    }
}


 org.apache.ibatis.executor.BaseExecutor#commit
 
 public void commit(boolean required) throws SQLException {
    if (this.closed) {
        throw new ExecutorException("Cannot commit, transaction is already closed");
    } else {
        //删除一级缓存
        this.clearLocalCache();
        this.flushStatements();
        if (required) {
            this.transaction.commit();
        }

    }
}

org.apache.ibatis.executor.BaseExecutor#rollback

public void rollback(boolean required) throws SQLException {
    if (!this.closed) {
        try {
            //删除一级缓存
            this.clearLocalCache();
            this.flushStatements(true);
        } finally {
            if (required) {
                this.transaction.rollback();
            }

        }
    }

}

二级缓存

二级缓存是手动开启的,作用域为sessionfactory(也可以说MapperStatement级缓存,也就是一个namespace就会有一个缓存),因为二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的,也就是要求实现Serializable接口,如果存储在内存中的话,实测不序列化也可以的。

如果开启了二级缓存的话,你的Executor将会被装饰成CachingExecutor,缓存是通过CachingExecutor来操作的,查询出来的结果会存在statement中的cache中,若有更新,删除类的操作默认就会清空该MapperStatement的cache(也可以通过修改xml中的属性,让它不执行),不会影响其他的MapperStatement。

相关源码:

org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? this.defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Object 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);
    }

    //是否开启缓存,传入的参数为SimpleExecutor
    if (this.cacheEnabled) {
        executor = new CachingExecutor((Executor)executor);
    }

    //责任链模式拦截器
    Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
    return executor;
}

query

org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)

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

org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    //获得该MappedStatement的cache
    Cache cache = ms.getCache();
    //如果缓存不为空
    if (cache != null) {
        //看是否需要清除cache(在xml中可以配置flushCache属性决定何时清空cache)
        this.flushCacheIfRequired(ms);
        //若开启了cache且resultHandler 为空
        if (ms.isUseCache() && resultHandler == null) {
            this.ensureNoOutParams(ms, parameterObject, boundSql);
            //从TransactionalCacheManager中取cache
            List<E> list = (List)this.tcm.getObject(cache, key);
            //若取出来list是空的
            if (list == null) {
                //查询数据库
                list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                //将结果存入cache中
                this.tcm.putObject(cache, key, list);
            }

            return list;
        }
    }
    //如果缓存为空,去查询数据库
    return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

对于

this.tcm.getObject(cache, key);  

因同一个namespace下的MappedStatement的cache是同一个,而TransactionalCacheManager中统一管理cache是里面的属性transactionalCaches,该属性以MappedStatement中的Cache为key,TransactionalCache对象为Value。即一个namespace对应一个TransactionalCache。

相关源码:

TransactionalCacheManager

org.apache.ibatis.cache.TransactionalCacheManager

public class TransactionalCacheManager {
private Map<Cache, TransactionalCache> transactionalCaches = new HashMap();

...
}

TransactionalCache

org.apache.ibatis.cache.decorators.TransactionalCache

public class TransactionalCache implements Cache {
    private static final Log log = LogFactory.getLog(TransactionalCache.class);
    //namespace中的cache
    private Cache delegate;
    //提交的时候清除cache的标志位
    private boolean clearOnCommit;
    //待提交的集合
    private Map<Object, Object> entriesToAddOnCommit;
    //未查到的key存放的集合
    private Set<Object> entriesMissedInCache;

...
}

update

//更新
org.apache.ibatis.executor.CachingExecutor#update

public int update(MappedStatement ms, Object parameterObject) throws SQLException {
     //看是否需要清除cache(在xml中可以配置flushCache属性决定何时清空cache)
    this.flushCacheIfRequired(ms);
    return this.delegate.update(ms, parameterObject);
}

org.apache.ibatis.executor.CachingExecutor#flushCacheIfRequired

private void flushCacheIfRequired(MappedStatement ms) {
    //获得cache
    Cache cache = ms.getCache();
    //若isFlushCacheRequired为true,则清除cache
    if (cache != null && ms.isFlushCacheRequired()) {
        this.tcm.clear(cache);
    }

}

一级、二级缓存测试

因为一级缓存是默认生效的,下面是二级缓存开启步骤。

mybatis-config.xml

<settings>
    <!--这个配置使全局的映射器(二级缓存)启用或禁用缓存-->
    <setting name="cacheEnabled" value="true" />
</settings>

在mapper.xml可以进行如下的配置

<mapper>
   <!--开启本mapper的namespace下的二级缓存-->
    <!--
        eviction:代表的是缓存回收策略,目前MyBatis提供以下策略。
        (1) LRU,最近最少使用的,一处最长时间不用的对象
        (2) FIFO,先进先出,按对象进入缓存的顺序来移除他们
        (3) SOFT,软引用,移除基于垃圾回收器状态和软引用规则的对象
        (4) WEAK,弱引用,更积极的移除基于垃圾收集器状态和弱引用规则的对象。这里采用的是LRU,
                移除最长时间不用的对形象
  
        flushInterval:刷新间隔时间,单位为毫秒,这里配置的是100秒刷新,如果你不配置它,那么当
        SQL被执行的时候才会去刷新缓存。
  
        size:引用数目,一个正整数,代表缓存最多可以存储多少个对象,不宜设置过大。设置过大会导致内存溢出。
        这里配置的是1024个对象
  
        readOnly:只读,意味着缓存数据只能读取而不能修改,这样设置的好处是我们可以快速读取缓存,缺点是我们没有
        办法修改缓存,他的默认值是false,不允许我们修改
    -->
    <cache eviction="LRU" flushInterval="100000" readOnly="true" size="1024"/>

    <!--刷新二级缓存-->
  <update id="updateByPrimaryKey" parameterType="com.demo.mybatis.pojo.User" flushCache="true">
    update user
    set name = #{name,jdbcType=VARCHAR},
      age = #{age,jdbcType=INTEGER}
    where id = #{id,jdbcType=INTEGER}
  </update>

   <!--可以通过设置useCache来规定这个sql是否开启缓存,ture是开启,false是关闭-->
   <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap"   useCache="true" >
     select
     <include refid="Base_Column_List" />
     from user
     where id = #{id,jdbcType=INTEGER}
   </select>
  
</mapper>

其中仅仅添加下面这个也可以

 <cache/>

如果我们配置了二级缓存就意味着:

  • 映射语句文件中的所有select语句将会被缓存。
  • 映射语句文件中的所欲insert、update和delete语句会刷新缓存。
  • 缓存会使用默认的Least Recently Used(LRU,最近最少使用的)算法来收回。
  • 根据时间表,比如No Flush Interval,(CNFI没有刷新间隔),缓存不会以任何时间顺序来刷新。
  • 缓存会存储列表集合或对象(无论查询方法返回什么)的1024个引用。
  • 缓存会被视为是read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全的被调用者修改,不干扰其他调用者或线程所做的潜在修改。

User.java

public class User implements Serializable {
    private Integer id;

    private String name;

    private Integer age;

    private static final long serialVersionUID = 1L;
    
    ...
    set/get
    ...
    
}

UserMapper.java

    User selectByPrimaryKey(Integer id);

测试方法

@Test
public void test03() throws IOException {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    //第一次
    User user = userMapper.selectByPrimaryKey(1);
    System.out.println("user1 => " + user.toString());
    //第二次
    User user2 = userMapper.selectByPrimaryKey(1);
    System.out.println("user2 => " + user2.toString());
    //session提交
    sqlSession.commit();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
    //第三次
    User user3 = userMapper2.selectByPrimaryKey(1);
    System.out.println("user3 => " + user3.toString());
    //第四次
    User user4 = userMapper2.selectByPrimaryKey(1);
    System.out.println("user4 => " + user4.toString());
    sqlSession2.commit();
}

来看下结果

DEBUG 2019-01-30 00:01:29791 Opening JDBC Connection
DEBUG 2019-01-30 00:01:34688 Created connection 1121453612.
DEBUG 2019-01-30 00:01:34689 Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@42d8062c]
DEBUG 2019-01-30 00:01:34691 ==>  Preparing: select id, name, age from user where id = ? 
DEBUG 2019-01-30 00:01:34737 ==> Parameters: 1(Integer)
DEBUG 2019-01-30 00:01:34757 <==      Total: 1
user1 => User{id=1, name='ayang', age=18}
DEBUG 2019-01-30 00:01:34757 Cache Hit Ratio [com.demo.mybatis.mapper.UserMapper]: 0.0
user2 => User{id=1, name='ayang', age=18}
DEBUG 2019-01-30 00:01:34818 Cache Hit Ratio [com.demo.mybatis.mapper.UserMapper]: 0.3333333333333333
user3 => User{id=1, name='ayang', age=18}
DEBUG 2019-01-30 00:01:34819 Cache Hit Ratio [com.demo.mybatis.mapper.UserMapper]: 0.5
user4 => User{id=1, name='ayang', age=18}

可以看到第一次和第二次走的是一级缓存,第三次和第四次走的是二级缓存。

总结:

一级缓存是自动开启的,sqlSession级别的缓存,查询结果存放在BaseExecutor中的localCache中。

如果第一次做完查询,接着做一次update | insert | delete | commit | rollback操作,则会清除缓存,第二次查询则继续走数据库。

对于一级缓存不同的sqlSession之间的缓存是互相不影响的。

二级缓存是手动开启的,作用域为sessionfactory,也可以说MapperStatement级缓存,也就是一个namespace(mapper.xml)就会有一个缓存,不同的sqlSession之间的缓存是共享的。

因为二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的,也就是要求实现Serializable接口,如果存储在内存中的话,实测不序列化也可以的。

一般为了避免出现脏数据,所以我们可以在每一次的insert | update | delete操作后都进行缓存刷新,也就是在Statement配置中配置flushCache属性,如下:

<!--刷新二级缓存  flushCache="true"-->
  <update id="updateByPrimaryKey" parameterType="com.demo.mybatis.pojo.User" flushCache="true">
    update user
    set name = #{name,jdbcType=VARCHAR},
      age = #{age,jdbcType=INTEGER}
    where id = #{id,jdbcType=INTEGER}
</update>

手写mybatis

https://www.cnblogs.com/leeSmall/p/10426629.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值