MyBatis源码学习(一)

昨天的我是个小木匠

图文不符系列?


工作中经常用到MyBatis,刚开始学习的时候,只会使用,却不知道底层原理,最近看了《MyBatis技术内幕》还有Debug跟踪了一下源码,去学习它的底层思想。

MyBatis的官网介绍:

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。


图化两个流程

Github中将源码拉下来,Debug了binding包下的BindingTest类,简单分析整个流程:

一、数据初始化阶段

二、查询阶段


源码分析

代码位置:org.apache.ibatis.binding.BindingTest

初始化

  @BeforeClass
  public static void setup() throws Exception {
    // 创建一个没有池化的数据源
    DataSource dataSource = BaseDataTest.createBlogDataSource();
    // 数据源执行两个SQL脚本,第一个是清除数据,第二个是插入数据
    BaseDataTest.runScript(dataSource, BaseDataTest.BLOG_DDL);
    BaseDataTest.runScript(dataSource, BaseDataTest.BLOG_DATA);
    // 创建jdbc事务工厂和环境
    TransactionFactory transactionFactory = new JdbcTransactionFactory();
    Environment environment = new Environment("Production", transactionFactory, dataSource);
    // 创建配置(配置巨多...),并且设置可延迟加载和注册类别名、添加两个Mapper资源
    Configuration configuration = new Configuration(environment);
    configuration.setLazyLoadingEnabled(true);
    configuration.getTypeAliasRegistry().registerAlias(Blog.class);
    configuration.getTypeAliasRegistry().registerAlias(Post.class);
    configuration.getTypeAliasRegistry().registerAlias(Author.class);
    configuration.addMapper(BoundBlogMapper.class);
    configuration.addMapper(BoundAuthorMapper.class);
    // 构建SqlSessionFactory的工厂,参数是configuration,返回的是DefaultSqlSession
    sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
  }
复制代码

Configuration配置参数

讲下配置巨多的Configuration: org.apache.ibatis.session.Configuration mybatis的所有配置信息都存储到Configuration类中,在初始化的时候进行加载

public class Configuration {

  //环境
  protected Environment environment;

  //---------<settings>节点,一般从xml加载-------
  protected boolean safeRowBoundsEnabled = false;
  protected boolean safeResultHandlerEnabled = true;
  protected boolean mapUnderscoreToCamelCase = false;
  protected boolean aggressiveLazyLoading = true;
  protected boolean multipleResultSetsEnabled = true;
  protected boolean useGeneratedKeys = false;
  protected boolean useColumnLabel = true;
  //默认启用缓存
  protected boolean cacheEnabled = true;
  protected boolean callSettersOnNulls = false;
  
  ······

  //类型处理器注册机
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
  //类型别名注册机
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();

  //映射的语句,存在Map里
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
  //缓存,存在Map里
  protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
  //结果映射,存在Map里
  protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
  ······

  //不完整的SQL语句
  protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
  ······
  
  // 初始化Configuration时会注册类型别名
  public Configuration() {
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
    ······
    }
}
复制代码

简单介绍它的属性对应的含义:

  • Environment:配置了环境ID(决定加载哪种环境-生产OR开发)、事务工厂和数据源
  • Settings: 主要是运行时的全局设置
  • Registrys: 配置了很多注册器: ①:mapperRegistry,映射注册器,主要用来加载自定义mapper; ②:typeHandlerRegistry,类型处理器,主要用于保存JDBC Type,JAVA Type与处理器的映射关系,各个Handler主要继承BaseTypeHandler,同时用户可以继承自定义实现; ③:typeAliasRegistry,类型别名注册器,但是typeAliasRegister构造方法里面已经有很多基础包装类型,在构建configuration中为何还要加载?? ④:LanguageDriverRegistry,脚本语言注册器,在configuration初始化时,设定XMLLanguageDriver为默认驱动,并添加RawLanguageDriver驱动

创建SqlSessionFactory

在初始化的时候,创建SqlSessionFactory调用的方法是 org.apache.ibatis.session.SqlSessionFactoryBuilder.build(Configuration config)

