SqlSessionFactory構(gòu)建過程
在上一遍我們通過JDK的動態(tài)代理簡單實現(xiàn)了一個“mybatis框架”(mybatis源碼分析(一):自己動手寫一個簡單的mybaits框架)券盅,并分析了一下我們自己的框架還有那些問題需要解決避咆,帶著這些問題我們就可以去看一下mybatis的源碼。mybatis作為一個持久層框架是如何解決這些問題的静秆。
學習一個框架的基本步驟就是找到框架的源碼的入口牵素,然后打斷點一步一步看內(nèi)部的實現(xiàn)細節(jié)严衬。看官方的文檔mybatis的入口大概是這樣的:
每個基于 MyBatis 的應(yīng)用都是以一個 SqlSessionFactory 的實例為核心的笆呆。SqlSessionFactory 的實例可以通過 SqlSessionFactoryBuilder 獲得请琳。而 SqlSessionFactoryBuilder 則可以從 XML 配置文件或一個預(yù)先配置的 Configuration 實例來構(gòu)建出 SqlSessionFactory 實例。 既然有了 SqlSessionFactory赠幕,顧名思義俄精,我們可以從中獲得 SqlSession 的實例。SqlSession 提供了在數(shù)據(jù)庫執(zhí)行 SQL 命令所需的所有方法榕堰。你可以通過 SqlSession 實例來直接執(zhí)行已映射的 SQL 語句竖慧。
也就是說嫌套,使用mybatis首先需要構(gòu)建出SqlSessionFactory,然后通過SqlSessionFactory獲得SqlSession實例圾旨,再通過SqlSession就可以操作數(shù)據(jù)庫了踱讨。
所以mybatis的入口是SqlSession,那么我們首先需要了解SqlSessionFactory是怎么構(gòu)建的砍的,SqlSession又是怎么被創(chuàng)建出來的痹筛,我們通過代碼逐步分析一下。
構(gòu)建SqlSessionFactory代碼:
public SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//1廓鞠、拿到全局配置
TransactionFactory transactionFactory = new JdbcTransactionFactory();
//準備環(huán)境信息
Environment environment = new Environment("development", transactionFactory, dataSource);
//2帚稠、使用我們的數(shù)據(jù)源
sqlSessionFactory.getConfiguration().setEnvironment(environment);
//使用mybatis-config.xml + Spring的數(shù)據(jù)源
return sqlSessionFactory;
}
我這里是用mybatis配置文件構(gòu)建的SqlSessionFactory,當然也可以用其他方法床佳。接下來我們看一下SqlSessionFactory在構(gòu)建的過程中都做了些什么滋早。
核心是這一行代碼:
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
mybatis的源碼:
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);
}
SqlSessionFactory子類DefaultSqlSessionFactory的構(gòu)造方法:
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
.....
}
我們可以看到SqlSessionFactory構(gòu)造方法非常簡單,創(chuàng)建一個DefaultSqlSessionFactory夕土,而DefaultSqlSessionFactory實例在實例化的時候需要創(chuàng)建了一個Configuration實例馆衔,而Configuration實例是通過XMLConfigBuilder實例的parse()方法得到的。
那么重點就是Configuration怨绣,我們來看一下SqlSessionFactory的Configuration是個什么東西。
public class Configuration {
protected Environment environment;
protected boolean useGeneratedKeys;
protected boolean useColumnLabel = true;
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls;
protected boolean useActualParamName = true;
protected boolean returnInstanceForEmptyRow;
.....
protected Class<? extends Log> logImpl;
protected Class<? extends VFS> vfsImpl;
protected Class<?> defaultSqlProviderType;
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
protected Properties variables = new Properties();
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
protected boolean lazyLoadingEnabled = false;
protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
protected String databaseId;
protected Class<?> configurationFactory;
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
protected final InterceptorChain interceptorChain = new InterceptorChain();
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
......
}
我們可以看到Configuration里面全部都是mybatis框架本身的所有的配置以及各種組件的定義拷获,從Configuration中我們可以得到很多的信息篮撑,有“cacheEnabled”緩存的配置,有“l(fā)azyLoadingEnabled”懶加載的配置匆瓜,有“InterceptorChain”攔截器的配置等等等赢笨。
那么可以推測出mybatis會把所有的配置項提前構(gòu)建好,Configuration里面有什么mybatis這個框架就會支持什么驮吱。我們可以再順便看一下源碼里Configuration里面的配置項是如何賦值的茧妒,這些配置項的來源是什么。這樣我們就可以熟練的配置mybatis了左冬。
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
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);
}
}
看到這里就有一種很熟悉的感覺了桐筏,XNode就是XML的節(jié)點對象,而“mappers”拇砰,"properties"梅忌,"settings"這些東西都是mybatis官方給的xml配置項,mybatis會解析我們配置的mybatis.xml除破,然后通過xml的配置給Configuration對象賦值牧氮。
還有XMLConfigBuilder構(gòu)造方法里面有一個super(newConfiguration())調(diào)用父類的構(gòu)造方法。
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;
}
構(gòu)造方法會去賦值typeAliasRegistry和typeHandlerRegistry這兩個屬性瑰枫。
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
typeAliasRegistry和typeHandlerRegistry這兩個屬性的值是在Configuration里面寫死的踱葛。
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
registerAlias("integer", Integer.class);
registerAlias("double", Double.class);
registerAlias("float", Float.class);
registerAlias("boolean", Boolean.class);
......
}
public TypeHandlerRegistry(Configuration configuration) {
this.unknownTypeHandler = new UnknownTypeHandler(configuration);
register(Boolean.class, new BooleanTypeHandler());
register(boolean.class, new BooleanTypeHandler());
register(JdbcType.BOOLEAN, new BooleanTypeHandler());
register(JdbcType.BIT, new BooleanTypeHandler());
register(Byte.class, new ByteTypeHandler());
register(byte.class, new ByteTypeHandler());
register(JdbcType.TINYINT, new ByteTypeHandler());
......
}
可以總結(jié)一下,Configuration的配置項來源于xml配置和mybatis自己定義的部分,這些配置在SqlSessionFactory構(gòu)建的時候就會全部初始化好尸诽。
例如我們的mybatis-config.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>
<mappers>
<mapper resource="mapper/DistrictMapper.xml"/>
</mappers>
</configuration>
就會得到如下的賦值:
同時mybatis還會加載我們在mapper.xml里定義的sql方法甥材,并把他封裝在了mappedStatements和loadedResources里了。
SqlSession構(gòu)建過程
那么構(gòu)建SqlSessionFactory的流程就大概整明白了逊谋,我們接下來看一下SqlSession是怎么被創(chuàng)建出來的擂达。
先寫一段測試代碼:
@Test
public void testMyBatis() throws IOException {
//1、得到 SqlSessionFactory
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
//2胶滋、得到 sqlSession ,代表和數(shù)據(jù)庫一次回話
SqlSession sqlSession = sqlSessionFactory.openSession();
//3板鬓、得到真正操作數(shù)據(jù)庫的Dao
DistrictMapper mapper = sqlSession.getMapper(DistrictMapper.class);
District district = mapper.getById(1);
System.out.println(district);
sqlSession.close();
}
先看這一句:
SqlSession sqlSession = sqlSessionFactory.openSession();
mybaits的源碼是這樣的:
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
@Override
public SqlSession openSession(ExecutorType execType) {
return openSessionFromDataSource(execType, null, false);
}
@Override
public SqlSession openSession(TransactionIsolationLevel level) {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), level, false);
}
@Override
public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
return openSessionFromDataSource(execType, level, false);
}
.....
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
可以看到openSession是一個重載的方法,它們都會通過openSessionFromDataSource去創(chuàng)建openSession究恤,而openSessionFromDataSource接受三個參數(shù):ExecutorType(執(zhí)行器類型)俭令,TransactionIsolationLevel(數(shù)據(jù)庫隔離級別),autoCommit(是否自動提交)部宿。
用戶可以通過重載的方法創(chuàng)建不同的SqlSession抄腔,我們使用的是無參的openSession方法,ExecutorType會被賦值成默認的ExecutorType.SIMPLE類型理张。
再接著看赫蛇,openSessionFromDataSource這三段代碼。
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
我們通過前面配置好的configuration拿到environment雾叭,又通過environment 拿到我們已經(jīng)創(chuàng)建好的TransactionFactory(事務(wù)工廠)悟耘,再通過事務(wù)工廠拿到Transaction對象。
通過源碼可以看到這里也是僅僅創(chuàng)建了一個對象织狐,并沒有其他操作暂幼。
@Override
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
return new JdbcTransaction(ds, level, autoCommit);
}
然后重點是這個代碼:
final Executor executor = configuration.newExecutor(tx, execType);
Executor執(zhí)行器,這個名字很核心然后我們看一下Executor是個什么東西移迫。
根據(jù)參數(shù)可以看到openSession方法給我們創(chuàng)建了一個SimpleExecutor執(zhí)行器旺嬉,除此之外我們還可以創(chuàng)建BatchExecutor、ReuseExecutor厨埋、CachingExecutor邪媳。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
然后可以看到SimpleExecutor就是真正去執(zhí)行Sql語句的組件,并且繼承BaseExecutor揽咕。而BaseExecutor幾乎實現(xiàn)了操作數(shù)據(jù)庫的所有方法悲酷。
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
......
}
看到這里openSession的所有操作就看完了∏咨疲可以總結(jié)一下:
1.openSession通過之前構(gòu)建好的Configuration實例拿到了Transaction對象
2.通過transaction對象和ExecutorType類型給我們創(chuàng)建好了一個數(shù)據(jù)庫的執(zhí)行器Executor设易,默認的Executor是SimpleExecutor
3.通過Executor創(chuàng)建了一個DefaultSqlSession實例給我們
可以畫個時序圖出來:
我們可以看到,到這一步mybatis都沒開始連接數(shù)據(jù)庫蛹头。而是幫我們把一切東西都準備好顿肺,其中最核心的東西就是SimpleExecutor戏溺,接下來應(yīng)該就是我們操作數(shù)據(jù)庫的主角SimpleExecutor要登場了。
下面一篇文章我將帶著大家一起學習mybatis核心組件Executor的執(zhí)行過程屠尊,希望可以給你帶來收獲旷祸。
本文涉及的源碼放在github上: