Mybatis源码分析-配置模块

楼主比较菜,肯定有很多说的不对的地方,主要还是写给自己看的!!
比起spring来说,mybatis实在是简单,所以就先来聊聊mybatis!
先来张mybatis整体的结构图 瞧瞧
Mybatis设计结构如下

从MyBatis代码实现的角度来看,MyBatis的主要的核心部件有以下几个:
1 SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
2 Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
3 StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
4 ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所需要的参数,
5 ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
6 TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换
7 MappedStatement MappedStatement维护了一条select|update|delete|insert节点的封装
8 SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
9 BoundSql 表示动态生成的SQL语句以及相应的参数信息
10 Configuration MyBatis所有的配置信息都维持在Configuration对象之中。

我们用一个列子来看下mybatis 的配置模块

String resource = "configs/mybatis-config.xml";
        Reader reader = Resources.getResourceAsReader(resource);
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(reader);
        SqlSession session = sqlSessionFactory.openSession();

1 根据 配置文件或者注解,生成和数据库交互的必要的数据,存储于Map中,以供后续使用;
1.1 我们来看下 Reader reader = Resources.getResourceAsReader(resource);
resource 地址我是直接用的resrouces文件夹中的相对地址,作用就是根据地址加载配置文件信息,输出Reader;

  ClassLoader[] getClassLoaders(ClassLoader classLoader) {
    return new ClassLoader[]{
        classLoader,
        defaultClassLoader,
        Thread.currentThread().getContextClassLoader(),
        getClass().getClassLoader(),
        systemClassLoader};
  }

类加载器为上述5种,可以看出,以传入的classLoader和默认的defaultClassLoader为主,下面三个大家都很熟悉了,那么一般我们用第三个;这里用到的两个为 org.apache.ibatis.io.Resources(主要用来解析文件,还可以返回Class)和org.apache.ibatis.io.ClassLoaderWrapper(主要是涉及到类加载器的用途,无非是加载文件和加载类),
这里主要涉及到org.apache.ibatis.io包,看包名就知道 ,该包下面都是关于io的类
这里写图片描述
包中就这么几个类,其中vfs类(虚拟文件系统,用来读取服务器里的资源),提供了2个实现 JBoss6VFS 和 DefaultVFS,并提供了用户扩展点,可定义VFS实现;加载顺序: 自定义VFS实现 > 默认VFS实现 取第一个加载成功的
1.2 根据Reader解析成mybatis必须数据

    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(reader);
 1.2.1 好了,来看下SqlSessionFactoryBuilder 这个类,主要集中在build方法中,该方法提供了很多的重载方法,不一一说,重点说下下面的方法
  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

看出,不仅仅可以传reader,还可以自定义environment(jdbc连接条件)和properties(变量)这两个配置信息,可以看出 重点又在XMLConfigBuilder这个类中了

 public XMLConfigBuilder(Reader reader, String environment, Properties props) {
    this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
  }

XPathParser 解析类,主要封装 通过javax.xml.xpath.XPath来生成Document,具体怎么解析就不说了,不是本文重点;

  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

出现Configuration 这个类,上面讲到过 , MyBatis所有的配置信息都维持在Configuration对象之中。从这里才真正的开始解析配置文件至mybatis中;Configuration 默认构造函数中就注册了很多别名,其实就是放进map中,这种方式在很多地方都会见到;
1.2.2 parser.parse()
来看看 ,parse方法中发生了什么;

 public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
重点是parseConfiguration这个方法,可以看出,先从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);
    }
  }

很明显,所有的配置信息全部在改方法中生成,我们挑些讲讲;
1.2.2.1 propertiesElement(root.evalNode(“properties”));

     Properties defaults = context.getChildrenAsProperties();
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("url");