/*
 * Builds {@link SqlSession} instances.
 * 构建SqlSessionFactory的工厂.工厂模式
 *
 */
/**
 * @author Clinton Begin
 */
public class SqlSessionFactoryBuilder {
    ······
    public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
}

/**
 * 默认的SqlSessionFactory
 * 
 */
public class DefaultSqlSessionFactory implements SqlSessionFactory {

  private final Configuration configuration;

  public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
  }
  ······
}
复制代码

到此,创建完sqlSessionFactory之后,初始化阶段就完成了。


查询阶段

简单查询?:

  @Test
  public void shouldSelectBlogWithPostsUsingSubSelect() throws Exception {
    // 打开会话连接
    SqlSession session = sqlSessionFactory.openSession();
    try {
      // 从会话中拿到mapper资源
      BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
      // 通过mapper接口进行查询
      Blog b = mapper.selectBlogWithPostsUsingSubSelect(1);
      // 剩下都是进行单元测试的校验···
      assertEquals(1, b.getId());
      session.close();
      assertNotNull(b.getAuthor());
      assertEquals(101, b.getAuthor().getId());
      assertEquals("jim", b.getAuthor().getUsername());
      assertEquals("********", b.getAuthor().getPassword());
      assertEquals(2, b.getPosts().size());
    } finally {
      session.close();
    }
  }
复制代码

打开sqlSession会话连接

刚才创建的是DefaultSqlSessionFactory,最终打开会话会调用org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      // 从环境中取出,该方法中判断如果没有配置txFactory,则返回托管txFactory
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //通过事务工厂来产生一个事务,最终生成的是org.apache.ibatis.transaction.jdbc.JdbcTransaction,用来控制commit和rollback
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //生成一个执行器(事务包含在执行器里)
      final Executor executor = configuration.newExecutor(tx, execType);
      //然后产生一个DefaultSqlSession,构造方法如下
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      //如果打开事务出错,则关闭它
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      //最后清空错误上下文
      ErrorContext.instance().reset();
    }
  }
  
/**
 * 默认SqlSession实现
 *
 */
public class DefaultSqlSession implements SqlSession {

  private Configuration configuration;
  private Executor executor;

  /**
   * 是否自动提交
   */
  private boolean autoCommit;
  private boolean dirty;
  
  public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }

  public DefaultSqlSession(Configuration configuration, Executor executor) {
    this(configuration, executor, false);
  }
  ······
}

复制代码

到此,获取sqlSession连接就结束了

通过mapper接口进行查询

mapperProxy:映射器代理,代理模式

从刚才取出的sqlSession获得mapper资源,接着调用mapper的接口方法,会进入mapperProxy代理方法 org.apache.ibatis.binding.MapperProxy#invoke(有种AOP的切面思想,与Spring类似??)

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 这个判断是,如果这个方法是Object中的通用方法,则不需要执行mapperMethod
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    //从去缓存中找MapperMethod
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //执行
    return mapperMethod.execute(sqlSession, args);
  }
  
  //去缓存中找MapperMethod
  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      //找不到才去new
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
复制代码
mapperMethod:映射器方法

