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
这里采用两种方式进行构建,SqlSessionFactoryBuilder和SqlsessionManager
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().这两个方法的执行顺序如下步骤。
- 获取configuration类中的运行环境信息,通过环境信息获取一个事务工厂,如果未设置环境信息,则使用默认的受管事务工厂(ManagedTransactionFactory),否则使用指定的事务工厂
- 从事务工厂(TransactionFactory)实例化出一个事务对象Transaction
- 根据指定的执行器类型实例化对应的执行器,如果未指定,则使用默认的Simple执行器
- 实例化一个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}