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}
原文地址:https://www.cnblogs.com/zhengzuozhanglina/p/11270756.html