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()
方法是我们要关注的内容。
它主要干了以下几个事情:
- 解析
标签; - 解析
标签;解析过程中如果包含了 vfsImpl 的属性,那么去设置自定义的 vfs 实现类。 - 处理日志相关组件;
- 解析
标签; - 解析
标签; - 解析
标签; - 解析
标签; - 解析
标签; - 解析
标签; - 解析
标签; - 解析
标签; - 解析
标签。
源码如下:
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
// 解析 标签
propertiesElement(root.evalNode("properties"));
// 解析 标签
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 从 标签中,查找并设置自定义的 vfs(虚拟文件系统) 实现类。
loadCustomVfs(settings);
// 处理日志相关组件
loadCustomLogImpl(settings);
// 解析 标签
typeAliasesElement(root.evalNode("typeAliases"));
// 解析 标签
pluginElement(root.evalNode("plugins"));
// 解析 标签
objectFactoryElement(root.evalNode("objectFactory"));
// 解析 标签
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 解析 标签
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 从 标签中解析到的内容,设置到 Configuration 对象的字段上
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);
}
}
}
解析 properties 标签
通过调用 propertiesElement(XNode)
方法,读取
标签以及它的属性值。
标签读取属性的优先级如下:通过方法参数传递 > resource 或者 url 属性指定的配置文件 > properties 元素指定的属性。
从
标签中解析出来的 KV 信息会被记录到一个 Properties 对象(也就是 Configuration 全局配置对象的 variables 字段),在后续解析其他标签的时候,MyBatis 会使用这个 Properties 对象中记录的 KV 信息替换匹配的占位符。
解析 settings 标签
MyBatis 在解析
标签时,主要是这么做:
- 通过调用
settingsAsProperties(XNode)
方法来解析
标签,并得到Properties
对象 - 通过调用
loadCustomVfs(Properties)
方法来查找并设置自定义的 vfs 实现 - 通过调用
loadCustomLogImpl(Properties)
方法来查找并设置自定义的日志实现 - 通过调用
settingsElement(Properties)
方法,来设置Configuration
对象的信息
解析 typeAliases 标签
将
标签扫描的类 class 和别名信息,保存到 TypeAliasRegistry.typeAliases
的字段中去。
源码如下:
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
// 如果是 标签,则通过 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 标签
扫描
下的
标签,并将这些
标签对应的 class ,通过无参构造方法来创建出来。添加到 Configuration
对象的 interceptorChain
成员变量(内部维护了 List
对象的链) 下。
处理 typeHandlers 标签
源码如下:
private void typeHandlerElement(XNode parent) {
if (parent != null) {
// 处理所有 的标签
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 通过解析
标签,加载并添加 Mapper 到 Configuration
对象的 MapperRegistry
成员变量中去。
它的工作流程如下:
- 扫描
标签下的
标签,根据其package
、resource
、url
或者mapperClass
属性的值,创建出XMLMapperBuilder
。 - 调用
XMLMapperBuilder#parse()
方法,以完成
标签的解析。
源码如下:
// 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())) {
// 如果指定了 标签,那么就去扫描指定包内的全部 class
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
// 解析 子标签,这里会获取 resource、url、class 三个属性,这三个属性互斥
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
/*
如果 标签指定了 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) {
// 如果 标签指定了 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()
方法的工作流程如下:
- 解析
跟
标签的信息,来设置该 Mapper 的二级缓存 - 解析
标签 - 解析
标签 - 解析
标签 - 解析
,
,
,
标签
源码如下:
// 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 字段上
// 后续解析 标签会用到
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 标签
解析
标签的流程如下:
- 将当前 mapper 的 namespace 与
的 namespace 属性关联到configuration.cacheRef
中去 - 从
Configuration.caches
中查找
指定的缓存,并通过MapperBuilderAssistant
将当前的 mapper 的缓存设置成
指定的缓存。 - 如果
指定的缓存没有加载进来的话,那么就会抛出 IncompleteElementException 的异常,
并添加到configuration
的incompleteCacheRefs
列表中去,等到后续再添加
标签的解析相对
就简单很多了,流程如下:
- 读取
标签下的type
,eviction
,flushInterval
等属性的信息 - 根据读取到的属性,用于创建一个新的
Cache
缓存对象,然后添加到Configuration.caches
中去
2 解析 parameterMap 标签
流程如下:
- 获取
标签的id
跟type
属性。 - 解析
标签内所有的
标签的属性,如property
,javaType
,jdbcType
,resultMap
等属性。 - 将
标签解析到的属性,通过builderAssistant#buildParameterMapping()
方法创建ParameterMapping
对象。 - 创建
ParameterMap
对象,并将ParameterMapping
对象添加到ParameterMap
里面。 - 将
ParameterMap
对象添加到Configuration
对象的parameterMap
中去。
源码如下:
private void parameterMapElement(List list) {
for (XNode parameterMapNode : list) {
// 解析每个 标签的 id 、type 属性
String id = parameterMapNode.getStringAttribute("id");
String type = parameterMapNode.getStringAttribute("type");
Class> parameterClass = resolveClass(type);
List parameterNodes = parameterMapNode.evalNodes("parameter");
List parameterMappings = new ArrayList();
for (XNode parameterNode : parameterNodes) {
// 解析 的标签属性
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);
// 根据 标签的属性,创建 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 在解析
的过程中,会创建 ParameterMapping
对象出来,用来表示
标签的信息。 ParameterMapping
对象创建的流程如下:
- 生成 resultMap 的名字,名字是 mapper 的全限定名+ resultMap 的 id,用于表示该
PameterMapping
对象与 resultMap 的关联关系。 -
解析
标签对应 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 ,用于决定 的类型
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 的 type 信息,也就是 parameterMap 映射的类
* @param property 的 property 信息,也就是 parameterMap 映射的类的字段名
* @param javaType 的 javaType ,表示指定的字段名的类型
* @param jdbcType 的 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 标签
在解析
标签的过程中,会解析它的 id
跟 type
属性值,以确定 resultMap 的 id 跟 java 类型。
然后解析它的子标签,如:
,
,
,
等子标签,然后将这些子标签转换成 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 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 resultMappings = new ArrayList(additionalResultMappings);
List resultChildren = resultMapNode.getChildren();
/*
解析 resultMap 下的各个子标签,每个子标签都会生成一个 ResultMapping 对象
这些 resultMapping 对象会添加到 resultMappings 集合中去
这里涉及到 , , , , 标签
*/
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
// 解析 标签
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List flags = new ArrayList();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
// 解析 、 标签
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
// 获取 标签的 id 属性,默认值会拼装所有父标签的 id、value、property 属性值
String id = resultMapNode.getStringAttribute("id",resultMapNode.getValueBasedIdentifier());
// 获取 resultMap 的 和 属性
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
// 创建 ResultMapResolver 对象,ResultMapResolver 会根据上面解析到的 ResultMappings 集合以及 标签的属性
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 方法
该方法是解析
下的
、
跟
标签的主要入口。
源码:
// org.apache.ibatis.builder.xml.XMLMapperBuilder#buildResultMappingFromContext
private ResultMapping buildResultMappingFromContext(XNode context, Class> resultType, List flags) {
String property;
// 如果是 标签,则取 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");
// 如果是 和 标签,则进行解析
// 如果 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 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 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 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 extendedResultMappings = new ArrayList(resultMap.getResultMappings());
// 删除需要覆盖的 ResultMapping 集合
extendedResultMappings.removeAll(resultMappings);
/*
* 如果当前 标签中定义了 标签,
* 则不需要使用父 ResultMap 中记录的相应 标签,这里会将其对应的 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 标签
解析
标签的流程则比较简单,主要是扫描所有
的标签,并把它们保存到 XMLMapperBuilder
的 sqlFragments
Map 中去。其中 sqlFragments
的 key 为
标签的 id
属性,value 为
标签对应的 XNode
对象。
对应源码在 org.apache.ibatis.builder.xml.XMLMapperBuilder#sqlElement(java.util.List
方法上。
5 解析 select , insert , update , delete 的标签
工作流程如下:
- 根据标签的名字来决定 SQL 的类型,是
select
,insert
还是其他 SQL 语句,并用SqlCommandType
来表示。 - 如果标签内包含
的子标签,则将
的内容转换成 SQL 片段。 - 获取标签的 parameterType ,确定标签的参数类型。
- 处理
标签,并解析
标签来得到KeyGenerator
对象。 - 通过
LanguageDriver.createSqlSource()
方法来创建 SqlSource 对象,这个过程会解析标签内的
,
等子标签。 - 获取SQL标签中配置的 resultSets、keyProperty、keyColumn 等属性,以及前面解析
标签得到的 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 标签前,先处理 标签
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
// 将 标签的内容转换成 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);
// 处理 标签
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等属性,以及前面解析标签得到的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)
方法,来将
标签引用的
标签,转换成 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())) {
// 通过 标签的 refid 字段,找到对应的 标签,得到 Node 对象
Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);
/*
解析 标签下的 标签,将得到的键值添加到 variablesContext 集合(Properties 类型)
并形成新的 Properties 对象返回,用于替换占位符
*/
Properties toIncludeContext = getVariablesContext(source, variablesContext);
/*
递归执行 applyIncludes()方法,因为在 标签的定义中可能会使用 引用其他 SQL 片段,
在 applyIncludes()方法递归的过程中,如果遇到“${}”占位符,则使用 variablesContext 集合中的键值对进行替换;
最后,将 标签替换成 标签的内容。
*/
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
5.3 获取 SqlSource 对象
MyBatis 通过调用 getLanguageDriver()
方法,以获取 LanguageDriver
对象,并调用其 createSqlSource()
方法,以获取 SqlSource
对象。 LanguageDriver
的默认实现类是 XMLLanguageDriver
,它的 createSqlSource()
会解析
,
,
等其他标签。
具体逻辑在 org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode()
方法中。
而 XMLScriptBuilder
内部维护了 NodeHandler
接口以及其实现类,NodeHandler
负责解析动态 SQL 内的标签,生成相应的 SqlNode
对象。
以
标签为例:
private interface NodeHandler {
// 将 nodeToHandle 参数,转化成 SqlNode 对象,并添加到 targetContents 列表中去
void handleNode(XNode nodeToHandle, List targetContents);
}
private class TrimHandler implements NodeHandler {
public TrimHandler() {
}
@Override
public void handleNode(XNode nodeToHandle, List 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, InitializingBean, ApplicationListener {
// 因为 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 configurationCustomizers;
private final List sqlSessionFactoryBeanCustomizers;
public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider interceptorsProvider,
ObjectProvider typeHandlersProvider, ObjectProvider languageDriversProvider,
ResourceLoader resourceLoader, ObjectProvider databaseIdProvider,
ObjectProvider> configurationCustomizersProvider,
ObjectProvider> 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 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();
}
}
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net