Mybatis原始碼解讀系列(七)-SqlSessionFactory&SqlSession對Mybatis的初始化構架&SqlSession對Mapper介面的執行
技術標籤:學習筆記javaMybatis原始碼解析mybatis
一、SqlSessionFactory的建立
1、demo程式碼
Reader reader = Resources
.getResourceAsReader("org/apache/ibatis/submitted/maptypehandler/mybatis-config.xml")) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="org/mybatis/example/config.properties"/>
<typeHandlers>
<typeHandler handler="org.apache.ibatis.submitted.maptypehandler.LabelsTypeHandler" />
</typeHandlers>
<environments default="development" >
<environment id="development">
<transactionManager type="JDBC">
<property name="" value="" />
</transactionManager>
<dataSource type="UNPOOLED">
< property name="driver" value="org.hsqldb.jdbcDriver" />
<property name="url" value="jdbc:hsqldb:mem:maptypehandler" />
<property name="username" value="sa" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper class="org.apache.ibatis.submitted.maptypehandler.Mapper" />
</mappers>
</configuration>
2、demo程式碼說明
1)、可以看到這裡是先通過resource目錄地址將對應關於mybatis初始化的xml檔案讀取為一個Reader流物件。
2)、然後再是SqlSessionFactoryBuilder物件的建立在呼叫build方法,入參是配置檔案的讀入流。
3)、build方法
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
......
}
}
二、SqlsessionFactory的構建過程原始碼分析
1、XMLConfigBuilder的建立
public class XMLConfigBuilder extends BaseBuilder {
......
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
可以看到這裡首先是通過reader建立了一個XPathParser物件,再將其與入參environment、props呼叫this。
1)、XPathParser的建立
public class XPathParser {
private final Document document;
private boolean validation;
private EntityResolver entityResolver;
private Properties variables;
private XPath xpath;
public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(reader));
}
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
}
可以看到這裡這裡會將輸入流的內容讀取建立為一個Document物件。我們再看下entityResolver(XMLMapperEntityResolver)
2)、XMLMapperEntityResolver
/**
* Offline entity resolver for the MyBatis DTDs.
*
* @author Clinton Begin
* @author Eduardo Macarron
*/
public class XMLMapperEntityResolver implements EntityResolver {
private static final String IBATIS_CONFIG_SYSTEM = "ibatis-3-config.dtd";
private static final String IBATIS_MAPPER_SYSTEM = "ibatis-3-mapper.dtd";
private static final String MYBATIS_CONFIG_SYSTEM = "mybatis-3-config.dtd";
private static final String MYBATIS_MAPPER_SYSTEM = "mybatis-3-mapper.dtd";
private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";
......
通過這個類的一些類變數即註釋可以看到,這個類是用來解析DTD檔案的(可以DTD檔案來約束XML檔案的節點的名稱、順序等)。
通過上面1)、2)我們可以初步判斷這個XPathParser類用來處理mybatis的初始配置的XML檔案的內容的。
3)、XMLConfigBuilder建構函式
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;
}
這裡先建立了一個Configuration物件。然後是將入參賦值。
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
......
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
這裡是將一些工廠建立類設定對應別名,用來以後根據別名建立對應物件。同時可以看到這裡將將全域性的variables設定到了Configurartion。
2、XMLConfigBuilder.parse()
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
首先通過 [ parsed = true ]標記這個解析流已經處理了,再通過parseConfiguration去解析xml檔案的<configuration>節點。起始就是對整個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);
}
}
這個方法我們這個系列的第3篇有梳理。
3、SqlSessionFactory的初始化
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
DefaultSqlSessionFactory的建立。
三、DefaultSqlSessionFactory
接下來我們來看下DefaultSqlSessionFactory,不過我們首先來開下DefaultSqlSessionFactory實現的介面SqlessionFactory
public interface SqlSessionFactory {
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);
Configuration getConfiguration();
}
這個介面的方法主要是兩個方面,建立獲取SqlSession&獲取Configuration。下面我們來看下沒有引數的openSession方法(這些方法都會調到DefaultSqlSessionFactory一個底層的openSessionFromDataSource方法)
1、openSession()
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
可以看到這裡的入參是通過getDefaultExecutorType來獲取ExecutorType,中間的null是TransactionIsolationLevel是事務管理級別,false是是否自動提交引數。
2、openSessionFromDataSource(…)
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();
}
}
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
if (environment == null || environment.getTransactionFactory() == null) {
return new ManagedTransactionFactory();
}
return environment.getTransactionFactory();
}
這裡是根據Envirement獲取事務工廠,再通過事務工廠去建立對應的事務,之後再根據執行器類別去建立對應的執行器,有這些再去建立對應的DefaultSqlSession。
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;
}
四、SqlSession介面
這個就是對應的sql會話的增刪改查相關的一些方法。
五、DefaultSqlSession
1、結構&成員變數&構造方法
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
private final boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
這裡根據名稱及全面 的介紹就能明白了,就不再贅敘了。
2、方法
1)、selectList(…)
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
可以看到這裡就是從configuration的mappedStatements(Map<String, MappedStatement> mappedStatements)獲取對應介面方法MappedStatement,再通過執行器的查詢方法去處理。
2)、update(String statement, Object parameter)
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
這裡與前面類似。
3、insert()&update()
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public int update(String statement) {
return update(statement, null);
}
六、Mapper介面代理物件的獲取
1、demo
@Test
void testGetNamesAndItemsLinked() {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
SPMapper spMapper = sqlSession.getMapper(SPMapper.class);
List<Name> names = spMapper.getNamesAndItemsLinked();
assertEquals(4, names.size());
......
}
}
這裡就是首先通過DefaultSqlSessionFactory獲取DefaultSqlSession,再通過DefaultSqlSession去獲取Mapper介面對應的代理物件
2、getMapper(Class<T> type)
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
可以看到這裡就是通過MapperProxyFactory代理工廠去建立對應的代理物件。
3、MapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -4724728412955527868L;
private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
private static final Constructor<Lookup> lookupConstructor;
private static final Method privateLookupInMethod;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
這裡就是對映代理,不過這裡中間有用到一些新特性,以及各種處理,為例不在這一篇增加新內容,我們跳過的一些細節。
1)、PlainMethodInvoker
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
super();
this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
}
然後是Mapper方法呼叫者,去處理對應的方法。這裡的MapperMethod就是對應待執行方法的內容描敘。再通過invoke去執行對應方法。
2)、MapperMethod
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
public static class MethodSignature {
private final boolean returnsMany;
private final boolean returnsMap;
private final boolean returnsVoid;
private final boolean returnsCursor;
private final boolean returnsOptional;
private final Class<?> returnType;
private final String mapKey;
private final Integer resultHandlerIndex;
private final Integer rowBoundsIndex;
private final ParamNameResolver paramNameResolver;
SqlCommand是用來描敘執行型別的種類的,然後MethodSignature通過成員變數的名稱就能理解。
3)、execute(SqlSession sqlSession, Object[] args)
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
......
}
return result;
}
這個execute方法就是通過sqlSession去執行具體的方法了。然後(case SELECT)這種型別我們可以其會有各種(execute…)型別,但最後還是會落到SqlSession中,而SqlSession則會再去呼叫Executor。
至此整個Mybatis原始碼解析系列的主要邏輯就結束了,之後可能再寫一些番外。例如之後複習學習設計模式的時候可以看下Spring&Mybatis這些框架對其的應用。