zoukankan      html  css  js  c++  java
  • Mybatis源码分析:SqlSessionFactory

    SqlSessionFactory讲解

      SqlSessionFactory通过类名可以看出其作用是通过工厂模式创建SqlSession,那么如何通过工厂模式创建Sqlsession呢?抛开Sqlsession,先看看SqlSessionFactory的类结构和相关的方法。SqlSessionFactory实现了有两个子类分别是DefaultSqlSessionFactory和SqlSessionManager。其中DefaultSqlSessionFactory是构建Sqlsession最常用的一个类,可以直接通过构造器方式进行实例化,也可以使用mybatis提供的SqlSessionFactoryBuilder来构建一个SqlSessionFactory。SqlSessionManager更像是一个组合体,不仅仅实现了SqlSessionFactory,同时实现了SqlSession接口,其包含了三个属性sqlSessionFactory,sqlSessionProxy,localSqlSession,在完成SqlSession的构建后,可以完成SqlSession的CURD功能,说白了,SqlsessionManager集合了SqlSessionFactory和SqlSession的功能。
       SqlSessionFactory重载了多个openSession()方法,其目的就是希望实例化出一个Sqlsession,并且通过不同的参数形式构建出不同功能的Sqlsession,从下面的方法可以看出,可以为即将实例化出的SqlSession设置事务隔离等级,执行器类型,是否自动提交。

    • SqlSession openSession();
    • SqlSession openSession(boolean autoCommit);
    • SqlSession openSession(Connection connection);
    • SqlSession openSession(TransactionIsolationLevel level);
    • SqlSession openSession(ExecutorType execType);
    • SqlSession openSession(ExecutorType execType, boolean autoCommit);
    • SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
    • SqlSession openSession(ExecutorType execType, Connection connection);

    构建一个SqlSessionFactory

    这里采用两种方式进行构建,SqlSessionFactoryBuilderSqlsessionManager

    SqlSessionFactoryBuilder

       SqlSessionFactoryBuilder采用建造者模式进行构建,观察实现方法,可以看到重载了多个build()方法,通过不同的入参方式或者参数组合方式实现。大体上可以看到三种方式进行build。通过输入流InputStream,读入器Reader,配置类 Configuration。除了这三种入参,还提供了环境参数environment用于选择当前的后端存储环境和额外的资源属性properties,资源属性会防止在Configuration类中的variables属性中,用于为SqlSession和mybatis其他类提供相关的资源值。
       通过reader和inputStream方式构建SqlSessionFactory比较类似,都是先解析mybatis配置文件和Mapper配置文件。如果在解析期间出错,则抛出"Error building SqlSession."异常提示,在解析完成之后,会调用build(Configuration config)返回一个DefaultSqlSessionFactory实例。值得注意的是,一个SqlSessionFactoryBuilder实例只能加载一次配置,这是因为将配置文件以流的形式读入后,XMLConfigBuilder的parse()方法会进行打标,如果发现已经加载过了,则抛出BuilderException("Each XMLConfigBuilder can only be used once.")异常信息。其源码如下:

    • 源码1
       1 public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
       2  try {
       3    XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
       4    return build(parser.parse());
       5  } catch (Exception e) {
       6    throw ExceptionFactory.wrapException("Error building SqlSession.", e);
       7  } finally {
       8    ErrorContext.instance().reset();
       9    try {
      10      reader.close();
      11    } catch (IOException e) {
      12      // Intentionally ignore. Prefer previous error.
      13    }
      14  }
      15 }
    • 源码2

      public SqlSessionFactory build(Configuration config) {
         //获取默认的DefaultSqlSessionFactory
       return new DefaultSqlSessionFactory(config);
      }
    • 实例1

     1 package com.zzz.mybatis.service;
     2 
     3 import java.io.InputStream;
     4 import java.io.InputStreamReader;
     5 import java.util.Map.Entry;
     6 import java.util.Properties;
     7 import org.apache.ibatis.session.Configuration;
     8 import org.apache.ibatis.session.SqlSessionFactory;
     9 import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    10 import org.junit.Test;
    11 public class SqlSessionFactoryTest {
    12 @Test
    13 public void SqlSessionFactoryBuilderTest() {
    14     SqlSessionFactory sessionFactory;
    15     //通过InputStream方式构建
    16     InputStream inputStream=getClass().getResourceAsStream("/mybatis-config.xml");
    17     SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
    18     //buil完成之后会将inputStream进行关闭,所以下述方式必须重新读取配置文件
    19     sessionFactory=builder.build(inputStream,new Properties() 
    20                                                 {
    21                                                     {put("name", "zhangsan");}
    22                                                 }
    23                                 );
    24     //通过reader进行builder
    25     sessionFactory=builder.build(new InputStreamReader(getClass().getResourceAsStream("/mybatis-config.xml")),new Properties() 
    26                                 {
    27                                     {put("name", "zhangsan");}
    28                                 }
    29                                 );
    30     //获取外部的资源
    31     Properties properties=sessionFactory.getConfiguration().getVariables();
    32     for(Entry<Object, Object> entry:properties.entrySet()) {
    33         System.out.println("key:"+entry.getKey()+"--->"+"value:"+entry.getValue());
    34     }
    35     //通过configuration方式进行build
    36     Configuration configuration = new Configuration();
    37     sessionFactory=builder.build(configuration);
    38 }
    39 }
    SqlSessionFactoryBuilder

    除了使用Builder方式,还可以通过SqlSessionManager的形式构建,其内部包含三个属性sqlSessionFactory,sqlSessionProxy,localSqlSession。其中sqlSessionProxy采用了JDK自身的代理机制,使用代理进行方法调用,而localSqlSession则使用了LocalThread对SqlSession进行纳管。SqlSessionManager中构造方法是私有的,只能通过其提供的newInstance()方法进行实例化,传参形式跟SqlSessionFactoryBuilder.builder()方式没有任何区别,事实上,newInstance()内部就是通过实例化了一个SqlSessionFactoryBuilder来获取DefaultSqlSessionFactory实例,再调用自身的私有构造器进行实例化。

     1 @Test
     2     public void SqlSessionManagerTest() throws SQLException {
     3         //通过InputStream方式构建
     4         InputStream inputStream=getClass().getResourceAsStream("/mybatis-config.xml");
     5         SqlSessionManager sessionManager=SqlSessionManager.newInstance(inputStream);
     6         //该方式调用了openSession()方法,向localSqlSession写入一个SqlSession,后续可以直接进行CURD操作,其原理是通过动态代理进行调用
     7         sessionManager.startManagedSession();
     8         //调用DefaultSqlSessionFactory中的方法
     9         SqlSession session=sessionManager.openSession();
    10         sessionManager.close();
    11         System.out.println(sessionManager.isManagedSessionStarted());
    12     }

    DefaultSqlSessionFactory

    DefaultSqlSessionFactory用于创建一个SqlSession,在实例化的时候必须传入Confugration类,openSession()方法返回一个DefaultSqlSession实例,该方法实际上调用了openSessionFromDataSource()或者openSessionFromConnection().这两个方法的执行顺序如下步骤。

    1. 获取configuration类中的运行环境信息,通过环境信息获取一个事务工厂,如果未设置环境信息,则使用默认的受管事务工厂(ManagedTransactionFactory),否则使用指定的事务工厂
    2. 从事务工厂(TransactionFactory)实例化出一个事务对象Transaction
    3. 根据指定的执行器类型实例化对应的执行器,如果未指定,则使用默认的Simple执行器
    4. 实例化一个DefaultSqlSession,该实例需要Configuration,Executor,autoCommit作为入参。

    使用DefaultSqlSessionFactory构造一个DefaultSqlSession实例核心代码如下

     1  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
     2     Transaction tx = null;
     3     try {
     4       final Environment environment = configuration.getEnvironment();
     5       final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
     6       tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
     7       final Executor executor = configuration.newExecutor(tx, execType);
     8       return new DefaultSqlSession(configuration, executor, autoCommit);
     9     } catch (Exception e) {
    10       closeTransaction(tx); // may have fetched a connection so lets call close()
    11       throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    12     } finally {
    13       ErrorContext.instance().reset();
    14     }
    15   }

    DefaultSqlSessionFactory 创建SqlSession的流程图

    测试代码如下

    mybatis-config.xml配置文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
      PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
      "./mybatis-3-config.dtd">
      <configuration>
          <!--引入外部资源  -->
          <properties resource="./mybatis-mysql.properties"> 
          </properties>
          <settings>
              <setting name="cacheEnabled" value="true"/>
          </settings>
          <objectFactory type="com.zzz.mybatis.object.MyObjectFacotry">
              <property name="env" value="test"/>
          </objectFactory>
          <!--设置默认的环境为开发环境  -->
            <environments default="test">
        <environment id="development">
          <transactionManager type="JDBC"/>
          <dataSource type="UNPOOLED">
            <property name="driver" value="${dev.driver}"/>
            <property name="url" value="${dev.url}"/>
            <property name="username" value="${dev.username}"/>
            <property name="password" value="${dev.password}"/>
          </dataSource>
        </environment>
        <!--测试环境用  -->
        <environment id="test">
            <transactionManager type="JDBC"/>
          <dataSource type="POOLED">
            <property name="driver" value="${test.driver}"/>
            <property name="url" value="${test.url}"/>
            <property name="username" value="${test.username}"/>
            <property name="password" value="${test.password}"/>
          </dataSource>
        </environment>
        <!--生产环境用  -->
        <environment id="prod">
            <transactionManager type="MANAGED"/>
          <dataSource type="JDBC">
            <property name="driver" value="${prod.driver}"/>
            <property name="url" value="${prod.url}"/>
            <property name="username" value="${prod.username}"/>
            <property name="password" value="${prod.password}"/>
          </dataSource>
        </environment>
      </environments>
      <mappers>
          <package name="com.zzz.mybatis.mapper"/>
      </mappers>
      </configuration>
    

    Mapper接口

    package com.zzz.mybatis.mapper;
    
    import java.util.List;
    import java.util.Map;
    
    import org.apache.ibatis.annotations.Flush;
    import org.apache.ibatis.annotations.MapKey;
    import org.apache.ibatis.annotations.Select;
    
    public interface SqlMapper {
        @Select("select id,name,age from student")
        public List<Map<String, Object>> list();
    
        @Flush
        public void flush();
    
        @MapKey(value="id")
        @Select("select id,name,age from student")
        public Map<String,Map<String,String>> listByMapkey();
    }
    

    测试代码

     1 @Test
     2 public void DefaultSqlSessionFacotryTest() {
     3 //通过InputStream方式构建
     4     InputStream inputStream=getClass().getResourceAsStream("/mybatis-config.xml");
     5     SqlSessionManager sessionManager=SqlSessionManager.newInstance(inputStream);
     6     //该方式调用了openSession()方法,向localSqlSession写入一个SqlSession,后续可以直接进行CURD操作,其原理是通过动态代理进行调用
     7     sessionManager.startManagedSession();
     8     //调用DefaultSqlSessionFactory中的方法
     9     SqlSession session=sessionManager.openSession();
    10     SqlMapper mapper=session.getMapper(SqlMapper.class);
    11     List<Map<String,Object>> rs=mapper.list();
    12     for(Map<String, Object> map:rs) {
    13         System.out.println(map.toString());
    14     }
    15 }

    测试结果

    {name=zhangsan, id=1, age=20}
    {name=lisi, id=2, age=30}
    {name=wangwu, id=3, age=40}
  • 相关阅读:
    T-SQL基础(1)
    shell命令--chage
    Oracle SQL Lesson (11)
    shell命令--chpasswd
    Oracle SQL Lesson (10)
    shell命令--passwd
    Oracle SQL Lesson (9)
    shell命令--usermod
    Spring中的工厂模式和单例模式
    Oracle SQL Lesson (8)
  • 原文地址:https://www.cnblogs.com/zhengzuozhanglina/p/11270756.html
Copyright © 2011-2022 走看看