Mybatis源码分析一SqlSessionFactory

博客摘抄自  https://www.cnblogs.com/jeffen/p/6274070.html  

 

简介

 

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

 

1. 加入pom

2. 我这里使用的是idea一定记得把自己文件夹要makedirectas  source-root

       

  因为只有这样,ClassLoader才能找到你这个文件夹路径下的配置文件啊。

  测试这个说法的代码如下 : 

  

public class TestMybatis {

    public static  void main(String[] args) throws Exception {
        SqlSessionFactory sessionFactory = null;
        System.out.println(ClassLoader.getSystemResource("configuration.xml"));
        String resource = "/MyLxfMybatisTest/configuration.xml";
        /*sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource));   //从类路径中加载资源,
        SqlSession sqlSession = sessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        Map<String,Object> map = userMapper.findUserById(1);
        for(String tempKey : map.keySet()){
            System.out.println(map.get(tempKey));
        }*/
    }

}

  首先我们创建 【MyLxfMybatisTest 】 文件夹,然后不改变文件夹的属性,我们接着创建 【configuration.xml】 文件,运行我上面的代码,打印的是null,而我们

使用 idea 的 make direact as source 之后,我们这个里面的文件才能被类加载器加载到。

3. 全局看一下我写的代码路径位置 : 

  

  创建了 MyLxfMybatisTest 文件夹,变味了 source 文件夹,里面创建了 configuration.xml 文件,此文件主要存放 mybatis的一些配置信息:包括数据源、settings、映射文件,

       后面会详细介绍,UserMapper这个接口就是一般的 Mapper的java类,resource.properties 就只是存放数据源连接信息,还有一个 mapper.xml的文件一定要记得放在 文档结构的

【resource】文件夹下 ,否则  configuration.xml 当中写的 mapper 标签很可能关联找不到 要扫描的mapper.xml文件 就会导致 SqlSessionFactory 构建 Configuration 信息失败,

从而整个项目都不能运行。

  UserMapper.xml 存放路径 : 

  

 额外补充 : 若是 我们不放在 resouce文件夹下改怎么做到 能匹配到呢? 

  我们可以借鉴一篇博客 : 

  https://blog.csdn.net/gugou123/article/details/80514823  

  这篇博客当中提到了 在pom文件中加入节点告诉maven我们还有别的xml在src/main/java下面,也要一起打包,这就是第一个resource节点的含义:

  

                        <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>

  

      但如果只加了这个节点,maven就会认为所有的配置文件都在src/main/java下而直接忽略src/main/resources下的所有配置文件,这就导致我们启动的时候直接读取不到配置文件,而且resources文件还被maven很不待见的扔进了包视图中下面src/main/下,为了避免这种情况发生,我们还需要加上一个节点:
  

            <resource>
                <directory>src/main/resources</directory>
            </resource>

  

  这样maven就会同时扫描src/main/java/和src/main/resources下的所有配置文件及xml映射文件了。

4. 每个文件所写代码如下 :

  UserMapper.xml : 

  

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="UserMapper">
    <select id="findUserById" parameterType="int" resultType="Map">
        select user_id,USER_NAME,CREATION_DATE from fnd_user where _id=#{id,jdbcType=INTEGER}
    </select>
</mapper>
UserMapper.xml

  resource.properties

  

# 数据库配置文件
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/form?serverTimezone=UTC&useSSL=true&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&rewriteBatchedStatements=true
jdbc.userName=root
jdbc.password=root
# 我这里连接的是form本地库
resource.properties

  UserMapper.java

  

import org.apache.ibatis.annotations.Mapper;

import java.util.Map;

/**
 * @Auther: pccw
 * @Date: 2018/11/7 10:19
 * @Description:
 */
@Mapper
public interface UserMapper {

    public Map<String,Object> findUserById(int id);

}
UserMapper.java

  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>
    <!-- 注意dtd 是引用mybatis的,而不是sprinh的   -->

    <!-- 指定properties配置文件, 我这里面配置的是数据库相关 ,注意 文件夹路径,我这里存放的是同级的这里  -->
    <properties resource="resource.properties"></properties>

    <!-- 指定Mybatis使用log4j -->
    <settings>
        <setting name="logImpl" value="LOG4J" />
    </settings>

    <environments default="development">
        <!-- 可以配置多个environment 来达到多数据源的配置,或者说能达到 生产、测试、开发环境的切换  -->
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <!-- 上面指定了数据库配置文件, 配置文件里面也是对应的这四个属性 一定要跟resource.properties当中的属性对应上 -->
                <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精髓, 后面才会细讲  ,扫描mybatis的mapper映射文件,一定要放在resource文件夹下 -->
    <!--  mapper resource= 的值 必须是resource文件夹下的路径值  -->
    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>
configuration.xml

  测试主类 : 

  

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.util.Map;

/**
 * @Auther: pccw
 * @Date: 2018/11/7 10:27
 * @Description:
 *    测试自己写的mybatis方法
 */
public class TestMybatis {

    public static  void main(String[] args) throws Exception {
        SqlSessionFactory sessionFactory = null;
        // System.out.println(ClassLoader.getSystemResource("configuration.xml"));
        String resource = "configuration.xml";
        sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource));   //从类路径中加载资源
        // sessionFactory.getConfiguration().getMapperRegistry().addMapper(UserMapper.class);  
        // 已经在configuration.xml 文件当中写了,不需要重复注册
        SqlSession sqlSession = sessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        Map<String,Object> map = userMapper.findUserById(1);
        for(String tempKey : map.keySet()){
            System.out.println(map.get(tempKey));
        }
    }

}

  测试效果 : 

  

 

 5. 总结项目demo 

  demo很简单,运行原来也很透明,其主要的就是写一个mybatis的配置文件 里面有 各种配置说明,然后我们一定要注意配置文件一定要扫描到 mapper.xml文件,

  然后编写好 mapper.xml 文件,mapper的java文件 ,最后我们就来通过一定的机制来解析此xml,把此xml变为 Configuration 类,最后被SqlSessionFactoryBuilder.build,

  这样就生成了 sqlSessionFactory 工厂,我们再直接 openSession 就打开了一次数据库会话,然后使用session.getMapper得到mapper的接口,然后直接就可以调用接口

  里面的方法进行数据库操作了,其底层是通过MethodProxy 代理来实现的。

 

6. 源码分析 : 

  copy 自 https://www.cnblogs.com/jeffen/p/6274070.html 

  在mybatis的配置文件中:

  6.1 configuration节点为根节点。 

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

  

  子节点的源码 : 

  6.2.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);
    }
  }
properties源码

  6.2.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")));
   }
Setting源码

  有关全局设置说明

  

  6.2.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里面
   }
typeAliases 源码

  6.2.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里面
        }
      }
    }
  }
environments源码

  6.2.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.");
         }
       }
     }
   }
 }
Mapper映射源码

   以上是几个常用的配置文件元素源码分析,其它的不在这里过多介绍。

posted @ 2018-11-07 15:15  白云是世界的公民  阅读(326)  评论(0)    收藏  举报