MyBatis源码简析

MyBatis源码简析

Mybatis作为一个经久不衰的ORM框架,其源码被众多人膜拜过,今天简单走一遍。

原生的经典使用示例:

InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sessionFacotry = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sessionFacotry.openSession();
		
OrderMapper mapper = session.getMapper(OrderMapper.class);
mapper.selectById(1L);

使用层面的API不赘述,进入源码看看这几句代码都发生的什么。

配置的加载和解释

SqlSessionFactoryBuilder

SqlSessionFactoryBuilder#build

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    
    
    try {
    
    
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      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.
      }
    }
  }
    
  public SqlSessionFactory build(Configuration config) {
    
    
    return new DefaultSqlSessionFactory(config);
  }

SqlSessionFactoryBuilder是经典的Builder模式,实现比较简单:通过XMLConfigBuilder解释配置文件得到Configuration对象,最后通过Configuration创建DefaultSqlSessionFactory。

XMLConfigBuilder

public Configuration parse() {
    
    
    if (parsed) {
    
    
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  //按顺序处理配置文件中的各个元素
  private void parseConfiguration(XNode root) {
    
    
    try {
    
    
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
    
    
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

这里主要看对mappers的解释:

private void mapperElement(XNode parent) throws Exception {
    
    
    if (parent != null) {
    
    
      for (XNode child : parent.getChildren()) {
    
    
        if ("package".equals(child.getName())) {
    
    
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);//A:解释Mapper接口
        } else {
    
    
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
    
    
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();//B:解释Mapper.xml
          } else if (resource == null && url != null && mapperClass == null) {
    
    
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();//B:解释Mapper.xml
          } else if (resource == null && url == null && mapperClass != null) {
    
    
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);//A:解释Mapper接口
          } else {
    
    
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

//mappers元素支持两种配置,一种按package,一种按mapper,mapper可以指向具体的xml或Mapper接口。如果通过Mapper接口走configuration.addMapper()解释,如果是xml走XMLMapperBuilder.parse()。这两种实现是等价,他们的实现内部会相互调用。
configuration.addMapper()会委托给MapperRegistry.addMapper():

MapperRegistry

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 {
    
    
        //执行Mapper注册
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        //执行Mapper解释
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
    
    
        if (!loadCompleted) {
    
    
          knownMappers.remove(type);
        }
      }
    }
  }

先是建立Mapper接口与MapperProxyFactory的映射,便于后继创建接口代理,然后通过Builder模式MapperAnnotationBuilder的解释,将每个接口方法解释成MappedStatement,并注册进Configuration中。MapperAnnotationBuilder#parse():

public void parse() {
    
    
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
    
    
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      Method[] methods = type.getMethods();
      for (Method method : methods) {
    
    
        try {
    
    
          // issue #237
          if (!method.isBridge()) {
    
    
          	//将每个接口方法解释成MappedStatement,并注册进Configuration中。
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
    
    
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }

parseStatement()主要解释Mapper接口方法上的各种注释,构造MappedStatement对象,并进行注册。 这里源码不贴出。
通过上面的一系列解释,生成Configuration对象,该类可以说是Mybatis最核心最重要的对象。

Configuration

Configuration对象的结构包含了mybatis-config.xml配置文件的所有信息,也包含了各个Mapper.xml的所有信息。以及维持和加速构架运行的其它信息。Mybatis中的各种配置项都会对应到一个对象。内部核心数据对象:

protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();

  //维护每个MappedStatement
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
  protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
  protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");

我们主要看Mapper相关信息的封装。
在这里插入图片描述
MapperProxy:与Mapper接口一一对应,Mapper接口的动态代理的回调处理器,内部维护着一个映射:Map<Method, MapperMethod>
MappedStatement:与XML中的一个CRUD元素(或者一个@Insert/@Update/@Select/@Delete/@SqlProvider对应),封装着一次数据库操作的SQL,参数,结果集映射等信息。
MapperMethod:与Mapper接口中的一个方法对应,提供execute方法,内部会调用SqlSession的crud方法。
SqlSource:封装着一次数据库操作的SQL,参数等。提供5种实现,无论哪种实现最终在执行阶段会被转换成StaticSqlSource,并最终生成BoundSql对象,该对象可被Executor直接执行。
再以Configuration的视角看怎么追溯到配置的内容:
在这里插入图片描述
一句话,Configuration可以直接或间接获取到配置相关和Mapper相关的所有信息。

解释完配置,分析SqlSession.getMapper()

动态生成Mapper接口代理

代码比较简单,直接贴出流程图:
在这里插入图片描述

MapperProxyFactory

代码已经足够清晰,不用多做解释

public class MapperProxyFactory<T> {
    
    

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

  public MapperProxyFactory(Class<T> mapperInterface) {
    
    
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    
    
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    
    
    return methodCache;
  }

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

  public T newInstance(SqlSession sqlSession) {
    
    
    //通过接口创建MapperProxy实例
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

SQL的执行

根据上面的分析,Mybatis会为Mapper创建动态代理,并通过MapperProxy来处理回调,按照动态代理的套路,重点看MaperProxy.invoke()方法:

MaperProxy

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
    try {
    
    
      if (Object.class.equals(method.getDeclaringClass())) {
    
    
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
    
    
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
    
    
      throw ExceptionUtil.unwrapThrowable(t);
    }
    //从缓存中查找MapperMethod,找不到则创建
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

MapperProxy主要有两个属性:

private final SqlCommand command;
private final MethodSignature method;

MapperProxy.execute()根据SqlCommand的不同类型,执行不同的操作

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

以select为例,分析如何拿到结果集:所有的查询最终会调用SqlSession.selectList()方法

SqlSession

SqlSession是面向开发者的最重要最熟悉的接口,该接口提供了封装了各种基础的SQL操作,同时可以通过该接口获取到各种Mapper接口。其默认实现是DefaultSqlSession,注:该实现不是线程安全,Spring提供了线程安全版本:SqlSessionTemplate。

@Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    
    
    try {
    
    
      //通过statementId查找到MappedStatement
      MappedStatement ms = configuration.getMappedStatement(statement);
      //委托给executor执行查询
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
    
    
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
    
    
      ErrorContext.instance().reset();
    }
  }

Executor主要有三个实现:BatchExecutor,ResueExecutor,和SimpleExecutor,通过默认是SimpleExecutor,这三者的公用逻辑被封装成BaseExecutor,而CachingExecutor当开起二级缓存时使用,
在这里插入图片描述
具体见Configuration.newExecutor():

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    
    
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
    
    
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
    
    
      executor = new ReuseExecutor(this, transaction);
    } else {
    
    
      executor = new SimpleExecutor(this, transaction);
    }
    //如果开启二级缓存
    if (cacheEnabled) {
    
    
      executor = new CachingExecutor(executor);
    }
    //插件增强,后面会细讲
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

BaseExecutor负责一级缓存的维护,BaseExecutor.query():

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.");
    }
    //SQL上设置了flushCache=true,则清空一级缓存
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
    
    
      clearLocalCache();
    }
    List<E> list;
    try {
    
    
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
    
    
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
    
    
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
    
    
      queryStack--;
    }
    if (queryStack == 0) {
    
    
      for (DeferredLoad deferredLoad : deferredLoads) {
    
    
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      //如果设置了loaclCacheScope=STATEMENT,则清空一级缓存
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
    
    
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    
    
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
    
    
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
    
    
      localCache.removeObject(key);
    }
    //维护一级缓存
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
    
    
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

doQuery是抽象方法,这里选择SimpleExecutor继续走:

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    
    
    Statement stmt = null;
    try {
    
    
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
    
    
      closeStatement(stmt);
    }
  }
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    
    
    Statement stmt;
    Connection connection = getConnection(statementLog);
    //调用Connection.prepareStatement,设置timeout,fetchSize
    stmt = handler.prepare(connection, transaction.getTimeout());
    //设置SQL的参数
    handler.parameterize(stmt);
    return stmt;
  }

先看回Configuration.newStatementHandler(),这里返回的是经过插件增加了的StatementHandler。

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 = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

StatementHandler有四个实现,其中

  • CallableStatementHandler对应jdbc的CallableStatement
  • Statementr对应jdbc的CallableStatement
  • PreparedStatementHandler对应jdbc的PreparedStatement
  • RoutingStatementHandler则是根据Statement的类型委托给上面三种实现中的一种去处理。

在这里插入图片描述
回来看看StatementHandler.query():

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

StatementHandler将参数设置和结果集处理分离出来,分别交到了ParameterHandler和ResultSetHandler,这两者都只提供一个实现。代码不详

plugin的实现

上面有些地方,有提到mybatis的插件机制。这里先上一张不严格的类图:
在这里插入图片描述
Mybatis的插件机制主要看Plugin和Interceptor,Plugin实现了InvocationHandler,同时聚合了Interceptor和4大组件(之一),会被包进动态代理。在invoke中调用Interceptor去实现最终的拦截操作。

4大组件

Mybatis支持对参与SQL执行的4大组件进行增加,分别是:
1.Executor
2.StatementHandler
3.ResultSetHandler
4.ParameterHandler
上面代码中有涉及到两块,Executor和StatementHandler,在Mybatis的实现中,4大组件的创建都是由Configuration提供创建入口,其原因主要是方便对组件进行增强,因为Configuration维护着各种配置,这其中自然也包括了plugin,在Mybatis中,plugin的本质就是实现对4大组件的增加。插件常用来实现诸如自动分页,数据脱敏,数据加解密等工作。
在实现层面,Mybatis通过InterceptorChain将4大对象和Interceptor包装进Plugin,并创建了动态代理,该类其实现了InvocationHandler ,最终由该类完成动态的回调逻辑。

InterceptorChain

InterceptorChain#pluginAll()

public Object pluginAll(Object target) {
    
    
    for (Interceptor interceptor : interceptors) {
    
    
      target = interceptor.plugin(target);
    }
    return target;
  }

pluginAll()很简单,就是以链的形式,将Interceptor 进行增强。通常情况下,interceptor.plugin()会调整Plugin.wrap(target, this),将目标对象和Interceptor自身包装进Plugin,并创建动态代理。看下Interceptor 的接口声明。

Interceptor

public interface Interceptor {
    
    
  //执行拦截
  Object intercept(Invocation invocation) throws Throwable;
  //该方法通常用于对target进行增加
  Object plugin(Object target);
  //属性注入
  void setProperties(Properties properties);

}

Plugin

public class Plugin implements InvocationHandler {
    
    

  private final Object target;
  private final Interceptor interceptor;
  private final Map<Class<?>, Set<Method>> signatureMap;

  private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
    
    
    this.target = target;
    this.interceptor = interceptor;
    this.signatureMap = signatureMap;
  }

  public static Object wrap(Object target, Interceptor interceptor) {
    
    
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    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());
      //如果目标方法有命中拦截器,则执行拦截
      if (methods != null && methods.contains(method)) {
    
    
        return interceptor.intercept(new Invocation(target, method, args));
      }
      //没命中则直接调用
      return method.invoke(target, args);
    } catch (Exception e) {
    
    
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }
}

整个插件动态代理的过程就是一个套娃的过程,前面的Interceptor会被套到最里层,会最晚被执行。
在这里插入图片描述
总结一下:
1.Mybatis的先执行配置的加载和解释,生成Configuration,该对象是构建SqlSessionFactory的基础。
2.SqlSession提供开发API的门面,其数据库操作委托给Executor,Executor不断细分合工,衍生出了StatementHandler,ResultSetHandler,ParameterHandler。
3.Mybatis提供了灵活的plugin机制,业务开发可以扩展Interceptor,拦截4大组件干点事情 。
4.Mybatis对各种组件都做了很好的封装,除了上面提到了SqlSession,Executor,StatementHandler等,还包括各种SqlSource/Buider/TypeHandler等,代码实现优雅,值得好好学习。

by simple

猜你喜欢

转载自blog.csdn.net/vipshop_fin_dev/article/details/121777104