开篇
过去一周忙着上线一个线上服务,没时间阅读源码,幸好服务已经顺利上线,可以抽空开始mybatis的系列了,没错这周开始准备开启mybatis的整个系列,欢迎大家订阅。
按照我现在粗浅的理解,从mybatis的使用过程来看基本可以分为三大步骤,分别是:
- 配置加载 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
- 会话建立 SqlSession sqlSession = sqlSessionFactory.openSession();
- 执行查询 User user = sqlSession.selectOne("userTest.selectUser", 1);
这篇博文主要讲解下配置加载的过程,由于配置加载涉及很多标签的加载,这篇文章暂时只分析宏观的过程,细节每个标签的加载后面文章再分析,核心需要记住mybatis的核心是 all in configuration
补充一点大家不要把mybatis和spring使用mybatis直接混在一起,因为spring和mybatis之间隔了一层spring-mybatis的实现,我们这边只讲解最纯的mybatis。
解析过程概述
在mybatis的使用过程中,我们构建一个配置的输入reader并通过SqlSessionFactoryBuilder解析配置生成SqlSessionFactory,我们的解析过程就在创建SqlSessionFactory的过程当中。
public class MyBatisTest {
public static void main(String[] args) throws Exception {
String resource = "configuration.xml";
Reader reader = Resources.getResourceAsReader(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(reader);
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = sqlSession.selectOne("userTest.selectUser", 1);
System.out.println(user.getUsername());
sqlSession.close();
}
}
配置解析过程
环境配置xml文件
mybatis的xml配置当中我认为可以分为两大块,分别是environment和mappers,前者主要是指整个mybatis连接的环境配置,后者表示mybatis写的SQL语句,配置的解析过程其实就是解析这块内容的。
关于mybatis支持的xml标签可以参考文章中的mybatis官网,相信我看了以后你一定不会后悔的,包括:
- properties
- settings
- typeAliases
- typeHandlers
- objectFactory
- plugins 插件
- environments 环境
- databaseIdProvider 数据库厂商标识
- mappers 映射器
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost/mybatis?useSSL=false" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments>
<!-- 映射文件 -->
<mappers>
<mapper resource="map/query.xml" />
<mapper resource="map/insert.xml" />
<mapper resource="map/update.xml" />
<mapper resource="map/delete.xml" />
</mappers>
</configuration>
环境配置解析源码分析
在XMLConfigBuilder类当中parseConfiguration负责解析上面的xml文件,基本上每个标签对应的都有一个解析类,这里我们就不展开了,每个标签的解析其实都是一个比较复杂的逻辑。
我们关注下mapperElement方法,这个方法是解析mappers标签引入的对应的SQL语句的xml文件,通过Resources.getResourceAsStream方法读取xml文件内容,通过mapperParser.parse()方法进行解析。
private void parseConfiguration(XNode root) {
try {
//分步骤解析
//issue #117 read properties first
//1.properties
propertiesElement(root.evalNode("properties"));
//2.类型别名
typeAliasesElement(root.evalNode("typeAliases"));
//3.插件
pluginElement(root.evalNode("plugins"));
//4.对象工厂
objectFactoryElement(root.evalNode("objectFactory"));
//5.对象包装工厂
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//6.设置
settingsElement(root.evalNode("settings"));
// read it after objectFactory and objectWrapperFactory issue #631
//7.环境
environmentsElement(root.evalNode("environments"));
//8.databaseIdProvider
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//9.类型处理器
typeHandlerElement(root.evalNode("typeHandlers"));
//10.映射器
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
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<?> 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.");
}
}
}
}
}
SQL解析
SQL的XML格式
我们通过标签<mapper resource="map/insert.xml" />引入的SQL文件的内容如下,mapper其实包含的标签个数不是特别多,可以给大家感受下:
- cache – 给定命名空间的缓存配置。
- cache-ref – 其他命名空间缓存配置的引用。
- resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
- parameterMap – 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。
- sql – 可被其他语句引用的可重用语句块。
- insert – 映射插入语句
- update – 映射更新语句
- delete – 映射删除语句
- select – 映射查询语句
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="dao.daoInterface.UserDao">
<resultMap id="UserMap" type="domain.UserEntity">
<id property="id" column="uid" />
<result property="username" column="username" />
<result property="password" column="password"/>
<result property="address" column="address"/>
<result property="createTime" column="createTime"/>
<result property="updateTime" column="updateTime"/>
<collection property="campanyEntity" resultMap="dao.daoInterface.CampanyDao.CampanyMap" />
</resultMap>
<!-- 可以将sql语句独立出来,然后引用 -->
<sql id="selectMap">
u.username, u.address ,c.campany_name
</sql>
<!-- 根据id查询用户 -->
<select id="getUserInfo" parameterType="int" resultMap="UserMap">
SELECT <include refid="selectMap"/>
FROM user u left join campany c
ON u.username = c.username
WHERE id = #{id}
ORDER BY id ASC
</select>
</mapper>
SQL解析源码分析
在XMLMapperBuilder类中通过parse进行解析,从源码可以看出来从标签/mapper开始解析,一次解析SQL对应的各类标签,针对每个标签的解析都是比较复杂的,这个暂时不展开进行分析。
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
}
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
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);
}
}
配置解析过程流程图
核心在于XMLConfiggBuilder解析环境变量和XMLMapperBuilder解析SQL变量,这个图应该还算比较清晰,通过解析后核心就生成了Configuration对象,这个是整个mybatis的核心