创建SqlSessionFactory
先读取xml配置文件,解析得到Configuration
,然后使用DefaultSqlSessionFactory
创建
# 读取xml配置文件
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
}
# 创建 SqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
Configuration.parse
保证配置文件只会解析一次
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
解析配置文件
重点看mapperElement(root.evalNode("mappers"))
,解析mapper
private void parseConfiguration(XNode root) {
try {
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);
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);
}
}
mappers
mappers节点可以写一个package或者一个mapper,其中mapper可以写class、url、resource中的一个。
package 会扫描包下所有class依次调用configuration.addMapper
<mappers>
<!-- 使用相对于类路径的资源引用 -->
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<!-- 使用完全限定资源定位符(URL) -->
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mapper class="org.mybatis.builder.BlogMapper"/>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<package name="org.mybatis.builder"/>
</mappers>
mapper
可以看出解析优先级是 resource > url > class
url和resource 最后都会调用XMLMapperBuilder.parse
class 则会直接调用configuration.addMapper
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
try(InputStream inputStream = Resources.getUrlAsStream(url)){
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
XMLMapperBuilder.parse
解析mapper前会检查当前resource是否解析过,isResourceLoaded函数是通过判断resource是否在一个set集合内,不在的话会解析该resource,然后addLoadedResource将resource加入集合。
(上面传入的url、resource)
解析成功后会调用bindMapperForNamespace()
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
configurationElement解析mapper.xml文件
mapper.xml中的namespace代表对应的接口类,必须存在。
该函数会依次解析一下节点
- cache-ref (引用其它命名空间的缓存配置)
- cache (缓存配置)
- /mapper/parameterMap (老式风格的参数映射、已被废弃)
- /mapper/resultMap (结果映射)
- /mapper/sql (可被其它语句引用的可重用语句块)
- select|insert|update|delete
重点看 buildStatementFromContext(context.evalNodes(“select|insert|update|delete”))
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
select|insert|update|delete
buildStatementFromContext
方法会调用buildStatementFromContext。
循环遍历所有节点,每个节点都会创建一个XMLStatementBuilder statementParser 对象,然后调用其parseStatementNode方法。
解析失败时会将statementParser添加到incompleteStatements。
后续所有节点解析完后回再调用parsePendingStatements()再次解析incompleteStatements集合内失败的节点。
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
parseStatementNode
源码很长,简单来说就是解析得到对应的属性,然后传入builderAssistant.addMappedStatement。
注意一下这个sqlSource,后续都是通过这个获取执行的sql。
createSqlSource会调用XMLLanguageDriver.createSqlSource,
然后调用XMLScriptBuilder.parseScriptNode 来获取SqlSource,获取流程
- 使用XMLScriptBuilder.parseDynamicTags包装sql得到MixedSqlNode rootSqlNode
- rootSqlNode被包装到 DynamicSqlSource 或者 RawSqlSource
- DynamicSqlSource 用于解析${} ,使用字符串拼接,DynamicSqlSource 初始化时只会做赋值操作,调用getBoundSql才会解析sql
- RawSqlSource 用于解析不包含${} ,使用 ? 替换 #{},RawSqlSource 初始化时就会调用其getSql解析sql
public void parseStatementNode() {
// ....
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
// ...
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
# isDynamic 代表xml中的sql是否包含 ${}
public SqlSource parseScriptNode() {
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
builderAssistant.addMappedStatement
这里会使用MappedStatement.Builder创建一个MappedStatement statement,然后传入configuration.addMappedStatement
public MappedStatement addMappedStatement( ... ) {
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
MappedStatement.Builder statementBuilder = new MappedStatement.Builder();
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
addMappedStatement
这里只是简单的将statement放入一个Map,这里的Map是Mybatis自定义的一个StrictMap
protected static class StrictMap<V> extends HashMap<String, V>
public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}
bindMapperForNamespace
这个方法是通过mapper.xml的namespace获取接口Class,然后调用configuration.addMapper
Mybatis并不强制要求使用接口,所以namespace不存在是不会抛出异常。
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
// ignore, bound type is not required
}
if (boundType != null && !configuration.hasMapper(boundType)) {
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
configuration.addMapper
这是只是简单调用mapperRegistry.addMapper
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
mapperRegistry.addMapper
- 根据type创建代理工厂new MapperProxyFactory<>(type))并加入名为knownMappers的Map中。
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
MapperProxyFactory
传入MapperProxy使用JDK动态代理生成代理类,也就是这里让Mybatis只用写接口就可以调用。
传入sqlSession也会被包装成MapperProxy
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethodInvoker> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
MapperProxy
实现了InvocationHandler,用于JDK动态代理调用,生成的代理类实际上也是调用的SqlSession内的selectOne、selectList等方法。
public class MapperProxy<T> implements InvocationHandler, Serializable {
}
SqlSession
public interface SqlSession extends Closeable {
<T> T selectOne(String statement);
<E> List<E> selectList(String statement);
// ...
<T> T getMapper(Class<T> type);
}
SqlSession.getMapper
调用configuration.getMapper(type, sqlSession) -> mapperRegistry.getMapper(type, sqlSession)
getMapper
从knownMappers容器中获取mapper代理工厂,调用mapperProxyFactory.newInstance(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);
}
SqlSession.selectList
- 根据 statement(String) 从 configuration.getMappedStatement 获取 MappedStatement ms
- 使用 executor 执行 ms
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
BaseExecutor.query (一级缓存)
- getBoundSql获取sql
- 调用queryFromDatabase
- 调用子类doQuery
- 查询结果会放入缓存localCache,这里就是Mybatiss的一级缓存
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query( ... ){
List<E> list;
try {
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
}
}
private <E> List<E> queryFromDatabase( ... )
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
doQuery
- SimpleExecutor 查询完成会关闭Statement
- ReuseExecutor 不会关闭Statement,会将创建的Statement放到一个Map<String, Statement> statementMap,key就是执行的sql
// SimpleExecutor.doQuery
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
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);
}
}
// ReuseExecutor.doQuery
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
}
二级缓存
如果开启了二级缓存时,executor会采用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;
}
二级缓存查询流程
得到cachekey后
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql)
会先获取二级缓存,获取不到再执行上面一级缓存流程
public <E> List<E> query( ... )
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key); // 获取二级缓存
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override