这篇主要分析SqlSessionFactory的构建过程,以及mybatis mapper文件的解析
先来看SqlSessionFactory的创建过程
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.Reader,java.lang.String,java.util.Properties)
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
//委托XMLConfigBuilder来解析xml文件,并构建
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
//这里是捕获异常,包装成自己的异常并抛出的idiom?,最后还要reset ErrorContext
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
可以看到SqlSessionFactoryBuilder委托XMLConfigBuilder来解析xml文件,下面我们跟进去查看解析过程
//构造函数,转换成XPathParser再去调用构造函数
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
//构造一个需要验证,XMLMapperEntityResolver的XPathParser
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
最终调用的构造方法是:
//上面6个构造函数最后都合流到这个函数,传入XPathParser
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
//首先调用父类初始化Configuration
super(new Configuration());
//错误上下文设置成SQL Mapper Configuration(XML文件配置),以便后面出错了报错用吧
ErrorContext.instance().resource("SQL Mapper Configuration");
//将Properties全部设置到Configuration里面去
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
创建一个Configuration对象,
然后会调用父类的构造org.apache.ibatis.builder.BaseBuilder#BaseBuilder
//需要配置,类型别名注册,类型处理器注册3个东西
protected final Configuration configuration;
protected final TypeAliasRegistry typeAliasRegistry;
protected final TypeHandlerRegistry typeHandlerRegistry;
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
//映射的语句,存在Map里
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
//缓存,存在Map里
protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
这是Configuration对象的成员变量,mapper中配置的sql解析出来之后会存在mappedStatements 中
org.apache.ibatis.builder.xml.XMLConfigBuilder#parse
//解析配置
public Configuration parse() {
//如果已经解析过了,报错
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// <?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="${driver}"/>
// <property name="url" value="${url}"/>
// <property name="username" value="${username}"/>
// <property name="password" value="${password}"/>
// </dataSource>
// </environment>
// </environments>
// <mappers>
// <mapper resource="org/mybatis/example/BlogMapper.xml"/>
// </mappers>
// </configuration>
//根节点是configuration
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
调用parse方法返回Configuration,调用XPathParser解析configuration节点
org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
//解析配置
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);
}
}
我们关心的怎么解析mapper中的sql,对于其他的配置解析暂且不关注
org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement
/**
*
*
* //10.映射器
* 10.1使用类路径
* <mappers>
* <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
* <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
* <mapper resource="org/mybatis/builder/PostMapper.xml"/>
* </mappers>
*
* 10.2使用绝对url路径
* <mappers>
* <mapper url="file:///var/mappers/AuthorMapper.xml"/>
* <mapper url="file:///var/mappers/BlogMapper.xml"/>
* <mapper url="file:///var/mappers/PostMapper.xml"/>
* </mappers>
*
* 10.3使用java类名
* <mappers>
* <mapper class="org.mybatis.builder.AuthorMapper"/>
* <mapper class="org.mybatis.builder.BlogMapper"/>
* <mapper class="org.mybatis.builder.PostMapper"/>
* </mappers>
*
* 10.4自动扫描包下所有映射器
* <mappers>
* <package name="org.mybatis.builder"/>
* </mappers>
*/
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
//10.4自动扫描包下所有映射器
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) {
//10.1使用类路径
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
//映射器比较复杂,调用XMLMapperBuilder
//注意在for循环里每个mapper都重新new一个XMLMapperBuilder,来解析
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
//10.2使用绝对url路径
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
//映射器比较复杂,调用XMLMapperBuilder
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
//10.3使用java类名
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.");
}
}
}
}
}
主要是针对不同的mapper映射方式做的处理,我们就看<mapper resource="org/mybatis/builder/PostMapper.xml"/>
这种映射怎么完成解析的
//10.1使用类路径
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
//映射器比较复杂,调用XMLMapperBuilder
//注意在for循环里每个mapper都重新new一个XMLMapperBuilder,来解析
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream,configuration,resource,configuration.getSqlFragments());
mapperParser.parse();
创建一个XMLMapperBuilder 然后调用parse方法,XMLMapperBuilder 构造需要一个configuration,那么可以肯定解析完成的数据是放到configuration中的
org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
//解析
public void parse() {
//如果没有加载过再加载,防止重复加载
if (!configuration.isResourceLoaded(resource)) {
//解析mapper节点下的数据
configurationElement(parser.evalNode("/mapper"));
//标记一下,已经加载过了
configuration.addLoadedResource(resource);
//绑定映射器到namespace
bindMapperForNamespace();
}
//还有没解析完的东东这里接着解析?
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}
- 解析mapper节点下的数据,可能有resultmap statement这些节点是只能等全部解析完再去完成映射
- 把上一步解析出的 resultmap 等信息和没有完成的sql来完成对应关系
org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
//配置mapper元素
// <mapper namespace="org.mybatis.example.BlogMapper">
// <select id="selectBlog" parameterType="int" resultType="Blog">
// select * from Blog where id = #{id}
// </select>
// </mapper>
private void configurationElement(XNode context) {
try {
//1.配置namespace
String namespace = context.getStringAttribute("namespace");
if (namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
//2.配置cache-ref
cacheRefElement(context.evalNode("cache-ref"));
//3.配置cache
cacheElement(context.evalNode("cache"));
//4.配置parameterMap(已经废弃,老式风格的参数映射)
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//5.配置resultMap(高级功能)
resultMapElements(context.evalNodes("/mapper/resultMap"));
//6.配置sql(定义可重用的 SQL 代码段)
sqlElement(context.evalNodes("/mapper/sql"));
//7.配置select|insert|update|delete TODO
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
- 解析namespace属性
- 解析parameterMap
- 解析resultMap
- 解析自定义sql代码片
- 解析select节点
org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext
//7.配置select|insert|update|delete
private void buildStatementFromContext(List<XNode> list) {
//调用7.1构建语句
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List
//7.1构建语句
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
//构建所有语句,一个mapper下可以有很多select
//语句比较复杂,核心都在这里面,所以调用XMLStatementBuilder
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//核心XMLStatementBuilder.parseStatementNode
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
//如果出现SQL语句不完整,把它记下来,塞到configuration去
configuration.addIncompleteStatement(statementParser);
}
}
}
委托XMLStatementBuilder去解析
org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
//解析语句(select|insert|update|delete)
//<select
// id="selectPerson"
// parameterType="int"
// parameterMap="deprecated"
// resultType="hashmap"
// resultMap="personResultMap"
// flushCache="false"
// useCache="true"
// timeout="10000"
// fetchSize="256"
// statementType="PREPARED"
// resultSetType="FORWARD_ONLY">
// SELECT * FROM PERSON WHERE ID = #{id}
//</select>
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
//如果databaseId不匹配,退出
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
.......
//又去调助手类
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
前面省略的是具体解析select节点下resultMap等属性代码
解析完成又调助手类的addMappedStatement方法
//增加映射语句
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
//为id加上namespace前缀
id = applyCurrentNamespace(id, false);
//是否是select语句
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//又是建造者模式
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType);
statementBuilder.resource(resource);
statementBuilder.fetchSize(fetchSize);
statementBuilder.statementType(statementType);
statementBuilder.keyGenerator(keyGenerator);
statementBuilder.keyProperty(keyProperty);
statementBuilder.keyColumn(keyColumn);
statementBuilder.databaseId(databaseId);
statementBuilder.lang(lang);
statementBuilder.resultOrdered(resultOrdered);
statementBuilder.resulSets(resultSets);
setStatementTimeout(timeout, statementBuilder);
//1.参数映射
setStatementParameterMap(parameterMap, parameterType, statementBuilder);
//2.结果映射
setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);
setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);
MappedStatement statement = statementBuilder.build();
//建造好调用configuration.addMappedStatement
configuration.addMappedStatement(statement);
return statement;
}
- 为id加上namespace前缀,现在id就是 namespace + id
- 创建MappedStatement.Builder对象设置各种属性
- statementBuilder.build()产生MappedStatement对象
- 添加到configuration对象中去
org.apache.ibatis.session.Configuration#addMappedStatement
public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}
那么我们可以清晰的看到mapper中配置的select语句是通过key:namespace+id value:sql语句放到map中去的
上述的基本解析流程完成之后把{key:namespace+id value:sql}放到Configuration的map中去,完成的事情:
- 解析数据源datasource
- 解析resultmap
- 解析select update等节点
//最后一个build方法使用了一个Configuration作为参数,并返回DefaultSqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
最后返回一个DefaultSqlSessionFactory对象