由上可知,属性变量有三种方式可以加载进来,节点子集、resource和url节点属性,其中resource和url只能存在一个,最终合并放进配置文件中

 parser.setVariables(defaults);
 configuration.setVariables(defaults);
    1.2.2.2
    typeAliasesElement(root.evalNode("typeAliases"));
    注册别名,简单点说就是以别名为key,class为value存放于对应的map中,以便后续用
    1.2.2.3 pluginElement(root.evalNode("plugins"));
    注册插件;先注册进别名,然后放入插件链中
    1.2.2.4 objectFactoryElement(root.evalNode("objectFactory"));
    类创建工程,该类作用仅仅是生成实例,默认是DefaultObjectFactory;我们可以实现我们自定义的工厂,实现ObjectFactory接口即可,可以用于类初始化的作用
    1.2.2.5 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    动态获取和设置属性的值,默认使用DefaultObjectWrapperFactory,mybatis基本考虑会很全,自定义的很少使用
    对象包装工程
    1.2.2.6 reflectorFactoryElement(root.evalNode("reflectorFactory"));
    反射工厂,功能很简单,就是生成一个反射配置数据,存储Reflector类(里面包含了该类涉及到方法、构造函数、字段、类型很全的一套反射信息,赋值、取值都可以通过他来操作。我们以后自己项目也可以直接拿来使用)数据,默认使用DefaultReflectorFactory类
      1.2.2.7 environmentsElement
      主要配置连接执行环境,里面包含了事务(JdbcTransactionFactory还有ManagedTransactionFactory,一般使用前者)及数据源(POOL、UNPOOL,JNDI之分,一般肯定选择池)的配置信息的生成;最终生成Environment;Environment这个类比较奇怪,里面实现了内部类Builder,但是内部类和外部类区别不大 ,何必呢。。
      1.2.2.8   typeHandlerElement(root.evalNode("typeHandlers"));
      类型转换器,该配置可以根据包名,进而解析整个包获取,也可以指定转换类,因为typeHandlerRegistry类中可以对包进行注册
      1.2.2.9    mapperElement(root.evalNode("mappers"));
          xml配置文件解析,同.1.2.2.8 可以对包(只能在mapper类同包名下才行),也可以其他方式
     String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");

由上可以看出是三种方式,其中数resource和url复杂,class是很简单的,
我们这里举resource的例子,这里涉及到XMLMapperBuilder类

            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();

好嘛,mapper.xml解析入口在这里了。好复杂。。
XMLMapperBuilder(解析mapper.xml配置信息类)和XmlConfigBuilder类似,都继承BaseBuilder,同样都有parse 解析方法,只是XMLMapperBuilder更复杂些,因为mapper.xml中的节点更多更复杂。

  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }
  private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }

从configurationElement方法可以看出,xml直接关联唯一mapper类,那么可以以此作为key,进而为后续调用获取配置信息打下基础。
MapperBuilderAssistant用于缓存、sql参数、查询返回的结果集处理。

SQL 映射文件有很少的几个顶级元素(按照它们应该被定义的顺序):

cache – 给定命名空间的缓存配置。可以配置
映射语句文件中的所有 select 语句将会被缓存。
映射语句文件中的所有 insert,update 和 delete 语句会刷新缓存。
缓存会使用 Least Recently Used(LRU,最近最少使用的)算法来收回。
根据时间表(比如 no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序 来刷新。
缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用。
缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而 且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
cache-ref – 其他命名空间缓存配置的引用。共用一个namespace缓存
resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
这里解析的resultMap中的数据,其中涉及到ResultMapping类,主要记录对应的表及实体类相关配置数据,存进resultMappings 中
parameterMap – 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。
sql – 可被其他语句引用的可重用语句块。
这块仅仅是将配置信息存入,而没有进一步去解析
insert – 映射插入语句
update – 映射更新语句
delete – 映射删除语句
select – 映射查询语句
上面四种都是一样处理,仅仅是类型不一样而已;
其中涉及到XMLStatementBuilder类,该类主要记录Statement相关的配置信息MappedStatement

好了 最麻烦的也处理完成了!!返回Configuration,所有的配置信息全在里面了

  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

返回DefaultSqlSessionFactory
接下来是下面这段代码

SqlSession session = sqlSessionFactory.openSession();
  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);
      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();
    }
  }

开启会话,来看看到底干什么了!
看代码,初始化了事务Transaction,根据例子,其实这里真实的对象应该是JdbcTransaction
也初始化了Executor ,mybatis的执行器

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

看代码,executor 最终被CachingExecutor装饰了,执行时,先执行CachingExecutor,再执行SimpleExecutor(我们例子里是simple)

  public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
  }

额,构造函数里又把CachingExecutor传递给SimpleExecutor了,这是要干啥,没看到什么用途,先不管,继续继续!
executor = (Executor) interceptorChain.pluginAll(executor);
将执行器放入插件链中,判断是否符合自定义插件类型,符合 则生成代理,则以后凡是到了执行器这里,则优先进入自定义插件执行!
这就是制作插件的原理,使用代理!
最终返回DefaultSqlSession,又是个Default
好了,会话成功开启!

总结:
mybatis配置阶段,使用了共享模式、装饰模式、代理模式、工厂模式、模板模式、外观模式。
插件的原理是使用代理(jdk代理(必须要有接口)或者cglib代理(类),两者性能都很高),用jdk的动态代理实现了拦截器和Mapper接口。这种动态代理和注解的运用也是非常值得学习的。
好吧!写的很烂,连图都没画!
下一篇准备mybatis执行部分解析

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值