共计 41305 个字符,预计需要花费 104 分钟才能阅读完成。
MyBatis 通过应用 SqlSessionFactoryBuilder
,以创立 SqlSessionFactory
对象来进行 MyBatis 的启动,无论是否集成 Spring 的模块。SqlSessionFactoryBuilder
蕴含以下办法:
- SqlSessionFactoryBuilder#build(java.io.Reader)
- SqlSessionFactoryBuilder#build(java.io.Reader,java.lang.String)
- SqlSessionFactoryBuilder#build(java.io.Reader,java.util.Properties)
- SqlSessionFactoryBuilder#build(java.io.Reader,java.lang.String,java.util.Properties)
- SqlSessionFactoryBuilder#build(java.io.InputStream)
- SqlSessionFactoryBuilder#build(java.io.InputStream,java.lang.String)
- SqlSessionFactoryBuilder#build(java.io.InputStream,java.util.Properties)
- SqlSessionFactoryBuilder#build(java.io.InputStream,java.lang.String,java.util.Properties)
- SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)
除了最初了一个须要传入 Configuration
参数的办法外,其余底层都是通过 XMLConfigBuilder#parse()
办法来启动 MyBaits。
起因也很简略,除了须要传入 Configuration
参数的办法外,其余的办法都是要传入 Reader
或者 InputStream
文件流参数来进行文件读写。
而 XMLConfigBuilder
类是用来读取 mybatis-config.xml
文件的来进行 MyBatis 的全局配置和启动,那么它天然就须要通过文件流参数来读取配置文件。
SqlSessionFactoryBuilder 源码
/** | |
* 暗藏了局部源码,只保留次要局部 | |
*/ | |
public class SqlSessionFactoryBuilder { | |
/** | |
* 通过第一个参数的 Reader 对象读取 MyBatis-config.xml 文件,进行 MyBatis 的初始化,并返回 SqlSessionFactory 对象。* SqlSessionFactoryBuilder 除了该办法之外,还有另外一个办法反对 InputStream 文件流来读取 MyBatis-config.xml 文件。*/ | |
public SqlSessionFactory build(Reader reader, String environment, Properties properties) { | |
try { | |
// 创立 XMLConfigBuilder 对象 | |
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); | |
// 调用 XMLConfigBuilder#parse() 办法,该办法进行 MyBatis 的初始化工作。// 最终失去了 Configuration 对象,并调用 build(Configuration) 办法,返回 SqlSessionFactory 的默认实现。return build(parser.parse()); | |
} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e); | |
} finally {ErrorContext.instance().reset(); | |
try {reader.close(); | |
} catch (IOException e) {}} | |
} | |
public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config); | |
} | |
} |
XMLConfigBuilder
在 SqlSessionFactoryBuilder#build(Reader, String, Properties)
办法中,咱们看到它实质上是通过 XMLConfigBuilder
对象来实现 MyBatis 的初始化,那么 XMLConfigBuilder#parse()
办法是咱们要关注的内容。
它次要干了以下几个事件:
- 解析
<properties>
标签; - 解析
<settings>
标签;解析过程中如果蕴含了 vfsImpl 的属性,那么去设置自定义的 vfs 实现类。 - 解决日志相干组件;
- 解析
<typeAliases>
标签; - 解析
<plugins>
标签; - 解析
<objectFactory>
标签; - 解析
<objectWrapperFactory>
标签; - 解析
<reflectorFactory>
标签; - 解析
<environments>
标签; - 解析
<databaseIdProvider>
标签; - 解析
<typeHandlers>
标签; - 解析
<mappers>
标签。
源码如下:
public class XMLConfigBuilder extends BaseBuilder { | |
// configuration、typeAliasRegistry、typeHandlerRegistry 都在父类 BaseBuilder 中 | |
/** | |
* Mybatis 初始化解析到的配置信息,都会记录到 configuration 这里 | |
* Mybatis 的初始化过程,根本都是围绕这个对象的 | |
* 这个能够了解成是这个单例的对象 | |
*/ | |
protected final Configuration configuration; | |
/** | |
* 别名注册核心,用于解析别名 | |
*/ | |
protected final TypeAliasRegistry typeAliasRegistry; | |
/** | |
* TypeHandler 注册核心,用于解析 TypeHandler | |
* TypeHandler 次要是做 Java 类型和数据库类型的转换映射 | |
* 而用户能够自定义 TypeHandler,自定义的 TypeHandler 都会记录在这里 | |
*/ | |
protected final TypeHandlerRegistry typeHandlerRegistry; | |
/** | |
* 标识是否曾经齐全解析完 mybatis-config.xml 配置文件 | |
*/ | |
private boolean parsed; | |
/** | |
* XML 解析器,用于解析 mybatis-config.xml 配置文件 | |
*/ | |
private final XPathParser parser; | |
/** | |
* 标签定义的环境名称 | |
*/ | |
private String environment; | |
/** | |
* 对 Reflector 对象的创立和缓存 | |
*/ | |
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory(); | |
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 | |
// 解析 <properties> 标签 | |
propertiesElement(root.evalNode("properties")); | |
// 解析 <settings> 标签 | |
Properties settings = settingsAsProperties(root.evalNode("settings")); | |
// 从 <settings> 标签中,查找并设置自定义的 vfs(虚构文件系统)实现类。loadCustomVfs(settings); | |
// 解决日志相干组件 | |
loadCustomLogImpl(settings); | |
// 解析 <typeAliases> 标签 | |
typeAliasesElement(root.evalNode("typeAliases")); | |
// 解析 <plugins> 标签 | |
pluginElement(root.evalNode("plugins")); | |
// 解析 <objectFactory> 标签 | |
objectFactoryElement(root.evalNode("objectFactory")); | |
// 解析 <objectWrapperFactory> 标签 | |
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); | |
// 解析 <reflectorFactory> 标签 | |
reflectorFactoryElement(root.evalNode("reflectorFactory")); | |
// 从 <settings> 标签中解析到的内容,设置到 Configuration 对象的字段上 | |
settingsElement(settings); | |
// read it after objectFactory and objectWrapperFactory issue #631 | |
// 解析 <environments> 标签 | |
environmentsElement(root.evalNode("environments")); | |
// 解析 <databaseIdProvider> 标签 | |
databaseIdProviderElement(root.evalNode("databaseIdProvider")); | |
// 解析 <typeHandlers> 标签 | |
typeHandlerElement(root.evalNode("typeHandlers")); | |
// 解析 <mappers> 标签 | |
mapperElement(root.evalNode("mappers")); | |
} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause:" + e, e); | |
} | |
} | |
} |
解析 properties 标签
通过调用 propertiesElement(XNode)
办法,读取 <properties>
标签以及它的属性值。<properties>
标签读取属性的优先级如下:通过办法参数传递 > resource 或者 url 属性指定的配置文件 > properties 元素指定的属性。
从 <properties>
标签中解析进去的 KV 信息会被记录到一个 Properties 对象(也就是 Configuration 全局配置对象的 variables 字段),在后续解析其余标签的时候,MyBatis 会应用这个 Properties 对象中记录的 KV 信息替换匹配的占位符。
解析 settings 标签
MyBatis 在解析 <settings>
标签时,次要是这么做:
- 通过调用
settingsAsProperties(XNode)
办法来解析<settings>
标签,并失去Properties
对象 - 通过调用
loadCustomVfs(Properties)
办法来查找并设置自定义的 vfs 实现 - 通过调用
loadCustomLogImpl(Properties)
办法来查找并设置自定义的日志实现 - 通过调用
settingsElement(Properties)
办法,来设置Configuration
对象的信息
解析 typeAliases 标签
将 <typeAliases>
标签扫描的类 class 和别名信息,保留到 TypeAliasRegistry.typeAliases
的字段中去。
源码如下:
private void typeAliasesElement(XNode parent) {if (parent != null) {for (XNode child : parent.getChildren()) {if ("package".equals(child.getName())) { | |
// 如果是 <package> 标签,则通过 name 属性来扫描所有的 class | |
// 而后查看每一个 class 是否蕴含 @Alias 注解。如果有,那么 class 的别名就是 @Alias 的值, 否则就是 class 的名字 | |
// 最初保留到 TypeAliasRegistry.typeAliases 的字段中去 | |
String typeAliasPackage = child.getStringAttribute("name"); | |
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage); | |
} else {String alias = child.getStringAttribute("alias"); | |
String type = child.getStringAttribute("type"); | |
try {Class<?> clazz = Resources.classForName(type); | |
if (alias == null) { | |
// 如果 alias 属性为 null,就先从 type 属性指定的 class 中,查找是否有 @Alias 这个注解 | |
// 如果注解存在,则拿注解的值,否则就间接拿 class 的名字 | |
// 最初保留到 TypeAliasRegistry.typeAliases 的字段中去 | |
typeAliasRegistry.registerAlias(clazz); | |
} else { | |
// 如果 alias 属性不为 null,就去将 alias 的值设置为 class 的别名 | |
// 最初保留到 TypeAliasRegistry.typeAliases 的字段中去 | |
typeAliasRegistry.registerAlias(alias, clazz); | |
} | |
} catch (ClassNotFoundException e) {throw new BuilderException("Error registering typeAlias for'" + alias + "'. Cause:" + e, e); | |
} | |
} | |
} | |
} | |
} |
解析 plugins 标签
扫描 <plugins>
下的 <plugin>
标签,并将这些 <plugin>
标签对应的 class,通过无参构造方法来创立进去。增加到 Configuration
对象的 interceptorChain
成员变量(外部保护了 List<Interceptor>
对象的链)下。
解决 typeHandlers 标签
源码如下:
private void typeHandlerElement(XNode parent) {if (parent != null) { | |
// 解决所有 <typeHandler> 的标签 | |
for (XNode child : parent.getChildren()) {if ("package".equals(child.getName())) { | |
// 如果指定了 package 属性,就扫描指定包中所有带有 @MappedTypes 注解的类,而后实现 TypeHandler 的注册 | |
String typeHandlerPackage = child.getStringAttribute("name"); | |
typeHandlerRegistry.register(typeHandlerPackage); | |
} else { | |
// 如果没有指定 package 属性,则尝试获取 javaType、jdbcType、handler 三个属性 | |
String javaTypeName = child.getStringAttribute("javaType"); | |
String jdbcTypeName = child.getStringAttribute("jdbcType"); | |
String handlerTypeName = child.getStringAttribute("handler"); | |
// 依据属性确定 TypeHandler 类型以及它可能解决的数据库类型和 Java 类型 | |
Class<?> javaTypeClass = resolveClass(javaTypeName); | |
JdbcType jdbcType = resolveJdbcType(jdbcTypeName); | |
Class<?> typeHandlerClass = resolveClass(handlerTypeName); | |
// 调用 TypeHandlerRegistry 的 register 办法来注册 TypeHandler | |
if (javaTypeClass != null) {if (jdbcType == null) {typeHandlerRegistry.register(javaTypeClass, typeHandlerClass); | |
} else {typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass); | |
} | |
} else {typeHandlerRegistry.register(typeHandlerClass); | |
} | |
} | |
} | |
} | |
} |
解析 mappers 标签
Mybatis 通过解析 <mappers>
标签,加载并增加 Mapper 到 Configuration
对象的 MapperRegistry
成员变量中去。
它的工作流程如下:
- 扫描
<mappers>
标签下的<mapper>
标签,依据其package
、resource
、url
或者mapperClass
属性的值,创立出XMLMapperBuilder
。 - 调用
XMLMapperBuilder#parse()
办法,以实现<mappers>
标签的解析。
源码如下:
// org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement | |
private void mapperElement(XNode parent) throws Exception {if (parent != null) { | |
// 遍历每个子标签 | |
for (XNode child : parent.getChildren()) {if ("package".equals(child.getName())) { | |
// 如果指定了 <package> 标签,那么就去扫描指定包内的全副 class | |
String mapperPackage = child.getStringAttribute("name"); | |
configuration.addMappers(mapperPackage); | |
} else { | |
// 解析 <mapper> 子标签,这里会获取 resource、url、class 三个属性,这三个属性互斥 | |
String resource = child.getStringAttribute("resource"); | |
String url = child.getStringAttribute("url"); | |
String mapperClass = child.getStringAttribute("class"); | |
/* | |
如果 <mapper> 标签指定了 resource 或者 url 属性,就会创立 XMLMapperBuilder 对象 | |
而后应用这个 XMLMapperBuilder 实例解析指定的 Mapper.xml 配置文件 | |
Mybatis 会为每个 mapper.xml 文件创建一个 XMLMapperBuilder | |
*/ | |
if (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource); | |
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); | |
InputStream inputStream = Resources.getUrlAsStream(url); | |
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); | |
mapperParser.parse();} else if (resource == null && url == null && mapperClass != null) { | |
// 如果 <mapper> 标签指定了 class 属性,则向 MapperRegistry 注册 class 属性指定的 Mapper 接口 | |
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() 办法
XMLMapperBuilder#parse()
办法的工作流程如下:
- 解析
<cache-ref>
跟<cache>
标签的信息,来设置该 Mapper 的二级缓存 - 解析
<parameterMap>
标签 - 解析
<resultMap>
标签 - 解析
<sql>
标签 - 解析
<select>
,<insert>
,<update>
,<delete>
标签
源码如下:
// org.apache.ibatis.builder.xml.XMLMapperBuilder#parse | |
public void parse() { | |
// 这么做次要是避免屡次加载同一个 mapper 的配置信息(因为 Spring 早就曾经加过一次了)if (!configuration.isResourceLoaded(resource)) { | |
// 真正解析 Mapper.xml 映射文件的中央 | |
configurationElement(parser.evalNode("/mapper")); | |
// 将 resource 对象增加到 Configuration 中,示意曾经加载过该资源了 | |
configuration.addLoadedResource(resource); | |
// 从 MapperBuilderAssistant 中获取以后加载 Mapper 的全限定名 | |
// 并通过全限定名找到该 Mapper 的 class 信息,而后增加到 Configuration.mapperRegistry 的 knownMappers 中去 | |
bindMapperForNamespace();} | |
parsePendingResultMaps(); | |
parsePendingCacheRefs(); | |
parsePendingStatements();} | |
// org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement | |
private void configurationElement(XNode context) { | |
try { | |
// 获取 mapper 的 namespace | |
String namespace = context.getStringAttribute("namespace"); | |
if (namespace == null || namespace.isEmpty()) {throw new BuilderException("Mapper's namespace cannot be empty"); | |
} | |
// 设置 mapper 的 namespace 到 builderAssistant 的 currentNamespace 字段上 | |
// 后续解析 <cache-ref> 标签会用到 | |
builderAssistant.setCurrentNamespace(namespace); | |
// 解析 cache 和 cache-ref(二级缓存) 标签 | |
cacheRefElement(context.evalNode("cache-ref")); | |
cacheElement(context.evalNode("cache")); | |
// 解析 parameterMap 和 resultMap 标签 | |
parameterMapElement(context.evalNodes("/mapper/parameterMap")); | |
resultMapElements(context.evalNodes("/mapper/resultMap")); | |
// 解析 sql 标签 | |
sqlElement(context.evalNodes("/mapper/sql")); | |
// 解析 select , insert , update , delete 的标签 | |
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); | |
} | |
} |
1 解析 cache-ref 和 cache 标签
解析 <cache-ref>
标签的流程如下:
- 将以后 mapper 的 namespace 与
<cache-ref>
的 namespace 属性关联到configuration.cacheRef
中去 - 从
Configuration.caches
中查找<cache-ref>
指定的缓存,并通过MapperBuilderAssistant
将以后的 mapper 的缓存设置成<cache-ref>
指定的缓存。 - 如果
<cache-ref>
指定的缓存没有加载进来的话,那么就会抛出 IncompleteElementException 的异样,
并增加到configuration
的incompleteCacheRefs
列表中去,等到后续再增加
<cache>
标签的解析绝对 <cache-ref>
就简略很多了,流程如下:
- 读取
<cache>
标签下的type
,eviction
,flushInterval
等属性的信息 - 依据读取到的属性,用于创立一个新的
Cache
缓存对象,而后增加到Configuration.caches
中去
2 解析 parameterMap 标签
流程如下:
- 获取
<parameterMap>
标签的id
跟type
属性。 - 解析
<parameterMap>
标签内所有的<parameter>
标签的属性,如property
,javaType
,jdbcType
,resultMap
等属性。 - 将
<parameter>
标签解析到的属性,通过builderAssistant#buildParameterMapping()
办法创立ParameterMapping
对象。 - 创立
ParameterMap
对象,并将ParameterMapping
对象增加到ParameterMap
外面。 - 将
ParameterMap
对象增加到Configuration
对象的parameterMap
中去。
源码如下:
private void parameterMapElement(List<XNode> list) {for (XNode parameterMapNode : list) { | |
// 解析每个 <parameterMap> 标签的 id、type 属性 | |
String id = parameterMapNode.getStringAttribute("id"); | |
String type = parameterMapNode.getStringAttribute("type"); | |
Class<?> parameterClass = resolveClass(type); | |
List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter"); | |
List<ParameterMapping> parameterMappings = new ArrayList<>(); | |
for (XNode parameterNode : parameterNodes) { | |
// 解析 <parameter> 的标签属性 | |
String property = parameterNode.getStringAttribute("property"); | |
String javaType = parameterNode.getStringAttribute("javaType"); | |
String jdbcType = parameterNode.getStringAttribute("jdbcType"); | |
String resultMap = parameterNode.getStringAttribute("resultMap"); | |
String mode = parameterNode.getStringAttribute("mode"); | |
String typeHandler = parameterNode.getStringAttribute("typeHandler"); | |
Integer numericScale = parameterNode.getIntAttribute("numericScale"); | |
ParameterMode modeEnum = resolveParameterMode(mode); | |
Class<?> javaTypeClass = resolveClass(javaType); | |
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType); | |
Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler); | |
// 依据 <parameter> 标签的属性,创立 ParameterMapping 对象,并增加到 Configuration 的 parameterMap 中去 | |
ParameterMapping parameterMapping = | |
builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale); | |
parameterMappings.add(parameterMapping); | |
} | |
builderAssistant.addParameterMap(id, parameterClass, parameterMappings); | |
} | |
} |
2.1 创立 ParameterMapping 对象流程
MyBatis 在解析 <parameter>
的过程中,会创立 ParameterMapping
对象进去,用来示意 <parameter>
标签的信息。ParameterMapping
对象创立的流程如下:
- 生成 resultMap 的名字,名字是 mapper 的全限定名 + resultMap 的 id,用于示意该
PameterMapping
对象与 resultMap 的关联关系。 -
解析
<parameter>
标签对应 javaType,也就是参数的 class 信息,它的流程如下:- 如果 javaType 参数不为空,那么 javaTypeClass 也就是 javaType
- 如果 jdbcType 参数的值为 JdbcType.CURSOR,那么 javaTypeClass 的值就是它
- 如果 parameterType 参数的值为 Map,那么 javaTypeClass 的值就是 Object.class
- 尝试通过 parameterType 参数来创立 MetaClass , 并通过 MetaClass 来获取 property 的 class 信息,那么 javaTypeClass 就是这个 class 信息。
实质就是通过反射来获取 parameterType 参数的 property 参数的 class 信息 - 如果都获取不到,那返回 Object.class
- 依据 typeHandler 的 class 信息来获取 / 创立 typeHandler 对象
- 返回
ParameterMapping
对象
源码如下:
//org.apache.ibatis.builder.MapperBuilderAssistant#buildParameterMapping | |
public ParameterMapping buildParameterMapping( | |
Class<?> parameterType, | |
String property, | |
Class<?> javaType, | |
JdbcType jdbcType, | |
String resultMap, | |
ParameterMode parameterMode, | |
Class<? extends TypeHandler<?>> typeHandler, | |
Integer numericScale) { | |
// 生成 resultMap 的名字 | |
resultMap = applyCurrentNamespace(resultMap, true); | |
// 解析参数的 javaType,用于决定 <parameter> 的类型 | |
Class<?> javaTypeClass = resolveParameterJavaType(parameterType, property, javaType, jdbcType); | |
// 依据 typeHandler 的 class 信息来获取 / 创立 typeHandler 对象 | |
TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler); | |
return new ParameterMapping.Builder(configuration, property, javaTypeClass) | |
.jdbcType(jdbcType) | |
.resultMapId(resultMap) | |
.mode(parameterMode) | |
.numericScale(numericScale) | |
.typeHandler(typeHandlerInstance) | |
.build();} | |
/** | |
* 解析参数的 javaType | |
* 如果 javaType 参数为空的话,则尝试通过 jdbcType 或者 resultType 去查找参数的类型 | |
* @param resultType <parameterMap> 的 type 信息,也就是 parameterMap 映射的类 | |
* @param property <parameter> 的 property 信息,也就是 parameterMap 映射的类的字段名 | |
* @param javaType <parameter> 的 javaType,示意指定的字段名的类型 | |
* @param jdbcType <parameter> 的 jdbcType,示意指定的字段名对应的 jdbcType | |
* @return | |
*/ | |
//org.apache.ibatis.builder.MapperBuilderAssistant#resolveParameterJavaType | |
private Class<?> resolveParameterJavaType(Class<?> resultType, String property, Class<?> javaType, JdbcType jdbcType) {if (javaType == null) { | |
// 如果没有指定 javaType 的话,则思考从 jdbcType 或者 resultType 外面获取 | |
if (JdbcType.CURSOR.equals(jdbcType)) {javaType = java.sql.ResultSet.class;} else if (Map.class.isAssignableFrom(resultType)) {javaType = Object.class;} else { | |
// 如果 jdbcType 不是 CURSOR,resultType 也不是 Map 的话 | |
// 则须要依据 resultType 来创立 MetaClass,而后获取 property 对应的 class 信息 | |
MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory()); | |
// 失去了 MetaClass 后,调用 getGetterType 办法 | |
// 底层是调用 Reflector#getGetterType 来获取 resultType 的 property 的 class 信息 | |
javaType = metaResultType.getGetterType(property); | |
} | |
} | |
if (javaType == null) {javaType = Object.class;} | |
return javaType; | |
} |
3 解析 resultMap 标签
在解析 <resultMap>
标签的过程中,会解析它的 id
跟 type
属性值,以确定 resultMap 的 id 跟 java 类型。
而后解析它的子标签,如:<constructor>
, <discriminator>
, <id>
, <result>
等子标签,而后将这些子标签转换成 ResultMapping
对象来示意。
接着创立 ResultMapResolver
对象,并把下面失去的 id
、type
、ResultMapping
对象,传入到 ResultMapResolver
对象中去。
最初调用 ResultMapResolver#resolve()
办法,创立出 ResultMap
对象,并把该对象存入到 Configuration
的 resultMaps
中去。
3.1 解析 resultMap 外围源码
// org.apache.ibatis.builder.xml.XMLMapperBuilder#resultMapElement | |
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {ErrorContext.instance().activity("processing" + resultMapNode.getValueBasedIdentifier()); | |
/* | |
解析 resultMap 的 type 属性,会顺次依照以下程序查找 type 属性 | |
type->ofType->resultType->javaType | |
*/ | |
String type = resultMapNode.getStringAttribute("type", | |
resultMapNode.getStringAttribute("ofType", | |
resultMapNode.getStringAttribute("resultType", | |
resultMapNode.getStringAttribute("javaType")))); | |
Class<?> typeClass = resolveClass(type); | |
if (typeClass == null) { | |
// 如果没有找到 resultMap 的类型,则尝试从参数 enclosingType 中获取 | |
typeClass = inheritEnclosingType(resultMapNode, enclosingType); | |
} | |
Discriminator discriminator = null; | |
List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings); | |
List<XNode> resultChildren = resultMapNode.getChildren(); | |
/* | |
解析 resultMap 下的各个子标签,每个子标签都会生成一个 ResultMapping 对象 | |
这些 resultMapping 对象会增加到 resultMappings 汇合中去 | |
这里波及到 <id> , <result> , <association> , <collection> , <discriminator> 标签 | |
*/ | |
for (XNode resultChild : resultChildren) {if ("constructor".equals(resultChild.getName())) { | |
// 解析 <constructor> 标签 | |
processConstructorElement(resultChild, typeClass, resultMappings); | |
} else if ("discriminator".equals(resultChild.getName())) {discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings); | |
} else {List<ResultFlag> flags = new ArrayList<>(); | |
if ("id".equals(resultChild.getName())) {flags.add(ResultFlag.ID); | |
} | |
// 解析 <id>、<result> 标签 | |
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); | |
} | |
} | |
// 获取 <resultMap> 标签的 id 属性,默认值会拼装所有父标签的 id、value、property 属性值 | |
String id = resultMapNode.getStringAttribute("id",resultMapNode.getValueBasedIdentifier()); | |
// 获取 resultMap 的 <extends> 和 <autoMapping> 属性 | |
String extend = resultMapNode.getStringAttribute("extends"); | |
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping"); | |
// 创立 ResultMapResolver 对象,ResultMapResolver 会依据下面解析到的 ResultMappings 汇合以及 <resultMap> 标签的属性 | |
ResultMapResolver resultMapResolver = | |
new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping); | |
try { | |
// 结构 resultMap 对象,并将其增加到 Configuration.resultMaps 汇合中去,而后返回 resultMap 对象 | |
return resultMapResolver.resolve();} catch (IncompleteElementException e) {configuration.addIncompleteResultMap(resultMapResolver); | |
throw e; | |
} | |
} |
3.2 buildResultMappingFromContext 办法
该办法是解析 <resultMap>
下的 <constructor>
、<id>
跟 <result>
标签的次要入口。
源码:
// org.apache.ibatis.builder.xml.XMLMapperBuilder#buildResultMappingFromContext | |
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) { | |
String property; | |
// 如果是 <construct> 标签,则取 name 属性,否则取 property 属性 | |
if (flags.contains(ResultFlag.CONSTRUCTOR)) {property = context.getStringAttribute("name"); | |
} else {property = context.getStringAttribute("property"); | |
} | |
// 获取 column、javaType、jdbcType、select 等一系列属性 | |
String column = context.getStringAttribute("column"); | |
String javaType = context.getStringAttribute("javaType"); | |
String jdbcType = context.getStringAttribute("jdbcType"); | |
String nestedSelect = context.getStringAttribute("select"); | |
// 如果是 <association> 和 <collection> 标签,则进行解析 | |
// 如果 resultMap 属性有值,则间接取 resultMap 的属性值,否则就要去获取 resultMap 的 id(如果标签的 select 属性不为空,那么 resultMap 的 id 就为 null)// processNestedResultMappings 其实就是递归调用 org.apache.ibatis.builder.xml.XMLMapperBuilder#resultMapElement() 办法 | |
String nestedResultMap = context.getStringAttribute("resultMap", () -> | |
processNestedResultMappings(context, Collections.emptyList(), resultType)); | |
String notNullColumn = context.getStringAttribute("notNullColumn"); | |
String columnPrefix = context.getStringAttribute("columnPrefix"); | |
String typeHandler = context.getStringAttribute("typeHandler"); | |
String resultSet = context.getStringAttribute("resultSet"); | |
String foreignColumn = context.getStringAttribute("foreignColumn"); | |
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager")); | |
// 依据下面失去的属性值,获取标签对应的 javaTypeClass , typeHandler , jdbcType 的类型 | |
Class<?> javaTypeClass = resolveClass(javaType); | |
Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler); | |
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType); | |
// 创立 ResultMapping 对象,源码在上面 | |
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, | |
nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy); | |
} | |
// org.apache.ibatis.builder.MapperBuilderAssistant#buildResultMapping | |
public ResultMapping buildResultMapping( | |
Class<?> resultType, | |
String property, | |
String column, | |
Class<?> javaType, | |
JdbcType jdbcType, | |
String nestedSelect, | |
String nestedResultMap, | |
String notNullColumn, | |
String columnPrefix, | |
Class<? extends TypeHandler<?>> typeHandler, | |
List<ResultFlag> flags, | |
String resultSet, | |
String foreignColumn, | |
boolean lazy) { | |
// 如果 javaType 为 null 的话,间接拿 resultMap(resultType 参数)的 class 下的属性 /setter 办法(property 参数)的 class | |
// 如果 javaType 不为 null 的话,则间接是拿 javaType 的 class | |
Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType); | |
// 获取 typeHandler | |
TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler); | |
List<ResultMapping> composites; | |
if ((nestedSelect == null || nestedSelect.isEmpty()) && (foreignColumn == null || foreignColumn.isEmpty())) {composites = Collections.emptyList(); | |
} else {composites = parseCompositeColumnName(column); | |
} | |
return new ResultMapping.Builder(configuration, property, column, javaTypeClass) | |
.jdbcType(jdbcType) | |
.nestedQueryId(applyCurrentNamespace(nestedSelect, true)) | |
.nestedResultMapId(applyCurrentNamespace(nestedResultMap, true)) | |
.resultSet(resultSet) | |
.typeHandler(typeHandlerInstance) | |
.flags(flags == null ? new ArrayList<>() : flags) | |
.composites(composites) | |
.notNullColumns(parseMultipleColumnNames(notNullColumn)) | |
.columnPrefix(columnPrefix) | |
.foreignColumn(foreignColumn) | |
.lazy(lazy) | |
.build();} |
3.3 ResultMapResolver#resolve() 办法
失去了 ResultMapping
对象的列表后,则须要将 resultMap 的 id
, type
, resultMapping
等属性,通过 ResultMapResolver#resolve()
办法创立 ResultMap
对象进去,再增加到 Configuration.resultMaps
中去。
源码:
// org.apache.ibatis.builder.ResultMapResolver#resolve | |
public ResultMap resolve() {return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping); | |
} | |
// org.apache.ibatis.builder.MapperBuilderAssistant#addResultMap | |
public ResultMap addResultMap( | |
String id, | |
Class<?> type, | |
String extend, | |
Discriminator discriminator, | |
List<ResultMapping> resultMappings, | |
Boolean autoMapping) { | |
// ResultMap 的残缺 id 是 "namespace.id" 的格局,namespace 就是 mapper 的 namespace | |
id = applyCurrentNamespace(id, false); | |
// 获取被继承的 ResultMap 的残缺 id,也就是父 ResultMap 对象的残缺 id | |
extend = applyCurrentNamespace(extend, true); | |
// 针对 extend 属性的解决 | |
if (extend != null) { | |
// 针对 extend 检测 Configuration.resultMaps 汇合中是否存在被继承的 ResultMap 对象 | |
if (!configuration.hasResultMap(extend)) {throw new IncompleteElementException("Could not find a parent resultmap with id'" + extend + "'"); | |
} | |
// 获取须要被继承的 resultMap 对象,也就是父 ResultMap 对象 | |
ResultMap resultMap = configuration.getResultMap(extend); | |
// 获取父对 ResultMap 对象中记录的 ResultMapping 汇合 | |
List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings()); | |
// 删除须要笼罩的 ResultMapping 汇合 | |
extendedResultMappings.removeAll(resultMappings); | |
/* | |
* 如果以后 <resultMap> 标签中定义了 <constructor> 标签,* 则不须要应用父 ResultMap 中记录的相应 <constructor> 标签,这里会将其对应的 resultMapping 对象删除 | |
*/ | |
boolean declaresConstructor = false; | |
for (ResultMapping resultMapping : resultMappings) {if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) { | |
declaresConstructor = true; | |
break; | |
} | |
} | |
if (declaresConstructor) {extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)); | |
} | |
// 增加须要被继承的 resultMapping 对象记录到 resultMappings 汇合中 | |
resultMappings.addAll(extendedResultMappings); | |
} | |
// 创立 resultMap 对象,并增加到 Configuration.resultMaps 汇合中 | |
ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping) | |
.discriminator(discriminator) | |
.build(); | |
configuration.addResultMap(resultMap); | |
return resultMap; | |
} |
4 解析 sql 标签
解析 <sql>
标签的流程则比较简单,次要是扫描所有 <sql>
的标签,并把它们保留到 XMLMapperBuilder
的 sqlFragments
Map 中去。其中 sqlFragments
的 key 为 <sql>
标签的 id
属性,value 为 <sql>
标签对应的 XNode
对象。
对应源码在 org.apache.ibatis.builder.xml.XMLMapperBuilder#sqlElement(java.util.List<org.apache.ibatis.parsing.XNode>)
办法上。
5 解析 select , insert , update , delete 的标签
工作流程如下:
- 依据标签的名字来决定 SQL 的类型,是
select
,insert
还是其余 SQL 语句,并用SqlCommandType
来示意。 - 如果标签内蕴含
<include>
的子标签,则将<include>
的内容转换成 SQL 片段。 - 获取标签的 parameterType,确定标签的参数类型。
- 解决
<selectKey>
标签,并解析<selectKey>
标签来失去KeyGenerator
对象。 - 通过
LanguageDriver.createSqlSource()
办法来创立 SqlSource 对象,这个过程会解析标签内的<if>
,<where>
等子标签。 - 获取 SQL 标签中配置的 resultSets、keyProperty、keyColumn 等属性,以及后面解析
<selectKey>
标签失去的 KeyGenerator 对象等,这些信息将会填充到MappedStatement
对象中。 - 根据上述属性信息创立
MappedStatement
对象,并增加到Configuration.mappedStatements
汇合中保留
源码如下:
// org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode | |
public void parseStatementNode() { | |
// 获取 id 跟 databaseId | |
String id = context.getStringAttribute("id"); | |
String databaseId = context.getStringAttribute("databaseId"); | |
/* | |
1. 如果 databaseId 跟以后的不匹配,则不加载该 SQL 标签 | |
2. 如果存在雷同 id 且 databaseId 不为空的 SQL 标签,则不再加载该 SQL 标签 | |
*/ | |
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;} | |
// 依据 SQL 标签名来决定其 SqlCommandType, SqlCommandType 用来判断是以后的 SQL 标签是 select , insert , update , delete 还是 flush | |
String nodeName = context.getNode().getNodeName(); | |
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); | |
boolean isSelect = sqlCommandType == SqlCommandType.SELECT; | |
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); | |
boolean useCache = context.getBooleanAttribute("useCache", isSelect); | |
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); | |
// 在解析 SQL 标签前,先解决 <include> 标签 | |
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); | |
// 将 <include> 标签的内容转换成 SQL 片段 | |
includeParser.applyIncludes(context.getNode()); | |
// 获取 SQL 标签的 parameterType 和 lang 属性 | |
String parameterType = context.getStringAttribute("parameterType"); | |
Class<?> parameterTypeClass = resolveClass(parameterType); | |
String lang = context.getStringAttribute("lang"); | |
LanguageDriver langDriver = getLanguageDriver(lang); | |
// 解决 <selectKey> 标签 | |
processSelectKeyNodes(id, parameterTypeClass, langDriver); | |
// 获取其余属性,而后持续解析这些属性 | |
// 获取 / 创立主键 id 生成器,如果是标签上的 useGeneratedKeys 的属性为 true | |
// 或者是 useGeneratedKeys 没有值,mybatis-config.xml 上设置了 userGeneratedKey,并且是 Insert 语句 | |
KeyGenerator keyGenerator; | |
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; | |
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); | |
if (configuration.hasKeyGenerator(keyStatementId)) {keyGenerator = configuration.getKeyGenerator(keyStatementId); | |
} else { | |
keyGenerator = context.getBooleanAttribute("useGeneratedKeys", | |
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) | |
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; | |
} | |
// 通过 LanguageDriver.createSqlSource() 办法来创立 SqlSource 对象 | |
// 这里会解析 if , where , case 等子标签 | |
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); | |
/* | |
获取 SQL 标签中配置的 resultSets、keyProperty、keyColumn 等属性,以及后面解析 <selectKey> 标签失去的 KeyGenerator 对象等,这些信息将会填充到 MappedStatement 对象中 | |
*/ | |
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); | |
Integer fetchSize = context.getIntAttribute("fetchSize"); | |
Integer timeout = context.getIntAttribute("timeout"); | |
String parameterMap = context.getStringAttribute("parameterMap"); | |
String resultType = context.getStringAttribute("resultType"); | |
Class<?> resultTypeClass = resolveClass(resultType); | |
String resultMap = context.getStringAttribute("resultMap"); | |
String resultSetType = context.getStringAttribute("resultSetType"); | |
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); | |
if (resultSetTypeEnum == null) {resultSetTypeEnum = configuration.getDefaultResultSetType(); | |
} | |
String keyProperty = context.getStringAttribute("keyProperty"); | |
String keyColumn = context.getStringAttribute("keyColumn"); | |
String resultSets = context.getStringAttribute("resultSets"); | |
// 根据上述属性信息创立 MappedStatement 对象,并增加到 Configuration.mappedStatements 汇合中保留 | |
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, | |
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, | |
resultSetTypeEnum, flushCache, useCache, resultOrdered, | |
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); | |
} |
5.1 获取标签对应的 SQL 类型
MyBatis 依据 SqlCommandType
枚举类来决定该 SQL 的类型,该 SqlCommandType
会最初在 MapperBuilderAssistant#addMappedStatement()
办法中,作为其中一个参数增加进去。
源码如下:
// org.apache.ibatis.mapping.SqlCommandType | |
public enum SqlCommandType {UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH} |
5.2 解析 include 标签
MyBatis 通过创立 XMLIncludeTransformer
对象,并通过其 applyIncludes(XNode)
办法,来将 <include>
标签援用的 <sql>
标签,转换成 SQL 片段。
源码如下
// org.apache.ibatis.builder.xml.XMLIncludeTransformer#applyIncludes(org.w3c.dom.Node) | |
public void applyIncludes(Node source) {Properties variablesContext = new Properties(); | |
Properties configurationVariables = configuration.getVariables(); | |
Optional.ofNullable(configurationVariables).ifPresent(variablesContext::putAll); | |
applyIncludes(source, variablesContext, false); | |
} | |
// org.apache.ibatis.builder.xml.XMLIncludeTransformer#applyIncludes(org.w3c.dom.Node, java.util.Properties, boolean) | |
private void applyIncludes(Node source, final Properties variablesContext, boolean included) {if ("include".equals(source.getNodeName())) { | |
// 通过 <include> 标签的 refid 字段,找到对应的 <sql> 标签,失去 Node 对象 | |
Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext); | |
/* | |
解析 <include> 标签下的 <property> 标签,将失去的键值增加到 variablesContext 汇合(Properties 类型)并造成新的 Properties 对象返回,用于替换占位符 | |
*/ | |
Properties toIncludeContext = getVariablesContext(source, variablesContext); | |
/* | |
递归执行 applyIncludes() 办法,因为在 <sql> 标签的定义中可能会应用 <include> 援用其余 SQL 片段,在 applyIncludes() 办法递归的过程中,如果遇到“${}”占位符,则应用 variablesContext 汇合中的键值对进行替换;最初,将 <include> 标签替换成 <sql> 标签的内容。*/ | |
applyIncludes(toInclude, toIncludeContext, true); | |
if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {toInclude = source.getOwnerDocument().importNode(toInclude, true); | |
} | |
source.getParentNode().replaceChild(toInclude, source); | |
while (toInclude.hasChildNodes()) {toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude); | |
} | |
toInclude.getParentNode().removeChild(toInclude); | |
} else if (source.getNodeType() == Node.ELEMENT_NODE) {if (included && !variablesContext.isEmpty()) { | |
// replace variables in attribute values | |
NamedNodeMap attributes = source.getAttributes(); | |
for (int i = 0; i < attributes.getLength(); i++) {Node attr = attributes.item(i); | |
attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext)); | |
} | |
} | |
NodeList children = source.getChildNodes(); | |
for (int i = 0; i < children.getLength(); i++) {applyIncludes(children.item(i), variablesContext, included); | |
} | |
} else if (included && (source.getNodeType() == Node.TEXT_NODE || source.getNodeType() == Node.CDATA_SECTION_NODE) | |
&& !variablesContext.isEmpty()) { | |
// replace variables in text node | |
source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext)); | |
} | |
} |
5.3 获取 SqlSource 对象
MyBatis 通过调用 getLanguageDriver()
办法,以获取 LanguageDriver
对象,并调用其 createSqlSource()
办法,以获取 SqlSource
对象。LanguageDriver
的默认实现类是 XMLLanguageDriver
,它的 createSqlSource()
会解析 <if>
, <where>
, <trim>
等其余标签。
具体逻辑在 org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode()
办法中。
而 XMLScriptBuilder
外部保护了 NodeHandler
接口以及其实现类,NodeHandler
负责解析动静 SQL 内的标签,生成相应的 SqlNode
对象。
以 <trim>
标签为例:
private interface NodeHandler { | |
// 将 nodeToHandle 参数,转化成 SqlNode 对象,并增加到 targetContents 列表中去 | |
void handleNode(XNode nodeToHandle, List<SqlNode> targetContents); | |
} | |
private class TrimHandler implements NodeHandler {public TrimHandler() { } | |
@Override | |
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { | |
// 通过 org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseDynamicTags 办法失去 MixedSqlNode | |
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); | |
// 解析 trim 标签的 prefix , prefixOverrides , suffix , suffixOverrides 属性 | |
String prefix = nodeToHandle.getStringAttribute("prefix"); | |
String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides"); | |
String suffix = nodeToHandle.getStringAttribute("suffix"); | |
String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides"); | |
// 将这些属性作为构造方法的参数,创立出 TrimSqlNode 对象进去 | |
TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides); | |
targetContents.add(trim); | |
} | |
} |
集成了 Spring 模块的状况
在集成了 MyBatis/Spring 或者是 MyBatis/Spring-Boot-Starter 下,则是应用 SqlSessionFactoryBean
来实现 SqlSessionFactory
对象的创立。
次要办法在 org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory
办法下:
public class SqlSessionFactoryBean | |
implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> { | |
// 因为 SqlSessionFactoryBean 继承了 InitializingBean,实现了该办法 | |
// 故在 Spring 初始化该 Bean 时,会调用该办法,而后调用 buildSqlSessionFactory() 办法,来进行 MyBatis 的初始化和启动流程,并获取 SqlSessionFactory 对象 | |
@Override | |
public void afterPropertiesSet() throws Exception {notNull(dataSource, "Property'dataSource'is required"); | |
notNull(sqlSessionFactoryBuilder, "Property'sqlSessionFactoryBuilder'is required"); | |
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null), | |
"Property'configuration'and'configLocation'can not specified with together"); | |
this.sqlSessionFactory = buildSqlSessionFactory();} | |
// 创立 SqlSessionFactory | |
// 与 XMLConfigBuilder#parseConfiguration 办法相似 | |
// 往 Configuration 对象设置全局配置信息 | |
// 最初再调用 SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration) 办法实现 MyBatis 的启动 | |
protected SqlSessionFactory buildSqlSessionFactory() throws Exception { | |
final Configuration targetConfiguration; | |
XMLConfigBuilder xmlConfigBuilder = null; | |
if (this.configuration != null) { | |
targetConfiguration = this.configuration; | |
if (targetConfiguration.getVariables() == null) {targetConfiguration.setVariables(this.configurationProperties); | |
} else if (this.configurationProperties != null) {targetConfiguration.getVariables().putAll(this.configurationProperties); | |
} | |
} else if (this.configLocation != null) {xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); | |
targetConfiguration = xmlConfigBuilder.getConfiguration();} else { | |
LOGGER.debug(() -> "Property'configuration'or'configLocation'not specified, using default MyBatis Configuration"); | |
targetConfiguration = new Configuration(); | |
Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables); | |
} | |
Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory); | |
Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory); | |
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl); | |
if (hasLength(this.typeAliasesPackage)) {scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream() | |
.filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface()) | |
.filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias); | |
} | |
if (!isEmpty(this.typeAliases)) {Stream.of(this.typeAliases).forEach(typeAlias -> {targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias); | |
LOGGER.debug(() -> "Registered type alias:'" + typeAlias + "'"); | |
}); | |
} | |
if (!isEmpty(this.plugins)) {Stream.of(this.plugins).forEach(plugin -> {targetConfiguration.addInterceptor(plugin); | |
LOGGER.debug(() -> "Registered plugin:'" + plugin + "'"); | |
}); | |
} | |
if (hasLength(this.typeHandlersPackage)) {scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass()) | |
.filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers())) | |
.forEach(targetConfiguration.getTypeHandlerRegistry()::register); | |
} | |
if (!isEmpty(this.typeHandlers)) {Stream.of(this.typeHandlers).forEach(typeHandler -> {targetConfiguration.getTypeHandlerRegistry().register(typeHandler); | |
LOGGER.debug(() -> "Registered type handler:'" + typeHandler + "'"); | |
}); | |
} | |
targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler); | |
if (!isEmpty(this.scriptingLanguageDrivers)) {Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {targetConfiguration.getLanguageRegistry().register(languageDriver); | |
LOGGER.debug(() -> "Registered scripting language driver:'" + languageDriver + "'"); | |
}); | |
} | |
Optional.ofNullable(this.defaultScriptingLanguageDriver) | |
.ifPresent(targetConfiguration::setDefaultScriptingLanguage); | |
if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls | |
try {targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource)); | |
} catch (SQLException e) {throw new IOException("Failed getting a databaseId", e); | |
} | |
} | |
Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache); | |
if (xmlConfigBuilder != null) { | |
try {xmlConfigBuilder.parse(); | |
LOGGER.debug(() -> "Parsed configuration file:'" + this.configLocation + "'"); | |
} catch (Exception ex) {throw new IOException("Failed to parse config resource:" + this.configLocation, ex); | |
} finally {ErrorContext.instance().reset();} | |
} | |
targetConfiguration.setEnvironment(new Environment(this.environment, | |
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory, | |
this.dataSource) | |
); | |
if (this.mapperLocations != null) {if (this.mapperLocations.length == 0) {LOGGER.warn(() -> "Property'mapperLocations'was specified but matching resources are not found."); | |
} else {for (Resource mapperLocation : this.mapperLocations) {if (mapperLocation == null) {continue;} | |
try {XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), | |
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments()); | |
xmlMapperBuilder.parse();} catch (Exception e) {throw new IOException("Failed to parse mapping resource:'" + mapperLocation + "'", e); | |
} finally {ErrorContext.instance().reset();} | |
LOGGER.debug(() -> "Parsed mapper file:'" + mapperLocation + "'"); | |
} | |
} | |
} else {LOGGER.debug(() -> "Property'mapperLocations'was not specified."); | |
} | |
return this.sqlSessionFactoryBuilder.build(targetConfiguration); | |
} | |
@Override | |
public SqlSessionFactory getObject() throws Exception {if (this.sqlSessionFactory == null) {afterPropertiesSet(); | |
} | |
return this.sqlSessionFactory; | |
} | |
} |
集成 MyBatis/Spring-Boot-Starter 模块
MyBatis/Spring-Boot-Starter 通过 MybatisAutoConfiguration
的 sqlSessionFactory()
办法,来主动创立 SqlSessionFactory
对象。
该办法实际上就是创立一个 SqlSessionFactoryBean
的对象,并调用其 getObject()
办法,来实现 MyBatis 的启动流程。
源码如下:
@org.springframework.context.annotation.Configuration(proxyBeanMethods = false) | |
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class}) | |
@ConditionalOnSingleCandidate(DataSource.class) | |
@EnableConfigurationProperties(MybatisProperties.class) | |
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class}) | |
public class MybatisAutoConfiguration implements InitializingBean {private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class); | |
private final MybatisProperties properties; | |
private final Interceptor[] interceptors; | |
private final TypeHandler[] typeHandlers; | |
private final LanguageDriver[] languageDrivers; | |
private final ResourceLoader resourceLoader; | |
private final DatabaseIdProvider databaseIdProvider; | |
private final List<ConfigurationCustomizer> configurationCustomizers; | |
private final List<SqlSessionFactoryBeanCustomizer> sqlSessionFactoryBeanCustomizers; | |
public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider, | |
ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider, | |
ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider, | |
ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider, | |
ObjectProvider<List<SqlSessionFactoryBeanCustomizer>> sqlSessionFactoryBeanCustomizers) { | |
this.properties = properties; | |
this.interceptors = interceptorsProvider.getIfAvailable(); | |
this.typeHandlers = typeHandlersProvider.getIfAvailable(); | |
this.languageDrivers = languageDriversProvider.getIfAvailable(); | |
this.resourceLoader = resourceLoader; | |
this.databaseIdProvider = databaseIdProvider.getIfAvailable(); | |
this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable(); | |
this.sqlSessionFactoryBeanCustomizers = sqlSessionFactoryBeanCustomizers.getIfAvailable();} | |
@Bean | |
@ConditionalOnMissingBean | |
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); | |
factory.setDataSource(dataSource); | |
if (properties.getConfiguration() == null || properties.getConfiguration().getVfsImpl() == null) {factory.setVfs(SpringBootVFS.class); | |
} | |
if (StringUtils.hasText(this.properties.getConfigLocation())) {factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation())); | |
} | |
applyConfiguration(factory); | |
if (this.properties.getConfigurationProperties() != null) {factory.setConfigurationProperties(this.properties.getConfigurationProperties()); | |
} | |
if (!ObjectUtils.isEmpty(this.interceptors)) {factory.setPlugins(this.interceptors); | |
} | |
if (this.databaseIdProvider != null) {factory.setDatabaseIdProvider(this.databaseIdProvider); | |
} | |
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage()); | |
} | |
if (this.properties.getTypeAliasesSuperType() != null) {factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType()); | |
} | |
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage()); | |
} | |
if (!ObjectUtils.isEmpty(this.typeHandlers)) {factory.setTypeHandlers(this.typeHandlers); | |
} | |
// 如果配置文件 application.yaml 的 mybatis.mapperLocations 信息不为空,那么就会设置 mapper.xml 的加载门路 | |
Resource[] mapperLocations = this.properties.resolveMapperLocations(); | |
if (!ObjectUtils.isEmpty(mapperLocations)) {factory.setMapperLocations(mapperLocations); | |
} | |
Set<String> factoryPropertyNames = Stream | |
.of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName) | |
.collect(Collectors.toSet()); | |
Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver(); | |
if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) { | |
// Need to mybatis-spring 2.0.2+ | |
factory.setScriptingLanguageDrivers(this.languageDrivers); | |
if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {defaultLanguageDriver = this.languageDrivers[0].getClass();} | |
} | |
if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) { | |
// Need to mybatis-spring 2.0.2+ | |
factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver); | |
} | |
applySqlSessionFactoryBeanCustomizers(factory); | |
// 这里实际上就是调用 SqlSessionFactoryBean 的 buildSqlSessionFactory() 办法 | |
return factory.getObject();} | |
} |