Mybatis加载配置信息
//读取配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//通过SqlSessionFactoryBuilder的build模式获取SqlSessionFactory
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(inputStream);
获取SqlSessionFactory
是通过SqlSessionFactoryBuilder
通过build
方法创建一个XMLConfigBuilder,
在创建的时候就会初始化Configuration
对象,这是一些别名会被注册到Configuration
的typeAliasRegistry中。最终会调用XMLConfigBuilder
类中的parseConfiguration
方法,但是在使用这个方法之前,会在parse
方法里面判断,配置文件只加载一次。根据配置文件的加载顺序,将各参数依次加载到Configuration
配置类里面。
XMLConfigBuilder
有一个父类,BaseBuilder,
几个实现类各司其职。
XMLConfigBuilder用来解析Mybatis的配置文件
XMLMapperBuilder用来及解析Mybatis中的映射文件(xxxMapeer.xml)
XMLStatementBuilder用来解析映射文件中的sql
MapperBuilderAssistant,主要是XMLMapperBuilder
的协作者。也是提供最终put到mappedStatements方法
XMLScriptBuilder 解析xml中各个节点sql部分的Builder。
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"));
loadCustomVfs(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"));
settingsElement(settings);
// 解析 environments 节点, 需要在 objectFactory 和 objectWrapperFactory才能读取
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
<!-- 方法一: 从外部指定properties配置文件, 除了使用resource属性指定外,还可通过url属性指定url -->
<properties resource="dbConfig.properties"></properties>
<properties url="file:///E:/Mybatis/src/main/resources/jdbcConfig.properties"></properties>
<!-- 方法二: 直接配置为xml -->
<properties>
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test1"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>
properties 属性可以给系统配置一些运行参数。url和resource只能存在一个。无论使用的是url或者是resource,xml类型配置的都优先加载。
settings
setting 标签的配置是配置 MyBatis 框架运行时的一些行为的,例如缓存、延迟加载、结果集控制、执行器、分页设置、命名规则、驼峰命名映射等一系列控制性参数,其所有的 setting 配置都放在父标签 settings 标签中。
typeAliases
由于类的全限定名称很长,需要大量使用的时候,总写那么长的名称不方便。在 MyBatis 中允许定义一个简写来代表这个类,这就是别名,别名分为系统定义别名和自定义别名。这些别名都会被存入configuration
的typeAliasRegistry
容器中。
<typeAliases>
<!--
通过package, 可以直接指定package的名字, mybatis会自动扫描你指定包下面的javabean,
并且默认设置一个别名,默认的名字为: javabean 的首字母小写的非限定类名来作为它的别名。
也可在javabean 加上注解@Alias 来自定义别名, 例如: @Alias(user)
<package name="xx.xx.entity"/>
-->
<typeAlias alias="UserEntity" type="xxx.xxx.xxx.User"/>
</typeAliases>
plugins
是一个可选配置,主要是为了实现自定义插件。可以拦截Executor 、StatementHandler 、ParameterHandler 、ResultSetHandler 的部分方法,处理我们自己的逻辑。一个插件拦截器如果全部拦截,执行顺序为Executor
-> StatementHandler
-> ParameterHandler
-> StatementHandler
-> ResultSetHandler
。
插件功能的实现,其实就是通过代理模式,代理上面四个对象,最终执行代理的invoke方法。
同个拦截对象,多个插件的执行顺序。主要是取决于生成代理对象的顺序。
/**
* 责任链模式
* 存放plugins的 所有字标签中Class全路径名的反射实例化对象
*/
public class InterceptorChain {
/**
* 所有拦截器的实例化对象
*/
private final List<Interceptor> interceptors = new ArrayList<>();
/**
* 将目标对象 target 通过代理方式包裹责任链,目标对象执行前,会先走一遍拦截器的过滤链
* target可以是Executor 、StatementHandler 、ParameterHandler 、ResultSetHandler
* */
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
//将目标对象进行 plugin 的 代理包装
target = interceptor.plugin(target);
}
return target;
}
/**
* 添加拦截器
*
* @param interceptor
*/
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
/**
* 获取拦截器列表
*
* @return
*/
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
上面的源码中,可以看到通过遍历interceptors
列表对target
对象进行包装。但是此时interceptors
列表最开始的插件会最先包裹到target
对象外层。所以看到的执行顺序是和配置的顺序完全相反的。
objectFactory
在创建返回结果集的时候,MyBatis 会使用一个对象工厂来完成创建这个结果集实例。默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。在默认的情况下,MyBatis 会使用其定义的对象工厂DefaultObjectFactory(org.apache.ibatis.reflection.factory.DefaultObjectFactory)来完成对应的工作。MyBatis 允许注册自定义的 ObjectFactory。如果自定义,则需要实现接口 org.apache.ibatis.reflection.factory.ObjectFactory,并给予配置。在大部分的情况下,我们都不需要自定义返回规则,因为这些比较复杂而且容易出错,在更多的情况下,都会考虑继承系统已经实现好的 DefaultObjectFactory ,通过一定的改写来完成我们所需要的工作。
objectWrapperFactory
reflectorFactory
Mybatis的反射模块,位于org.apache.ibatis.reflection包中,它对常见的反射操作做了进一步的封装,提供简单易用的API。
每一个Reflector对象都对应一个类,在Reflector中缓存了反射操作需要使用的类的元信息。
environments
可以配置多个environment
,会根据environments
节点中默认的环境加载environment
节点。主要是创建DataSource
和TransactionFactory
放置到Environment
这个类中,最终赋值给configuration
对象。事务, mybatis有两种:JDBC 和 MANAGED, 配置为JDBC则直接使用JDBC的事务,配置为MANAGED则是将事务托管给容器,
databaseIdProvider
Mybatis会根据配置的数据厂商执行不同的语句。这种多厂商的支持是基于映射语句中的 databaseId 属性。 MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库 databaseId 属性的所有语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则只会使用带databaseId 的。
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql" />
<property name="Oracle" value="oracle" />
</databaseIdProvider>
typeHandlers
<typeHandlers>
<!--
当配置package的时候,mybatis会去配置的package扫描TypeHandler
<package name="xxx.xx.demo"/>
-->
<!-- handler属性直接配置我们要指定的TypeHandler -->
<typeHandler handler=""/>
<!-- javaType 配置java类型,例如String, 如果配上javaType, 那么指定的typeHandler就只作用于指定的类型 -->
<typeHandler javaType="" handler=""/>
<!-- jdbcType 配置数据库基本数据类型,例如varchar, 如果配上jdbcType, 那么指定的typeHandler就只作用于指定的类型 -->
<typeHandler jdbcType="" handler=""/>
<!-- 也可两者都配置 -->
<typeHandler javaType="" jdbcType="" handler=""/>
</typeHandlers>
无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成 Java 类型。Mybatis默认为我们实现了许多TypeHandler, 当我们没有配置指定TypeHandler时,Mybatis会根据参数或者返回结果的不同,默认为我们选择合适的TypeHandler处理。
mappers
<mappers>
<!-- 第一种方式:通过resource指定 -->
<mapper resource="com/dy/dao/userDao.xml"/>
<!-- 第二种方式, 通过class指定接口,进而将接口与对应的xml文件形成映射关系
不过,使用这种方式必须保证 接口与mapper文件同名(不区分大小写),
我这儿接口是UserDao,那么意味着mapper文件为UserDao.xml
<mapper class="com.dy.dao.UserDao"/>
-->
<!-- 第三种方式,直接指定包,自动扫描,与方法二同理
<package name="com.dy.dao"/>
-->
<!-- 第四种方式:通过url指定mapper文件位置
<mapper url="file://........"/>
-->
</mappers>
-
MyBatis会遍历
<mappers>
下所有的子节点,如果当前遍历到的节点是<package>
,则MyBatis会将该包下的所有Mapper Class注册到configuration
的mapperRegistry
容器中。 -
如果当前节点为
<mapper>
,则会依次获取resource、url、class属性,解析映射文件,并将映射文件对应的Mapper Class注册到configuration
的mapperRegistry
容器中。 -
在解析sql之前会创建
XMLMapperBuilder
。
//解析<mapper> 节点
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
// 将configuration赋给BaseBuilder
super(configuration);
// 创建MapperBuilderAssistant对象(该对象为MapperBuilder的协助者)
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
this.parser = parser;
this.sqlFragments = sqlFragments;
this.resource = resource;
}
public void parse() {
// 若当前的Mapper.xml尚未被解析,则开始解析
// PS:若<mappers>节点下有相同的<mapper>节点,那么就无需再次解析了
if (!configuration.isResourceLoaded(resource)) {
// 解析<mapper>节点
configurationElement(parser.evalNode("/mapper"));
// 将该Mapper.xml添加至configuration的LoadedResource容器中,下回无需再解析
configuration.addLoadedResource(resource);
// 将该Mapper.xml对应的Mapper Class注册进configuration的mapperRegistry容器中
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
//最终解析mapper节点的地方
private void configurationElement(XNode context) {
try {
// 获取<mapper>节点上的namespace属性,该属性必须存在,表示当前映射文件对应的Mapper Class是谁
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 将namespace属性值赋给builderAssistant
builderAssistant.setCurrentNamespace(namespace);
// 解析<cache-ref>节点
cacheRefElement(context.evalNode("cache-ref"));
// 解析<cache>节点
cacheElement(context.evalNode("cache"));
// 解析<parameterMap>节点
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 解析<resultMap>节点
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析<sql>节点
sqlElement(context.evalNodes("/mapper/sql"));
// 解析sql语句
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
buildStatementFromContext这个方法最终XMLStatementBuilder
将处理的sql、结果集、参数、是否缓存、是否有嵌套的结果集封装到MappedStatement
最后会MapperBuilderAssistant.``addMappedStatement
方法put到mappedStatements
这个Map里面。key是命名空间+mapperId