public static void main(String[] args) throws IOException {
// 读取配置文件
InputStream is = Resources.getResourceAsStream("org/apache/ibatis/builder/MapperConfig1.xml");
// 创建SqlSessionFactory工厂
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(is);
// 使用工厂生产SqlSession对象
SqlSession session = factory.openSession();
//使用SqlSession创建Dao接口的代理对象
AuthorMapper authorMapper = session.getMapper(AuthorMapper.class);
List<Author> authors = authorMapper.selectAllAuthors();
//释放资源
session.close();
is.close();
}
下面我们来进行源码分析。
配置文件的解析主要涉及到的类如下:XMLConfigBuilder、XPathParser、XPath、XNode,其中XPath、XNode是对
1、build方法内部首先会根据输入流等信息创建XMLConfigBuilder类的实例对象,然后调用XMLConfigBuilder实例的parse方法对配置文件进行解析;这里需要注意的是parse方法最后返回的是一个Configuration对象
2、parse方法则是调用了XPath对象的evalNode方法对配置文件中的configuration节点进行解析,会把节点内容放在XNode对象中然后返回;
3、parseConfiguration方法会对configuration节点解析出来的内容再进行解析,会把解析出来的内容放在configuration对象中;实际上配置文件中的内容解析出来后都会存到Configuration中。
4、parseConfiguration方法中主要做的事如下:
/**
* 解析 properties节点
*
* 解析到org.apache.ibatis.parsing.XPathParser#variables
* org.apache.ibatis.session.Configuration#variables
*/
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
/**
* 解析我们的mybatis-config.xml中的settings节点
* 具体可以配置哪些属性:http://www.mybatis.org/mybatis-3/zh/configuration.html#settings
*
..............
*
*/
Properties settings = settingsAsProperties(root.evalNode("settings"));
/**
* 基本没有用过该属性
* VFS含义是虚拟文件系统;主要是通过程序能够方便读取本地文件系统、FTP文件系统等系统中的文件资源。
Mybatis中提供了VFS这个配置,主要是通过该配置可以加载自定义的虚拟文件系统应用程序
解析到:org.apache.ibatis.session.Configuration#vfsImpl
*/
loadCustomVfs(settings);
/**
* 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
* SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
* 解析到org.apache.ibatis.session.Configuration#logImpl
*/
loadCustomLogImpl(settings);
/**
* 解析别名
*
解析到oorg.apache.ibatis.session.Configuration#typeAliasRegistry.typeAliases
*/
typeAliasesElement(root.evalNode("typeAliases"));
/**
* 解析我们的插件(比如分页插件)
* mybatis自带的
* Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
解析到:org.apache.ibatis.session.Configuration#interceptorChain.interceptors
*/
pluginElement(root.evalNode("plugins"));
// 设置settings 和默认值
settingsElement(settings);
/**
* 解析mybatis环境
* 解析到:org.apache.ibatis.session.Configuration#environment
* 在集成spring情况下由 spring-mybatis提供数据源 和事务工厂
*/
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
/**
* 解析数据库厂商
* 解析到:org.apache.ibatis.session.Configuration#databaseId
*/
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
/**
* 解析我们的类型处理器节点
*
解析到:org.apache.ibatis.session.Configuration#typeHandlerRegistry.typeHandlerMap
*/
typeHandlerElement(root.evalNode("typeHandlers"));
/**
* 解析mapper文件(SQL映射文件)
*
resource:来注册我们的class类路径下的
url:来指定我们磁盘下的或者网络资源的
class:
若注册Mapper不带xml文件的,这里可以直接注册
若注册的Mapper带xml文件的,需要把xml文件和mapper文件同名 同路径
-->
-->
* package 1.解析mapper接口 解析到:org.apache.ibatis.session.Configuration#mapperRegistry.knownMappers
2.
*/
mapperElement(root.evalNode("mappers"));
到这里配置文件就解析完了,mybatis会根据configuration对象创建SqlSessionFactory类的对象。
上面解析全局配置文件的最后一行代码就是解析SQL映射文件的入口,下面我们来分享一下SQL映射文件的解析。
mapperElement方法中首先判断注册SQL映射的方式(通过package、resource、url还是class),然后再去解析对应的mapper文件。
parse方法中首先会判断mapper文件是否被加载过,如果被加载过就不需要再次解析了。
/**
* 方法实现说明:解析我们的 节点
* @param context document节点
*/
private void configurationElement(XNode context) {
try {
//解析namespace属性
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//保存我们当前的namespace 并且判断接口完全类名==namespace
builderAssistant.setCurrentNamespace(namespace);
//
// 解析我们的缓存引用
// 说明我当前的缓存引用和DeptMapper的缓存引用一致
//
// 解析到org.apache.ibatis.session.Configuration#cacheRefMap<当前namespace,ref-namespace>
// 异常下(引用缓存未使用缓存):org.apache.ibatis.session.Configuration#incompleteCacheRefs
cacheRefElement(context.evalNode("cache-ref"));
// 解析我们的cache节点
//
// 解析到:org.apache.ibatis.session.Configuration#caches
// org.apache.ibatis.builder.MapperBuilderAssistant#currentCache
cacheElement(context.evalNode("cache"));
//解析paramterMap节点(该节点mybaits3.5貌似不推荐使用了)
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析我们的resultMap节点
//解析到:org.apache.ibatis.session.Configuration#resultMaps
// 异常 org.apache.ibatis.session.Configuration#incompleteResultMaps
resultMapElements(context.evalNodes("/mapper/resultMap"));
/**
* 解析我们通过sql节点
* 解析到org.apache.ibatis.builder.xml.XMLMapperBuilder#sqlFragments
* 其实等于 org.apache.ibatis.session.Configuration#sqlFragments
* 因为他们是同一引用,在构建XMLMapperBuilder 时把Configuration.getSqlFragments传进去了
*/
sqlElement(context.evalNodes("/mapper/sql"));
/**
* 解析我们的select | insert |update |delete节点
* 解析到org.apache.ibatis.session.Configuration#mappedStatements
*/
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);
}
}
我们看一下buildStatementFromContext方法,这个方法最终会把节点解析成MappedStatement对象。
下面3张图都是一个方法中的代码
上面一张图中的createSqlSource方法会解析出Sql,接下来我们看下此方法是怎么解析的。
最后解析出来的sql如下图所示:
到这里sql就解析完成了。
Mybatis中关于sql解析的一些类:
Configuration中使用map存储MappedStatement对象,key是mapper接口中的方法名;MappedStatement中存储的信息如下:
sqlSource是解析出来的sql。
XMLScriptBuilder类结构如下图所示。这个类中有许多继承了NodeHandler接口的内部类,在解析动态sql的时候,会使用whereHandler去解析where节点,IfHandler解析if节点,其它的类似。
下面给出的是一条update语句,parseDynamicTags的解析流程如下:
<update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User">
UPDATE users
<trim prefix="SET" prefixOverrides=",">
<if test="name != null and name != ''">
name = #{name}
if>
<if test="age != null and age != ''">
, age = #{age}
if>
<if test="birthday != null and birthday != ''">
, birthday = #{birthday}
if>
trim>
where id = ${id}
update>
parseDynamicTags方法的返回值是一个List,也就是一个Sql节点集合