mybatis源码体系介绍配置文件解析及源码解析

传统JDBC和Mybatis相比的弊病

1.数据库连接创建,释放频繁造成西戎资源的浪费,从而影响系统性能,使用数据库连接池可以解决问题。

2.sql语句在代码中硬编码,造成代码的不已维护,实际应用中sql的变化可能较大,sql代码和java代码没有分离开来维护不方便。

3.使用preparedStatement向有占位符传递参数存在硬编码问题因为sql中的where子句的条件不确定,同样是修改不方便

4.对结果集中解析存在硬编码问题,sql的变化导致解析代码的变化,系统维护不方便。

5、JDBC没有提供缓存,增加了数据库压力。

mybatis整体架构图

 mybatis功能架构分为三层:

API接口层:

提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层完成具体的数据处理。其核心是SqlSession接口。

数据处理层:

负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次工作。

配置解析流程:

 在Mybatis初始化过程中,会加载mybatis-config.xml配置文件、映射文件以及Mapper接口中的信息,解析后的配置信息会形成相应的对象并保存到Configuration对象中。利用该configuration对象创建SqlSessionFactory对象。待Mybatis初始化之后。开发人员可以通过初始化得到SqlSessionFactory创建SqlSession对象并完成数据库操作。

Configuration对象

是一个所有配置型的的容器对象。

SQL解析(sqlsource)

对应的是scripting模块。Mybatis中的scripting模块,会根据用户传入的实参,解析映射文件中定义的SQL节点,并形成数据库可执行的SQL语句。之后会处理SQL语句中的占位符,绑定用户传入的实参

负责根据用户传递的parameterObject,动态生成SQL语句,将信息封装到BoundSql对象中并返回

 SQL执行(executor)

基础支撑层:

负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取成最基础的组件。为上层的数据处理层提供最基础的支撑。

一个mybatis简单的例子


1
2 /***
3 * 
4 * 
5 */
6 public class App {
7 public static void main(String[] args) {
8 String resource = "mybatis‐config.xml";
9 Reader reader;
10 try {
11 //将XML配置文件构建为Configuration配置类
12 reader = Resources.getResourceAsReader(resource);
13 // 通过加载配置文件流构建一个SqlSessionFactory DefaultSqlSessionFactory
14 SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
15 // 数据源 执行器 DefaultSqlSession
16 SqlSession session = sqlMapper.openSession();
17 try {
18 // 执行查询 底层执行jdbc
19 //User user = (User)session.selectOne("com.tuling.mapper.selectById", 1);
20
21 UserMapper mapper = session.getMapper(UserMapper.class);
22 System.out.println(mapper.getClass());
23 User user = mapper.selectById(1L);
24 System.out.println(user.getUserName());
25 } catch (Exception e) {
26 e.printStackTrace();
27 }finally {
28 session.close();
29 }
30 } catch (IOException e) {
31 e.printStackTrace();
32 }
33 }
34 }

mybatis执行分为以下四个步骤:

1、执行从配置文件(通常是XML文件)得到SessionFactory;

2、从SessionFactory得到SqlSession;

3、通过SqlSession进行CRUD和事务的操作;

4、执行完相关操作之后关闭Session。

mybatis启动流程分析:

//将XML配置文件构建为Configuration配置类
            reader = Resources.getResourceAsReader(resource);
            // 通过加载配置文件流构建一个SqlSessionFactory  DefaultSqlSessionFactory
            SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);

加载配置文件,并且解析配置文件,把配置文件的内容解析到configuration对象中。

mybatis中有很多build方法和xxxbuild类,是因为mybatis用到了build模式来创建对象。build模式是指创建对象的过程比较复杂,不能再一个简单的构造方法中完成,就需要用到build模式。

通过上面代码发现,创建SqlSessionFactory的代码在SqlSessionFactoryBuilder中,进去一探究竟: 

///整个过程就是将配置文件解析成Configration对象,然后创建SqlSessionFactory的过程
  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.
      }
    }
  }
// 到这里配置文件已经解析成了Configuration
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

在上面代码中的

XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);

 其实内部是创建了一个XMLParser对象

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

 在XMLParser对象中有以下的这些属性:

