回顾
前面已经将基础支持层看完了,各种底层模块的功能
- 类型转换
- 参数解析器
- 数据库连接池
- 事务
- 缓存
核心处理层
下面开始学习核心处理层
首先,核心处理层包括6个部分
- 配置解析
- 参数映射
- SQL解析
- SQL执行
- 结果集映射
- 插件
MyBatis初始化
首先先看第一个部分,配置解析,MyBatis跟Spring一样,MyBatis也有配置文件,因此也需要去解析配置文件,而在MyBatis中的配置文件主要有两个,分别是mybatis-config.xml和映射配置文件
建造者模式
建造者模式是用来将一类复杂对象的构建过程与它的表示分离,让相同的建造工程可以有不同的表示,比如A产品和B产品的建造流程是一样的,那么A产品和B产品就可以使用同一套建造流程!这时候就是使用相同的构建过程,但表示的产品不一致了
首先建造者模式需要三个对象
- 建造者(接口):负责使用特定建造顺序去建造产品!
- 导演:调用具体的建造者去创建产品,为建造者提供数据来源!
- 产品
建造者模式的优点
- 使用导演来构建出具体的产品,但构建的细节在建造者,所以导演根本不知道创建产品的具体细节,只负责构建所需的信息传递,实现了产品对象的上层代码和产品对象的解耦,最上层的指挥完全不知道产品的建造流程,我们不需要改动指挥的代码!!!
- 建造者模式将复杂的产品的创建过程分散到了不同的构造步骤中,这样可以对产品创建过程实现更加精细的控制,同时让创建过程更加清晰,建造者将复杂产品的创建过程分开多个方法来创建,并且使用一个build方法来规定了先执行哪个方法,说白了就是先创建哪个部位
- 每个具体的构造者有产品的每个部位的创建方法,因此一个具体的构建者是可以单独创建出完整的产品对象的,而具体的建造者之间是独立的,因此当我们新增产品时,去新添加建造者即可!然后指挥调用新的建造者来build即可!
构造者模式的缺点
- 创建的产品要有共同点,组成部分相似,并且创建的流程要基本一致,如果产品很多、产品内部变化复杂、产品之间的差异性很大,是不适合使用建造者模式的,因为产品很多且内部变化复杂会导致建造者越来越多,而产品之间的差异性很大,也会导致建造者越来越多。。。。。。
初始化!BaseBuilder!
在MyBatis中,初始化过程就采用了建造者模式,说白了创建SqlSessionFactory的时候采用了建造者模式,具体的产品是SqlSessionFactory,指挥者是SqlSessionFactoryBuilder,那谁是建造者呢?看代码!
指挥者SqlSessionFactoryBuilder会调用build方法来调用建造者来进行创建SqlSessionFactory(产品),源码如下
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//建造者就是XMLConfigBuilder
//而且注意,这里建造者建造的并不是直接的SqlSessionFactory
//创建出需要的建造者
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//build方法就是指挥者去调用建造者去创建产品了!
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
可以看到,对于SqlSessionFactory这个产品的建造者就是XMLConfigBuilder对象,可能因为这个产品只有一款,所以这里没有使用接口
下面就来看一下MyBatis的建造者系列(XmlConfigBuilder是继承了BaseBuilder的,因此从BaseBuilder开始看起)
BaseBuilder其实就扮演着建造者接口的角,虽然其是一个抽象类,所有的具体建造者都是在其基础上建立的
三个关键成员属性
- configuration:存储MyBatis的所有配置信息的
- typeAliasRegistry:之前我们也看过了,这是类型别名注册中心(对Java类型取别名,比如java.lang.Integer转化为int)
- typeHandlerRegistry:类型转换器注册中心(TypeHandler,用来将数据库类型转化为Java类型的)
而且从构造方法上可以看到,typeAliasRegistry和typeHandlerRegistry都是从Configuration来的,也就是从配置文件上来的!!!
现在BaseBuilder先看到这
XMLConfigBuilder
回到我们的XMLConfigBuilder,SqlSessionFactoryBuilder就是指挥XMLConfigBuilder来进行,先来看一下XMLConfigBuilder的结构
关键的属性
- parsed:代表是否已经解析过mybatis-config.xml配置文件了(用改标志来让配置文件只被解析一次!!!)
- XPathParser:用来解析mybatis-config.xml配置文件的XPathParser对象,前面已经看过了,其底层是解析DOM树的
- environment:environment标签的default属性
- ReflectorFactory:负责创建和缓存Reflector对象的,Reflector就是用来将行记录映射成Java Bean的
可以看到执行的是build方法,而在build方法之前先会执行构造者的parse方法,下面就来看看这个构造者如何创建产品的
源码如下
public Configuration parse() {
//判断MyBatis文件是否已经解析过了
if (parsed) {
//如果已经解析过,抛错
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
//没有解析过,将标志位改为true,开始解析
parsed = true;
//在mybatis配置文件中找到configuration节点,并开始进行解析
//之前已经分析过XPathParser的evalNode方法了,就是解析该标签节点
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
- 判断是不是已经解析过了,如果解析过了,抛错
- 如果没有解析过,修改标志位,代表现在进行解析
- 使用XPathParser取解析configuration标签节点后,调用parseConfiguration去处理解析结果
parseConfiguration
该方法就是用来正式解析MyBatis总配置文件下有用的信息了,也就是configuration标签节点下的信息
该方法源码如下,可以看到,这就是parseConfiguration方法就是建造者的建造流程了!说得更具体一点是解析配置文件,然后按照建造流程去取配置文件里面的对应节点信息来创建Configuration的流程!!!
private void parseConfiguration(XNode root) {
try {
//解析configuration标签下的properties属性,组装Configuration的properties部分
propertiesElement(root.evalNode("properties"));
//获取configuration标签下的settings属性
Properties settings = settingsAsProperties(root.evalNode("settings"));
//从settings部分去设置vfsImpl字段(虚拟文件系统,之前提到过这个用来查找文件的!!)
loadCustomVfs(settings);
loadCustomLogImpl(settings);
//解析typeAliases标签,并组装Configuration的typeAliases部分
typeAliasesElement(root.evalNode("typeAliases"));
//解析plugins标签,并组装Configuration的plugins部分
pluginElement(root.evalNode("plugins"));
//解析objectFatory标签,并组装Configuration的objectFactory部分
objectFactoryElement(root.evalNode("objectFactory"));
//解析objectWrapperFactory标签,并组装Configuration的objectWrapperFactory部分
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//解析reflectorFactory标签,并组装Configuration的reflectorFactory部分
reflectorFactoryElement(root.evalNode("reflectorFactory"));
//将上面解析出的settings属性,组装Configuration的setting部分
settingsElement(settings);
//解析environments属性,组装Configuration的environments部分
environmentsElement(root.evalNode("environments"));
//解析databseIdProvider属性,组装Configuration的databaseIdProvider部分
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//解析typeHandlers属性,组装Configuration的typeHandlers部分
typeHandlerElement(root.evalNode("typeHandlers"));
//解析mappers属性,组装Configuration的mappers部分
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
可以看到,其实XMLConfigBuilder建造的产品其实是Configuration,也就是解析出来的配置文件里面的内容,并且按照一定的顺序来解析配置文件里面的标签,然后进行组装到Configuration中,最后完成创建,也就是说对于Configuration这个产品来说已经被分成几部分来完成创建了,解析组装的顺序如下
- properties标签
- typeAlias标签
- plugins标签
- objectFactory标签
- objectFactoryWrapper标签
- settings标签
- environments标签
- databaseIdProvider标签
- typeHandler标签
- mapper标签
下面就看一下,MyBatis是如何对这几个标签节点进行解析的
解析Properties标签
对应的方法为propertiesElement,源码如下
先说明一下properties标签的作用,该标签的子标签其实就是用来记录一些配置信息的,比如数据库的连接信息,账号密码。。。。。。。
private void propertiesElement(XNode context) throws Exception {
//判断properties标签节点是否为null
//如果不为null
if (context != null) {
//获取properties下面的子标签,也就是所有property标签
Properties defaults = context.getChildrenAsProperties();
//解析properties里面的resource属性
String resource = context.getStringAttribute("resource");
//解析properties里面的url属性
String url = context.getStringAttribute("url");
//url和resource不能同时存在,同时存在会抛错
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
//如果resource不为null
if (resource != null) {
//将解析resource结果存放进properties中
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
//将解析url的结果存放进properties中
defaults.putAll(Resources.getUrlAsProperties(url));
}
//
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
//将Property标签存放进XPather中
parser.setVariables(defaults);
//将Property标签存放进Configuration中
configuration.setVariables(defaults);
}
}
可以看到,propertiesElement方法会解析配置文件中的properties节点,形成Properties对象(java.util包下的,可以知道使用了Java原生解析XML获取子标签的支持),然后将Properties对象设置到XPathParser和Configuration的variables属性中
并且从代码上可以知道,MyBatis不支持URL和Resouce同时使用!!!!!!
解析settings标签
对应的方法为settingsAsProperties,源码如下
先说明一下settings标签的作用,该标签里面的子标签其实配置的是MyBatis的全局性配置,这些配置会改变MyBatis的运行行为
private Properties settingsAsProperties(XNode context) {
//判空
if (context == null) {
return new Properties();
}
//获取settings标签下的子标签,同样使用一个Properties对象存储
Properties props = context.getChildrenAsProperties();
// 创建Configuration对应的MetaClass对象
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
for (Object key : props.keySet()) {
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
//返回settings的子标签
return props;
}
可以看到,在这里并没有仅仅只是解析了settings标签而已,并没有将其添加进Configuration对象中!返回了settings标签里面的子标签,要完成settings的所有子标签的解析,后面才会往Configuration中添加settings标签
加载vfs
前面我们已经认识过VFS了,就是Virtual File System,虚拟文件系统,可以用来根据路径找到文件的,对应加载的方法为loadCustomVfs,并且注意,这里传进来放封props参数是setting的子标签,因此可以知道,对于vfs的配置是在settings的子标签下完成的
private void loadCustomVfs(Properties props) throws ClassNotFoundException {
//获取vfsImpl标签
String value = props.getProperty("vfsImpl");
//如果不为空
if (value != null) {
//遍历指定的实现类然后添加进Configuration中
String[] clazzes = value.split(",");
for (String clazz : clazzes) {
if (!clazz.isEmpty()) {
@SuppressWarnings("unchecked")
Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
configuration.setVfsImpl(vfsImpl);
}
}
}
}
可以看到,vfsImpl标签是settings的子标签第一个被解析的,仅仅只是简单的获取vfsImpl子标签的值,然后遍历vfs指定的实现类,添加进Configuration中而已
加载logImpl
MyBatis可以自定义日志使用的实现类,具体的自定义也是在settings子标签下,具体就是logImpl子标签,当加载完vfs后就到加载日志了
private void loadCustomLogImpl(Properties props) {
Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
}
源码很简单,获取logImpl子标签然后根据值去解析出对应日志实现类的Class类型,然后添加进Configuration中
解析typeAlias标签
接下来到typeAlias标签,这个不属于settings标签内,typeAlias标签就是用来处理别名与实体类的映射关系的!!!
这里传进来得到parent是typeAlias标签节点,先来说一下typeAlias标签节点的子节点
- package:package的name属性指定了包的路径,该包的路径下的所有类都创建了别名,默认以类名作为别名(真实的名字应该是全限定名)
- alias:alias的alias属性指定了别名,type为指定类的全限定名
private void typeAliasesElement(XNode parent) {
if (parent != null) {
//获取里面的子节点
for (XNode child : parent.getChildren()) {
//判断是不是package节点
if ("package".equals(child.getName())) {
//获取里面的name属性!
String typeAliasPackage = child.getStringAttribute("name");
//从Configuration中获取typeAliasRegistry(别名注册中心)
//执行registerAlias方法去注册
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
//如果不是package节点
//只能是alias节点了
//获取alias属性和type属性
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
//使用反射获取type属性指定的类Class对象
Class<?> clazz = Resources.classForName(type);
//如果别名为空
if (alias == null) {
//也会去注册,只不过此时的别名就是类名
typeAliasRegistry.registerAlias(clazz);
} else {
//别名不为空,使用指定的别名进行注册
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
可以看到typeAlias标签的完整解析,针对两个子节点,package和alias子标签进行解析
- 获取typeAlias的所有子标签
- 判断是不是package标签,如果是就将其指定包名下面的类全部进行注册,别名就是类名
- 如果不是package标签,那就默认视为alias标签(注意这里不会去检验是不是alias标签),获取alias标签的alias和type属性,进行注册,假如根据type没找到类,会抛错;假如alias属性为空,那就默认为类名!
解析plugins节点
对应的方法为pluginElement
首先说明一下plugins节点的作用,代表着插件,是MyBatis的扩展机制之一,用户可以通过添加自定义插件在SQL语句执行过程中的某一点进行拦截;而MyBatis中的自定义插件只需要实现Interceptor接口,然后通过注解指定想要拦截的方法签名即可!(后面再介绍使用)
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
//获取plugin节点下面的子标签节点
for (XNode child : parent.getChildren()) {
//获取interceptor属性,里面指定了Interceptor接口的实现类
String interceptor = child.getStringAttribute("interceptor");
//获取plugins下的properties配置的信息,形成properties对象!
Properties properties = child.getChildrenAsProperties();
//使用反射实例化出该拦截插件实例
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
//拦截插件实例注入配置的properties信息
interceptorInstance.setProperties(properties);
//添加进Configuation中!
configuration.addInterceptor(interceptorInstance);
}
}
}
- 遍历plugins节点下面的子标签节点
- 获取子标签节点的Interceptor属性,获取子标签节点的properties配置信息
- 通过Interceptor属性去实例出拦截插件的实例
- 给拦截插件实例注入进properties配置信息
- 添加进Configuration中,Configuration就会按顺序创建出一条拦截器链,进行一系列的拦截!!
解析objectFactory标签
前面已经提到过ObjectFactory的功能了,并且MyBatis也支持我们自定义ObjectFactory
对应的方法为objectFactoryElement
private void objectFactoryElement(XNode context) throws Exception {
if (context != null) {
//获取objectFactory子标签的type属性
String type = context.getStringAttribute("type");
//获取properties配置信息
Properties properties = context.getChildrenAsProperties();
//使用type属性指定的类进行反射,创建出ObjectFactory实例
ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();
//给ObjectFactory实例注入配置信息
factory.setProperties(properties);
//添加进Configuration中
configuration.setObjectFactory(factory);
}
}
跟plugins节点十分相似,都是获取type属性指定的类型来进行进行实例化创建后,注入properties配置信息,最后添加进Configuration中
解析ObjectWrapperFactory标签
前面已经提到过ObjectWrapperFactory的功能了,并且MyBatis也支持我们自定义ObjectFactory
对应的方法为ObjectWrapperFactoryElement
private void objectWrapperFactoryElement(XNode context) throws Exception {
if (context != null) {
//获取type属性
String type = context.getStringAttribute("type");
//实例化
ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance();
//添加进Configuration中
configuration.setObjectWrapperFactory(factory);
}
}
可以看到,解析ObjectWrapperFactory标签跟前面的Plugins节点和ObjectWrapper也是很接近的,唯一的不同点就是没有去解析和注入properties信息
解析reflectorFactory标签
前面已经学过reflectorFactory了,是用来管理Reflector的,也就是数据库类型与Java类型的映射关系
对应的方法为reflectorFactoryElement
private void reflectorFactoryElement(XNode context) throws Exception {
if (context != null) {
//获取Type属性
String type = context.getStringAttribute("type");
//实例化ReflectorFactory
ReflectorFactory factory = (ReflectorFactory) resolveClass(type).getDeclaredConstructor().newInstance();
//注入进Configuration
configuration.setReflectorFactory(factory);
}
}
reflectorFactory标签的解析跟前面的ObjectWrapperFactory标签是相同步骤的,都是直接获取Type属性,然后根据Type属性里面的实现类全限定类名来进行反射创建出实例,然后添加进Configuration中的
对settings剩下的子标签的解析
前面虽然已经对settings标签进行解析了,但仅仅只是注入了settings标签下的vsf和LogImpl子标签,对于其他的子标签都还没有进行解析!这个方法就是对剩下的子标签进行解析和注入进Configuration中的。。。。
解析environments标签
environments标签,顾名思义就是针对环境的不同的,同一项目可能分为开发、测试和生产多个不同的环境,而每个环境的配置可能也不尽相同,MyBatis可以去配置多个environment节点,并且每个environment节点对应一种环境的配置,尽管可以配置多个环境,但每个SqlSessionFactory实例只能选择其中一个
不过首先要认识environment标签下有什么,主要是两个对象(前面已经看过了)
- DataSourceFactory:数据源工厂
- TransactionFactory:事务工厂
对应的方法为environmentsElement
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
//如果没有指定的environment(成员属性)
if (environment == null) {
//使用environments节点里面的default属性
environment = context.getStringAttribute("default");
}
//遍历environments标签的子标签,既environment
for (XNode child : context.getChildren()) {
//获取environment的id属性
String id = child.getStringAttribute("id");
//判断id属性是否与前面指定的environment所匹配
if (isSpecifiedEnvironment(id)) {
//如果匹配,创建对应的TransactionFactory
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
//如果匹配,创建对应的DataSourceFactory
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
//获取DataSource,既数据源
DataSource dataSource = dsFactory.getDataSource();
//使用DataSource与TransactionFactory来创建出environmentBuilder对象
//这里也用了建造者模式,分开组件进行建造!!!
//transactionFactory与dataSource
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
//添加进Configuration中
configuration.setEnvironment(environmentBuilder.build());
break;
}
}
}
}
- 判空,判断environments子标签的内容是否为空
- 判断有没有定义要使用的environment,如果没有,就以environments的default属性为准
- 遍历environments下的所有environment子标签,对应判断environment的id符不符合指定的环境
- 如果符合,既找到了对应的environment,创建对应的TransactionFactory和DataSourceFactory,然后创建出EnvironmentBuilder,并添加进Configuration中
解析databaseIdProvider标签
MyBatis不像Hibernate一样,可以去屏蔽不同的SQL语言支持方面的差异,但在MyBatis的配置文件中,可以通过databaseProviderId指定该SQL语句引用的数据库产品!!!
实现的原理是:会根据前面已经确定好的DataSource来确定当前使用的数据库产品,Configuration只会存储匹配当前数据库类型的databaseId,然后在解析SQL映射配置文件时,会进行筛选,主要的筛选方式是,只加载不带databaseId的SQL语句和拥有匹配当前数据库的databaseId属性的SQL;反过来就是,带有databseId的,但与当前数据库类型不匹配的,会被舍弃掉;但如果出现带有databaseId和不带databaseId的相同SQL语句,则后者会被舍弃掉!!!!
解析databaseIdProvider标签的方法对应为databaseIdProviderElement
private void databaseIdProviderElement(XNode context) throws Exception {
//MyBatis使用DatabaseIdProvider来存储databaseIdProvider标签内容
//MyBatis提供了VendorDatabaseProvider
DatabaseIdProvider databaseIdProvider = null;
if (context != null) {
//获取里面的type属性
String type = context.getStringAttribute("type");
if ("VENDOR".equals(type)) {
type = "DB_VENDOR";
}
//解析相关的properties配置信息
Properties properties = context.getChildrenAsProperties();
//通过指定的type属性对应的实现类,来创建DatabaseIdProvider实例
databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor().newInstance();
//注入相关的properties配置信息
databaseIdProvider.setProperties(properties);
}
//获取environment属性(前面已经解析过了)
Environment environment = configuration.getEnvironment();
if (environment != null && databaseIdProvider != null) {
//筛选出匹配的databaseId
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
//将匹配的databaseId添加进Configuration中
configuration.setDatabaseId(databaseId);
}
}
- 对于databaseIdProvider,MyBatis使用DatabaseIdProvider对象来进行存储
- 获取databaseIdProvider里面的Type属性
- 解析相关的properties配置信息
- 使用反射,根据Type属性来创建出DatabaseIdProvider实例
- 从前面解析的environment标签中获取到当前dataSource
- 选出符合当前dataSource的databaseId
- 将符合的databaseId添加进Configuration中
解析typeHandlers标签
前面已经看过TypeHandler的作用了,就是用来处理类型映射的,比如java类型与jdbc类型如何对应,并且是通过TypeHandlerRegistry来注册的
private void typeHandlerElement(XNode parent) {
if (parent != null) {
//遍历所有typeHandler子节点
for (XNode child : parent.getChildren()) {
//处理package属性
if ("package".equals(child.getName())) {
String typeHandlerPackage = child.getStringAttribute("name");
//注册package包名下的TypeHandler
//(注意:TypeHandler为JdbcType,然后使用MappedType接口映射对应javaType)
typeHandlerRegistry.register(typeHandlerPackage);
} else {
//如果不是package属性
//获取javaType和jdbcType
String javaTypeName = child.getStringAttribute("javaType");
String jdbcTypeName = child.getStringAttribute("jdbcType");
//获取handler属性
//handler属性其实跟jdbcType一样
String handlerTypeName = child.getStringAttribute("handler");
//使用反射,来创建出javaType和jdbcType对应的类对象
Class<?> javaTypeClass = resolveClass(javaTypeName);
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
Class<?> typeHandlerClass = resolveClass(handlerTypeName);
if (javaTypeClass != null) {
if (jdbcType == null) {
//进行注册
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
typeHandler的解析跟TypeAlias的解析十分类似,不再赘述
解析mapper标签
最后解析的标签就是mapper标签了,用来指定我们的sql映射文件的
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
//获取mapper的子标签
for (XNode child : parent.getChildren()) {
//解析package子标签
if ("package".equals(child.getName())) {
//获取package子标签的name属性
String mapperPackage = child.getStringAttribute("name");
//添加进Configuration中
configuration.addMappers(mapperPackage);
} else {
//如果不是package子标签
//获取resource属性
String resource = child.getStringAttribute("resource");
//获取url属性
String url = child.getStringAttribute("url");
//获取class属性
String mapperClass = child.getStringAttribute("class");
//resource和url和mapperClass不能同时定义
//当url、mapperClass不存在,而resource存在时
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
//使用xmlMapperBuilder来对SQL映射配置文件进行解析
try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
}
}
//如果resource、mapperClass不存在,而url存在
else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
//同样使用XMLMapperBuilder来进行解析SQL映射配置文件
try(InputStream inputStream = Resources.getUrlAsStream(url)){
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
}
}
//如果resource和url不存在,而mapperClass存在,也就是接口的全限定名
else if (resource == null && url == null && mapperClass != null) {
//根据接口的全限定名找到接口的Class对象
Class<?> mapperInterface = Resources.classForName(mapperClass);
//添加到Configuration中,将该接口注册进MapperRegistry中
configuration.addMapper(mapperInterface);
} else {
//如果出现重复存在,或者三个都不存在,抛错
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
从代码上可以看到,主要是对mapper标签的两个子标签进行
- package:对某一包下的接口进行注册
- mapper:针对url、resource、class属性进行解析和注册(三者只能存在一个)
- 对于url和resouce都是采用XMLMapperBuilder来进行解析的,所以说url和resouce的值其实是SQL映射文件
- 对于class则是采用反射创建出接口,然后将其注册进MapperRegistry的,所以说class属性的值是接口的全限定类名
至此,MyBatis的初始化到此结束了,对mybatis-config.xml的总配置文件的解析过程到这里就结束了