经过代理之后,进入org.apache.ibatis.binding.MapperMethod#execute执行

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //有4种情况,insert|update|delete|select,分别调用SqlSession的4大类方法
    if (SqlCommandType.INSERT == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      if (method.returnsVoid() && method.hasResultHandler()) {
        //如果有结果处理器
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        //如果结果有多条记录
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        //如果结果是map
        result = executeForMap(sqlSession, args);
      } else {
        //我们这次是查询方法,只查询一条记录
        //转换参数
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else {
      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;
  }
复制代码

接着进入DefaultSqlSession进行查询 org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne(java.lang.String, java.lang.Object)

  @Override
  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    // 去调用selectList,如果得到结果数量大于1,进行报错;不然返回第一条记录
    // 特别需要主要的是当没有查询到结果的时候就会返回null。因此一般建议在mapper中编写resultType的时候使用包装类型
    // 而不是基本类型,比如推荐使用Integer而不是int。这样就可以避免NPE(空指针异常)
    List<T> list = this.<T>selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }
  
  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //根据statement id找到对应的MappedStatement
      MappedStatement ms = configuration.getMappedStatement(statement);
      //转而用执行器来查询结果,注意这里传入的ResultHandler是null
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  
  private <E> List<E> selectList() throws SQLException {
    Executor localExecutor = executor;
    //如果executor已经被关闭了,则创建一个新的
    if (Thread.currentThread().getId() != this.creatorThreadId || localExecutor.isClosed()) {
      localExecutor = newExecutor();
    }
    try {
      // 从CachingExecutor.query中的delegate代理又调用一次Executor.query
      return localExecutor.<E> query(mappedStatement, parameterObject, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER, cacheKey, boundSql);
    } finally {
      if (localExecutor != executor) {
        localExecutor.close(false);
      }
    }
  }
复制代码

使用执行器进行查询 org.apache.ibatis.executor.BaseExecutor#query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql)

  @Override
  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();
      }
      // issue #601
      //清空延迟加载队列
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
    	//如果是STATEMENT,清本地缓存
        clearLocalCache();
      }
    }
    return list;
  }
复制代码

查询到Object结果后,进入结果抽取器进行解析 org.apache.ibatis.executor.ResultExtractor#extractObjectFromList

  public Object extractObjectFromList(List<Object> list, Class<?> targetType) {
    Object value = null;
    if (targetType != null && targetType.isAssignableFrom(list.getClass())) {
      //1.如果targetType是list,直接返回list
      value = list;
    } else if (targetType != null && objectFactory.isCollection(targetType)) {
      //2.如果targetType是Collection,返回包装好的list
      value = objectFactory.create(targetType);
      MetaObject metaObject = configuration.newMetaObject(value);
      metaObject.addAll(list);
    } else if (targetType != null && targetType.isArray()) {
      //3.如果targetType是数组,则数组转list
      Class<?> arrayComponentType = targetType.getComponentType();
      Object array = Array.newInstance(arrayComponentType, list.size());
      if (arrayComponentType.isPrimitive()) {
        for (int i = 0; i < list.size(); i++) {
          Array.set(array, i, list.get(i));
        }
        value = array;
      } else {
        value = list.toArray((Object[])array);
      }
    } else {
      //4.最后返回list的第0个元素
      if (list != null && list.size() > 1) {
        throw new ExecutorException("Statement returned more than one row, where no more than one was expected.");
      } else if (list != null && list.size() == 1) {
        value = list.get(0);
      }
    }
    return value;
  }
复制代码

