Mybatis源码分析-配置模块

来源:互联网 发布:管家婆备份数据库布 编辑:程序博客网 时间:2024/05/17 23:46

楼主比较菜,肯定有很多说的不对的地方,主要还是写给自己看的!!
比起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执行部分解析

原创粉丝点击