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

扫描二维码关注公众号,回复: 10961564 查看本文章

请求对象相似的范围内。换句话说,基于收到的 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

发布了37 篇原创文章 · 获赞 6 · 访问量 4656

猜你喜欢

转载自blog.csdn.net/littlewhitevg/article/details/105474365