private final Document document;
private boolean validation;
private EntityResolver entityResolver;
private Properties variables;
private XPath xpath;

其中xpath就是Java提供用来解析XML文件,而xml的内容都放在了document对象中,而解析document文件得到的信息都是放在node对象中。

 xml文件中的每个节点都是用一个node对象表示,所以解析配置文件都是对node对象操作。比如可以获得节点的名字、值、以及下面的子节点

 builder.append(node.getNodeName());
        NamedNodeMap attributeNodes = node.getAttributes();
        if (attributeNodes != null) {
            for (int i = 0; i < attributeNodes.getLength(); i++) {
                Node attribute = attributeNodes.item(i);
                String value = PropertyParser.parse(attribute.getNodeValue(), null);
                //System.out.println(attribute.getNodeName()+":"+ value);
                builder.append(" ");
                builder.append(attribute.getNodeName());
                builder.append("=\"");
                builder.append(value);
                builder.append("\"");
            }
        }
        NodeList nodeList = node.getChildNodes();

得到XMLConfigBuilder对象之后就去执行 parse()方法,去解析XML文件:

先判断是否解析过,如果已经解析过就抛异常

public Configuration parse() {
    /**
     * 若已经解析过了 就抛出异常
     */
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    /**
     * 设置解析标志位
     */
    parsed = true;
    /**
     * 解析我们的mybatis-config.xml的
     * 节点
     * <configuration>
     *
     *
     * </configuration>
     */
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

具体执行的是parseConfiguration():XNode是经过Mybatis封装的node,XNode中有toString方法可以输出当前节点说封装的内容

private void parseConfiguration(XNode root) {
    try {
      /**
       * 解析 properties节点
       *     <properties resource="mybatis/db.properties" />
       *     解析到org.apache.ibatis.parsing.XPathParser#variables
       *           org.apache.ibatis.session.Configuration#variables
       */
      propertiesElement(root.evalNode("properties"));
      /**
       * 解析我们的mybatis-config.xml中的settings节点
       * 具体可以配置哪些属性:http://www.mybatis.org/mybatis-3/zh/configuration.html#settings
       * <settings>
            <setting name="cacheEnabled" value="true"/>
            <setting name="lazyLoadingEnabled" value="true"/>
           <setting name="mapUnderscoreToCamelCase" value="false"/>
           <setting name="localCacheScope" value="SESSION"/>
           <setting name="jdbcTypeForNull" value="OTHER"/>
            ..............
           </settings>
       *
       */
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      /**
       * 基本没有用过该属性
       * VFS含义是虚拟文件系统;主要是通过程序能够方便读取本地文件系统、FTP文件系统等系统中的文件资源。
         Mybatis中提供了VFS这个配置,主要是通过该配置可以加载自定义的虚拟文件系统应用程序
         解析到:org.apache.ibatis.session.Configuration#vfsImpl
       */
      loadCustomVfs(settings);
      /**
       * 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
       * SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
       * 解析到org.apache.ibatis.session.Configuration#logImpl
       */
      loadCustomLogImpl(settings);
      /**
       * 解析我们的别名
       * <typeAliases>
           <typeAlias alias="Author" type="cn.tulingxueyuan.pojo.Author"/>
        </typeAliases>
       <typeAliases>
          <package name="cn.tulingxueyuan.pojo"/>
       </typeAliases>
       解析到oorg.apache.ibatis.session.Configuration#typeAliasRegistry.typeAliases
       */
      typeAliasesElement(root.evalNode("typeAliases"));
      /**
       * 解析我们的插件(比如分页插件)
       * mybatis自带的
       * Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
         ParameterHandler (getParameterObject, setParameters)
         ResultSetHandler (handleResultSets, handleOutputParameters)
         StatementHandler (prepare, parameterize, batch, update, query)
        解析到:org.apache.ibatis.session.Configuration#interceptorChain.interceptors
       */
      pluginElement(root.evalNode("plugins"));

      /**
       * todo
       */
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      // 设置settings 和默认值
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631

      /**
       * 解析我们的mybatis环境
         <environments default="dev">
           <environment id="dev">
             <transactionManager type="JDBC"/>
             <dataSource type="POOLED">
             <property name="driver" value="${jdbc.driver}"/>
             <property name="url" value="${jdbc.url}"/>
             <property name="username" value="root"/>
             <property name="password" value="Zw726515"/>
             </dataSource>
           </environment>

         <environment id="test">
           <transactionManager type="JDBC"/>
           <dataSource type="POOLED">
           <property name="driver" value="${jdbc.driver}"/>
           <property name="url" value="${jdbc.url}"/>
           <property name="username" value="root"/>
           <property name="password" value="123456"/>
           </dataSource>
         </environment>
       </environments>
       *  解析到:org.apache.ibatis.session.Configuration#environment
       *  在集成spring情况下由 spring-mybatis提供数据源 和事务工厂
       */
      environmentsElement(root.evalNode("environments"));
      /**
       * 解析数据库厂商
       *     <databaseIdProvider type="DB_VENDOR">
                <property name="SQL Server" value="sqlserver"/>
                <property name="DB2" value="db2"/>
                <property name="Oracle" value="oracle" />
                <property name="MySql" value="mysql" />
             </databaseIdProvider>
       *  解析到:org.apache.ibatis.session.Configuration#databaseId
       */
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      /**
       * 解析我们的类型处理器节点
       * <typeHandlers>
            <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
          </typeHandlers>
          解析到:org.apache.ibatis.session.Configuration#typeHandlerRegistry.typeHandlerMap
       */
      typeHandlerElement(root.evalNode("typeHandlers"));
      /**
       * 最最最最最重要的就是解析我们的mapper
       *
       resource:来注册我们的class类路径下的
       url:来指定我们磁盘下的或者网络资源的
       class:
       若注册Mapper不带xml文件的,这里可以直接注册
       若注册的Mapper带xml文件的,需要把xml文件和mapper文件同名 同路径
       -->
       <mappers>
          <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
          <mapper class="com.tuling.mapper.DeptMapper"></mapper>


            <package name="com.tuling.mapper"></package>
          -->
       </mappers>
       * package 1.解析mapper接口 解析到:org.apache.ibatis.session.Configuration#mapperRegistry.knownMappers
                 2.
       */
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

在执行parseConfiguration()过程中会执行具体的方法去解析不同的组件例如解析properties属性到configuration的variables属性里面去:

private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
      Properties defaults = context.getChildrenAsProperties();
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("url");
      if (resource != null && url != null) {
        throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
      }
      if (resource != null) {
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
      Properties vars = configuration.getVariables();
      if (vars != null) {
        defaults.putAll(vars);
      }
      parser.setVariables(defaults);
      configuration.setVariables(defaults);
    }
  }

以及最重要的是解析mapper属性,mapper的配置方式一共有四种,在源码中可以看到分别是package、resource、url、class

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      /**
       * 获取我们mappers节点下的一个一个的mapper节点
       */
      for (XNode child : parent.getChildren()) {
        /**
         * 判断我们mapper是不是通过批量注册的
         * <package name="com.tuling.mapper"></package>
         */
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          /**
           * 判断从classpath下读取我们的mapper
           * <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
           */
          String resource = child.getStringAttribute("resource");
          /**
           * 判断是不是从我们的网络资源读取(或者本地磁盘得)
           * <mapper url="D:/mapper/EmployeeMapper.xml"/>
           */
          String url = child.getStringAttribute("url");
          /**
           * 解析这种类型(要求接口和xml在同一个包下)
           * <mapper class="com.tuling.mapper.DeptMapper"></mapper>
           *
           */
          String mapperClass = child.getStringAttribute("class");

          /**
           * 我们得mappers节点只配置了
           * <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
           */
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            /**
             * 把我们的文件读取出一个流
             */
            InputStream inputStream = Resources.getResourceAsStream(resource);
            /**
             * 创建读取XmlMapper构建器对象,用于来解析我们的mapper.xml文件
             */
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            /**
             * 真正的解析我们的mapper.xml配置文件(说白了就是来解析我们的sql)
             */
            mapperParser.parse();
          } 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();
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值