zoukankan      html  css  js  c++  java
  • Mybatis源码分析之SqlSessionFactory(一)

    简介


    MyBatis的前身叫iBatis,本是apache的一个开源项目, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis。MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。MyBatis使用简单的XML或注解用于配置和原始映射,将接口和Java的POJOs(Plan Old Java Objects,普通的Java对象)映射成数据库中的记录。



    实验代码

    public static void main(String[] args) throws Exception {
            SqlSessionFactory sessionFactory = null;
            String resource = "configuration.xml";
            sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource));
            SqlSession sqlSession = sessionFactory.openSession();
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            System.out.println(userMapper.findUserById(1));
        }

        

    configuration.xml 

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
      PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    
    	<!-- 指定properties配置文件, 我这里面配置的是数据库相关 -->
    	<properties resource="resource.properties"></properties>
    
    	<!-- 指定Mybatis使用log4j -->
    	<settings>
    		<setting name="logImpl" value="LOG4J" />
    	</settings>
    
    	<environments default="development">
    		<environment id="development">
    			<transactionManager type="JDBC" />
    			<dataSource type="POOLED">
    				<!-- 上面指定了数据库配置文件, 配置文件里面也是对应的这四个属性 -->
    				<property name="driver" value="${jdbc.driverClass}" />
    				<property name="url" value="${jdbc.url}" />
    				<property name="username" value="${jdbc.userName}" />
    				<property name="password" value="${jdbc.password}" />
    			</dataSource>
    		</environment>
    	</environments>
    
    	<!-- 映射文件,mybatis精髓, 后面才会细讲 -->
    	<mappers>
    		<mapper resource="sqlmap/UserMapper.xml"/>
    	</mappers>
    </configuration>

    resource.properties


    jdbc.driverClass=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8
    jdbc.userName=root
    jdbc.password=root




    SqlSessionFactoryBuilder


    更加上面demo的main方法我们先来看下 SqlSessionFactoryBuilder,他的主要目的是创建一个SqlSessionFactory

    看下面源码:


    /**
     * Builds {@link SqlSession} instances.
     *
     * @author Clinton Begin
     */
    public class SqlSessionFactoryBuilder {
      public SqlSessionFactory build(Reader reader) {  //Reader读取mybatis配置文件 "configuration.xml";
        return build(reader, null, null);
      }
      public SqlSessionFactory build(Reader reader, String environment) {
        return build(reader, environment, null);
      }
      public SqlSessionFactory build(Reader reader, Properties properties) {
        return build(reader, null, properties);
      }
      //通过XMLConfigBuilder解析mybatis配置,然后创建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.
          }
        }
      }
      public SqlSessionFactory build(InputStream inputStream) {
        return build(inputStream, null, null);
      }
      public SqlSessionFactory build(InputStream inputStream, String environment) {
        return build(inputStream, environment, null);
      }
      public SqlSessionFactory build(InputStream inputStream, Properties properties) {
        return build(inputStream, null, properties);
      }
      public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
          XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
          return build(parser.parse());
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
          ErrorContext.instance().reset();
          try {
            inputStream.close();
          } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
          }
        }
      }
        
      public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
      }
    }


    通过源码,我们可以看到SqlSessionFactoryBuilder有不同的参数的build,通过XMLConfigBuilder 去解析我们传入的mybatis的配置文件,转化成Configuration,通过SqlSessionFactory build(Configuration config) 

    最终创建DefaultSqlSessionFactory,下面就接着看看 XMLConfigBuilder 部分源码


      // mybatis 配置文件解析
     public XMLConfigBuilder(Reader reader, String environment, Properties props) {
        this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
      }
      
      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;
      }
    //读取XMLConfigBuilder返回一个Configuration对象
     public Configuration parse() {
        if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        parseConfiguration(parser.evalNode("/configuration"));//xml的根节点
        return configuration;
      }
      //configuration下面能配置的节点为以下11个节点 reflectionFactory是后面版本新增的
      //不同子节点进行Element处理
      private void parseConfiguration(XNode root) {
        try {
          Properties settings = settingsAsPropertiess(root.evalNode("settings"));
          //issue #117 read properties first
          propertiesElement(root.evalNode("properties"));
          loadCustomVfs(settings);
          typeAliasesElement(root.evalNode("typeAliases"));
          pluginElement(root.evalNode("plugins"));
          objectFactoryElement(root.evalNode("objectFactory"));
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          reflectionFactoryElement(root.evalNode("reflectionFactory"));
          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);
        }
      }

    通过以上源码,我们就能看出,在mybatis的配置文件中:


    1. configuration节点为根节点。


    2. 在configuration节点之下,我们可以配置11个子节点, 分别为:properties、typeAliases、plugins、objectFactory、objectWrapperFactory、settings、environments、databaseIdProvider、typeHandlers、mappers、reflectionFactory(后面版本新增的)

    如下图:



    下面我们看下子节点的源码


    1:properties(相关配置读取)

     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) { //如果resource和url都为空就抛出异常
            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);
          }
    //把Properties defaults 设置到 parser.setVariables
          parser.setVariables(defaults);
    //把Properties defaults 设置到 configuration.setVariables
          configuration.setVariables(defaults);
        }
      }

      

    2:settings 全局性的配置


    private Properties settingsAsPropertiess(XNode context) {
        if (context == null) {
          return new Properties();
        }
        Properties props = context.getChildrenAsProperties();
        // Check that all settings are known to the configuration class
        MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
        for (Object key : props.keySet()) {
          if (!metaConfig.hasSetter(String.valueOf(key))) {
            throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
          }
        }
        return props;
      }
      
      //settings元素设置 元素比较多不懂的可以看下官方文档
      private void settingsElement(Properties props) throws Exception {
         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"), true));
         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.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.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
         configuration.setLogPrefix(props.getProperty("logPrefix"));
         configuration.setLogImpl(resolveClass(props.getProperty("logImpl"))); //这个是我们的配置文件里面设置的 日志
         configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
       }

       

    有关全局设置说明

    3:typeAliases  为一些类定义别名

      private void typeAliasesElement(XNode parent) {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
              String typeAliasPackage = child.getStringAttribute("name");
              configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
            } else {
              String alias = child.getStringAttribute("alias");
              String type = child.getStringAttribute("type");
              try {
                Class clazz = Resources.classForName(type);
                if (alias == null) {
                  typeAliasRegistry.registerAlias(clazz);
                } else {
                  typeAliasRegistry.registerAlias(alias, clazz);
                }
              } catch (ClassNotFoundException e) {
                throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
              }
            }
          }
        }
      }
      
      public void registerAlias(String alias, Class value) {
         if (alias == null) {
           throw new TypeException("The parameter alias cannot be null");
         }
         // issue #748
         String key = alias.toLowerCase(Locale.ENGLISH);
         if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
           throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
         }
         TYPE_ALIASES.put(key, value); //最终是放到map里面
       }

       

    4:environments Mybatis的环境


    //数据源  事务管理器 相关配置
      private void environmentsElement(XNode context) throws Exception {
        if (context != null) {
          if (environment == null) {
            environment = context.getStringAttribute("default"); //默认值是default
          }
          for (XNode child : context.getChildren()) {
            String id = child.getStringAttribute("id");
            if (isSpecifiedEnvironment(id)) {
       // MyBatis 有两种事务管理类型(即type=”[JDBC|MANAGED]”) Configuration.java的构造函数
              TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));//事物
              DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); //数据源
              DataSource dataSource = dsFactory.getDataSource();
              Environment.Builder environmentBuilder = new Environment.Builder(id)
                  .transactionFactory(txFactory)
                  .dataSource(dataSource);
              configuration.setEnvironment(environmentBuilder.build());  //放到configuration里面
            }
          }
        }
      }

      

     5: mappers 映射文件或映射类


     既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要定义 SQL 映射语句了。 但是, 首先我们需要告诉 MyBatis  到哪里去找到这些语句。 Java 在这方面没有提供一个很好 的方法, 所以最佳的方式是告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的  资源引用,或者字符表示,或 url 引用的完全限定名(包括 file:///URLs) 

     

     /

    /读取配置文件将接口放到configuration.addMappers(mapperPackage) 
     //四种方式 resource、class name url
     private void mapperElement(XNode parent) throws Exception {
       if (parent != null) {
         for (XNode child : parent.getChildren()) {
           if ("package".equals(child.getName())) {
             String mapperPackage = child.getStringAttribute("name");
             configuration.addMappers(mapperPackage);
           } else {
             String resource = child.getStringAttribute("resource");
             String url = child.getStringAttribute("url");
             String mapperClass = child.getStringAttribute("class");
             if (resource != null && url == null && mapperClass == null) {//demo是通过xml加载的
               ErrorContext.instance().resource(resource);
               InputStream inputStream = Resources.getResourceAsStream(resource);
               XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
               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.");
             }
           }
         }
       }
     }

     以上是几个常用的配置文件元素源码分析,其它的不在这里过多介绍,因为有些子元素本人也没有用到过。







  • 相关阅读:
    负数幅角的选取
    记一次py交易
    区间估计
    平方和
    正态总体 下常用结论
    每日一背
    乘积的期望
    java调用javascript
    Java Agent入门
    JavaPoet入门
  • 原文地址:https://www.cnblogs.com/jeffen/p/6274070.html
Copyright © 2011-2022 走看看