MyBatis源代码学习

什么是 MyBatis

 

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

版本

mybatis:3.5.3

源码分析

使用(不集成Spring)

public class App {
    public static void main(String[] args) {
        String resource = "mybatis-config.xml";
        Reader reader;
        try {
            //将XML配置文件构建为Configuration配置类
            reader = Resources.getResourceAsReader(resource);
            // 通过加载配置文件流构建一个SqlSessionFactory  DefaultSqlSessionFactory
            SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
            // 数据源 执行器  DefaultSqlSession
            SqlSession session = sqlMapper.openSession();
            try {
                // 执行查询 底层执行jdbc
                TimeZoneName timeZoneName = (TimeZoneName)session.selectOne("com.whc.mapper.TimeZoneNameMapper.selectById", 12);

                TimeZoneNameMapper timeZoneNameMapper = session.getMapper(TimeZoneNameMapper.class);
                TimeZoneName zoneName = timeZoneNameMapper.selectById(11);
                System.out.println(zoneName);
                /*UserMapper mapper = session.getMapper(UserMapper.class);
                System.out.println(mapper.getClass());
                User user = mapper.selectById(1L);*/
                session.commit();
                System.out.println(timeZoneName);
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                session.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

流程主要分为以下几步:

1.解析配置文件

2.解析mapper

3.获取SqlSession

4.使用

解析配置文件

最终在org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration 中解析xml配置文件各个属性

 private void parseConfiguration(XNode root) {
        try {
            //1.解析 properties节点
            propertiesElement(root.evalNode("properties"));
            //2.解析 settings节点
            Properties settings = settingsAsProperties(root.evalNode("settings"));
            //3.加载自定义文件系统
            loadCustomVfs(settings);
            //4.加载自定义日志实现类
            loadCustomLogImpl(settings);
            //5.加载别名
            typeAliasesElement(root.evalNode("typeAliases"));
            //6.加载插件
            pluginElement(root.evalNode("plugins"));
            //不常用
            objectFactoryElement(root.evalNode("objectFactory"));
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            reflectorFactoryElement(root.evalNode("reflectorFactory"));
            //7.设置settings 和默认值
            settingsElement(settings);
            //8.environments节点
            environmentsElement(root.evalNode("environments"));
            //9.解析数据库厂商
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            //10.解析我们的类型处理器节点
            typeHandlerElement(root.evalNode("typeHandlers"));
            //11.解析mapper
            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }

1.解析属性(properties)

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>

2.解析设置(settings)

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
  <setting name="useGeneratedKeys" value="false"/>
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <setting name="safeRowBoundsEnabled" value="false"/>
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

3.加载自定义文件系统

VFS含义是虚拟文件系统;主要是通过程序能够方便读取本地文件系统、FTP文件系统等系统中的文件资源。

Mybatis中提供了VFS这个配置,主要是通过该配置可以加载自定义的虚拟文件系统应用程序 解析到:org.apache.ibatis.session.Configuration#vfsImpl

4.加载自定义日志实现类

指定 MyBatis 所用日志的具体实现,未指定时将自动查找。

* SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING *

解析到org.apache.ibatis.session.Configuration#logImpl

5.加载类型别名(typeAliases)

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。

<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
  <typeAlias alias="Comment" type="domain.blog.Comment"/>
  <typeAlias alias="Post" type="domain.blog.Post"/>
  <typeAlias alias="Section" type="domain.blog.Section"/>
  <typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>

当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。

6.加载插件

configuration.addInterceptor(interceptorInstance);

 会加入到  InterceptorChain 拦截器链

7.设置settings 和默认值

 private void settingsElement(Properties props) {
    configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
    configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
    configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
    configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
    configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
    configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
    configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
    configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
    configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
    configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
    configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
    configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
    configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
    configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
    configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
    configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
    configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
    configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
    configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
    configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
    configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
    configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
    configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
    configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
    configuration.setLogPrefix(props.getProperty("logPrefix"));
    configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
  }

8.environments环境配置节点

MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。

不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

9.解析数据库厂商

MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 MyBatis 会加载带有匹配当前数据库 databaseId 属性和所有不带 databaseId 属性的语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。 为支持多厂商特性,只要像下面这样在 mybatis-config.xml 文件中加入 databaseIdProvider 即可:

<databaseIdProvider type="DB_VENDOR" />

10.解析我们的类型处理器typeHandlers节点

MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。

11.解析映射器(mappers)

指定mapper映射文件可以通过如下4中方式:类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名

<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>

解析mapper文件

入口在:org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement

我们是通过<package name=""></package> 包名批量注册的,所以会走

if ("package".equals(child.getName()))分支,

通过configuration.addMappers(mapperPackage)来解析

在MapperRegistry#addMappers(java.lang.String, java.lang.Class<?>)中循环指定包名下的每个类,使用 MapperAnnotationBuilder 的parse()解析每个类,

最终用XMLMapperBuilder.parse() 方法解析mapper.xml文件

我们来看看org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement,具体解析mapper.xml

 private void configurationElement(XNode context) {
    try {
      /**
       * 1.解析namespace属性
       * <mapper namespace="com.whc.mapper.EmployeeMapper">
       */
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      /**
       * 2.保存当前的namespace  并且判断接口完全类名==namespace
       */
      builderAssistant.setCurrentNamespace(namespace);
      /**
       * 3.解析缓存引用
       * 说明我当前的缓存引用和DeptMapper的缓存引用一致
       * <cache-ref namespace="com.whc.mapper.DeptMapper"></cache-ref>
            解析到org.apache.ibatis.session.Configuration#cacheRefMap<当前namespace,ref-namespace>
            异常下(引用缓存未使用缓存):org.apache.ibatis.session.Configuration#incompleteCacheRefs
       */
      cacheRefElement(context.evalNode("cache-ref"));
      /**
       * 4.解析cache节点
       * <cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
          解析到:org.apache.ibatis.session.Configuration#caches
                 org.apache.ibatis.builder.MapperBuilderAssistant#currentCache
       */
      cacheElement(context.evalNode("cache"));
      /**
       * 5.解析paramterMap节点(该节点mybaits3.5貌似不推荐使用了)
       */
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      /**
       * 6.解析我们的resultMap节点
       * 解析到:org.apache.ibatis.session.Configuration#resultMaps
       *    异常 org.apache.ibatis.session.Configuration#incompleteResultMaps
       *
       */
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      /**
       * 7.解析我们通过sql节点
       *  解析到org.apache.ibatis.builder.xml.XMLMapperBuilder#sqlFragments
       *   其实等于 org.apache.ibatis.session.Configuration#sqlFragments
       *   因为他们是同一引用,在构建XMLMapperBuilder 时把Configuration.getSqlFragments传进去了
       */
      sqlElement(context.evalNodes("/mapper/sql"));
      /**
       * 8.解析我们的select | insert |update |delete节点
       * 解析到org.apache.ibatis.session.Configuration#mappedStatements
       */
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

当配置了<cache ></cache>节点后,会在“4”中创建二级缓存对象

二级缓存对象通过装饰器模式,一层层包装

SynchronizedCache-->LoggingCache-->SerializedCache-->LruCache-->PerpetualCache

而解析select | insert |update |delete节点会在XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>)方法中,通过 langDriver.createSqlSource(),由默认的XMLLanguageDriver 驱动来解析,最终递归解析每个节点

递归解析  selectById这个sql元素会解析成
   1层  MixedSqlNode <SELECT>
   2层  WhereSqlNode <WHERE>
   2层  IfSqlNode <IF>

把解析结果放入 mappedStatement

获取SqlSession

通过SqlSessionFactory.openSession()获得回话

openSession()主要作用:

1.创建执行器 Executor : newExecutor

2.依次调用每个插件的plugin方法,包装Executor

  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 {
      //简单的sql执行器对象
      executor = new SimpleExecutor(this, transaction);
    }
    //判断mybatis的全局配置文件是否开启缓存
    if (cacheEnabled) {
      //把当前的简单的执行器包装成一个CachingExecutor
    }
    executor = new CachingExecutor(executor);
    /**
     * 调用所有的拦截器对象plugin方法
     */
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

执行查询

 TimeZoneNameMapper timeZoneNameMapper = session.getMapper(TimeZoneNameMapper.class);
                TimeZoneName zoneName = timeZoneNameMapper.selectById(11);

 由于我们是返回单个对象,所以调用selectOne

 最后我们得到了

O:className

R:jdbcTypes

M:columnNames

根据对应关系,我们得到了返回值对象


流程图

总体

 解析xml

 执行sql

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值