返回结果Object后,经过PropertyTokenizer(属性分词器)、BeanWrapper(Bean包装器)进行metaResultObject结果设置: org.apache.ibatis.reflection.wrapper.BeanWrapper#set

  @Override
  public void set(PropertyTokenizer prop, Object value) {
    //如果有index,说明是集合,那就要解析集合,调用的是BaseWrapper.resolveCollection 和 setCollectionValue
    if (prop.getIndex() != null) {
      Object collection = resolveCollection(prop, object);
      setCollectionValue(prop, collection, value);
    } else {
        //否则,setBeanProperty
      setBeanProperty(prop, object, value);
    }
  }
  
  private void setBeanProperty(PropertyTokenizer prop, Object object, Object value) {
    try {
        //得到setter方法,然后调用(通过反射org.apache.ibatis.reflection.Reflector#getSetInvoker)
      Invoker method = metaClass.getSetInvoker(prop.getName());
      Object[] params = {value};
      try {
        // method执行org.apache.ibatis.reflection.invoker.MethodInvoker#invoke
        method.invoke(object, params);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    } catch (Throwable t) {
      throw new ReflectionException("Could not set property '" + prop.getName() + "' of '" + object.getClass() + "' with value '" + value + "' Cause: " + t.toString(), t);
    }
  }
复制代码

调用到这个地方,其实已经从blog表中查询到一条blog记录,然后由于mapper文件设定的sql语句是:

<resultMap id="blogWithPosts" type="Blog">
    <id property="id" column="id"/>
    <result property="title" column="title"/>
    <association property="author" column="author_id"
                 select="selectAuthorWithInlineParams"/>
    <collection property="posts" column="id" select="selectPostsForBlog"/>
</resultMap>
<select id="selectBlogWithPostsUsingSubSelect" parameterType="int" resultMap="blogWithPosts">
    select * from Blog where id = #{id}
</select>
<select id="selectAuthorWithInlineParams"
            parameterType="int"
            resultType="org.apache.ibatis.domain.blog.Author">
    select * from author where id = #{id}
</select>
<select id="selectPostsForBlog" parameterType="int" resultType="Post">
    select * from Post where blog_id = #{blog_id}
</select>
复制代码

官网解释节点含义:

  • constructor :用于在实例化类时,注入结果到构造方法中
  • idArg :ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
  • arg : 将被注入到构造方法的一个普通结果
  • id : 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能
  • result : 注入到字段或 JavaBean 属性的普通结果
  • association : 一个复杂类型的关联;许多结果将包装成这种类型 嵌套结果映射 : 关联可以指定为一个 resultMap 元素,或者引用一个
  • collection : 一个复杂类型的集合 嵌套结果映射 : 集合可以指定为一个 resultMap 元素,或者引用一个
  • discriminator : 使用结果值来决定使用哪个 resultMap
  • case : 基于某些值的结果映射 嵌套结果映射 : 一个 case 也是一个映射它本身的结果,因此可以包含很多相 同的元素,或者它可以参照一个外部的 resultMap。

可以看出,这条查询语句涉及到三个表,所以第一次查询到blog表中的一条记录后,循环调用查询流程,进行一个author查询和posts集合查询

将所有查询结果汇总: org.apache.ibatis.executor.loader.ResultLoaderMap#loadAll

  public boolean load(String property) throws SQLException {
	//先删除key,防止第二次又去查数据库就不对了
    LoadPair pair = loaderMap.remove(property.toUpperCase(Locale.ENGLISH));
    if (pair != null) {
      //去数据库查
      pair.load();
      return true;
    }
    return false;
  }

  public void loadAll() throws SQLException {
    final Set<String> methodNameSet = loaderMap.keySet();
    String[] methodNames = methodNameSet.toArray(new String[methodNameSet.size()]);
    // 方法列表
    for (String methodName : methodNames) {
      load(methodName);
    }
  }
复制代码

最后通过Javassist进行延迟加载: org.apache.ibatis.executor.loader.javassist.JavassistProxyFactory.EnhancedResultObjectProxyImpl#invoke

public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
      final String methodName = method.getName();
      try {
        synchronized (lazyLoader) {
          if (WRITE_REPLACE_METHOD.equals(methodName)) {
            Object original = null;
            if (constructorArgTypes.isEmpty()) {
              original = objectFactory.create(type);
            } else {
              original = objectFactory.create(type, constructorArgTypes, constructorArgs);
            }
            PropertyCopier.copyBeanProperties(type, enhanced, original);
            if (lazyLoader.size() > 0) {
              return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);
            } else {
              return original;
            }
          } else {
            if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
              if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
                lazyLoader.loadAll();
              } else if (PropertyNamer.isProperty(methodName)) {
                final String property = PropertyNamer.methodToProperty(methodName);
                if (lazyLoader.hasLoader(property)) {
                  lazyLoader.load(property);
                }
              }
            }
          }
        }
        return methodProxy.invoke(enhanced, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
  }
复制代码

好吧,这个太底层了,这个我没看懂,有了解的小伙伴请跟我说说~~??

结果返回之后,就要进行会话连接中断之类的就不用说了。好了,整个调用链路都清晰了,只能说,发明这个持久化框架的Clinton Begin大神??!


挖坑

先挖个坑,下一篇要学MyBatis的分页组件(虽然GitHub已经有,原则是不要重复造轮子,但是只学习了调用原理,还是多去深入学习一下吧~)


参考资料: ①:Mybatis源码分析(一)- Configuration配置文件详解 ②:mybatis源码中文注释 ③:《MyBatis技术内幕》--徐郡明

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值