使用 XML 文件构建 SqlSessionFactory
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSessionFactoryBuilder:
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
解释:
1,通过输入流来构建一个 SqlSessionFactory 对象。new了一个 DefaultSqlSessionFactory 对象
build() 方法:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 根据我们传入的文件,创建一个能够解析我们当前文件的解析器
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 构建一个 SqlSessionFactory 的对象
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.
}
}
}
parser:
调用 parser.parse() 方法进入到 XMLConfigBuilder 类中:
private final XPathParser parser
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 调用解析配置的方法
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
解释:
1,调用 parser.evalNode("/configuration") 来获取 configuration 标签下的所有内容
2,调用 parseConfiguration(parser.evalNode("/configuration")); 来解析parser.evalNode("/configuration")得到的内容
首先调用 parser.evalNode("/configuration") 方法来到了 XPathParser 类中:
public XNode evalNode(String expression) {
return evalNode(document, expression);
}
public XNode evalNode(Object root, String expression) {
Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
if (node == null) {
return null;
}
return new XNode(this, node, variables);
}
解释:这个方法就是获取指定节点下的内容包括此节点。这个方法会返回一个 XNode 对象。
这个方法的作用大家可以记一下,后面的流程会多次调用此方法。
点这个view就可以看见
获取完内容之后,然后调用 parseConfiguration(XNode root) 来解析配置:
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
//typeAliases 标签 别名
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
//设置 settings 标签中能写的配置
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
//环境标签
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
// mappers 标签
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
解释:这里获取的配置,就是我们 configuration 标签中能写的配置/标签。的这些方法都是解析标签的,没什么好说的。我们说一下这个方法 mapperElement(root.evalNode("mappers"));
先看一下 mappers 标签里面能写什么:
调用 mapperElement(root.evalNode("mappers")):
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);
try(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);
try(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.");
}
}
}
}
}
解释:
1,首先判断 parent 是否为null,parent就是 mappers 标签
2,然后调用 parent.getChildren() 来获取到 mappers 标签下的所有子标签,然后循环
3,获取到子标签的名字,判断名字是什么以此来决定进入到哪个判断里面。名字为 package 进入到 if 里面,否则就进入到 else 里面(mapper)。我写的是 mapper 标签。
4,通过 child.getStringAttribute("") 方法来获取 mapper 标签上的属性内容,然后就开始判断,你写了哪个属性它就会进入到哪个判断里面,写多个属性就会抛异常。判断里面调用的方法都是一样的。我写的是 resource。
5,InputStream inputStream = Resources.getResourceAsStream(resource) 通过 resource 拿到输入流
6,XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); 又拿到一个 mapper 的解析器,然后调用这个解析器的 parse() 方法 mapperParser.parse();
XMLMapperBuilder 类中 mapperParser.parse() 方法:
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// 配置元素
configurationElement(parser.evalNode("/mapper"));
// 添加到 protected final Set loadedResources = new HashSet<>(); 中
configuration.addLoadedResource(resource);
// 通过 namespace 的值,为接口绑定映射文件
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
configurationElement(parser.evalNode("/mapper")):
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//设置当前的命名空间
builderAssistant.setCurrentNamespace(namespace);
//缓存相关
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
//parameterMap标签
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//resultMap标签 结果集映射
resultMapElements(context.evalNodes("/mapper/resultMap"));
//sql标签 避免重复sql
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);
}
}
解释:首先拿到 命名空间,如果没写,会抛一个异常。
下面这些方法都是拿到指定标签下的内容(包括指定标签),然后就设置进去。重点看一下这个方法: buildStatementFromContext(context.evalNodes("select|insert|update|delete"));:
首先调用 context.evalNodes("select|insert|update|delete") 来获取所有的增删改查标签:
public List evalNodes(String expression) {
return xpathParser.evalNodes(node, expression);
}
public List evalNodes(Object root, String expression) {
List xnodes = new ArrayList<>();
NodeList nodes = (NodeList) evaluate(expression, root, XPathConstants.NODESET);
for (int i = 0; i < nodes.getLength(); i++) {
xnodes.add(new XNode(this, nodes.item(i), variables));
}
return xnodes;
}
解释:这个方法就是获取到 mapper文件下的所有增删改查标签添加进一个list集合里并返回。
buildStatementFromContext(context.evalNodes("select|insert|update|delete")); 通过前一个方法获取到节点内容来构建 sql 语句:
private void buildStatementFromContext(List list) {
if (configuration.getDatabaseId() != null) {
//调用构建方法
buildStatementFromContext(list, configuration.getDatabaseId());
}
//调用构建方法
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List list, String requiredDatabaseId) {
for (XNode context : list) {
//获取到语句解析器 每一个标签都会有一个语句解析器
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 解析节点
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
解释:遍历刚才拿到的增删改查标签,然后又获取到一个语句解析器,通过语句解析器来解析语句节点。
语句解析器:
调用 statementParser.parseStatementNode(); 方法来到 XMLStatementBuilder 类中:
public void parseStatementNode() {
//获取到标签的id属性的值
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
//获取到标签名
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
String parameterType = context.getStringAttribute("parameterType");
Class> parameterTypeClass = resolveClass(parameterType);
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: and were parsed and removed)
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
//构建 MappedStatement 对象
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
解释:拿到增删改查标签能写的所有属性的值,然后把这些值全部传入一个方法中,并构建出 MappedStatement 对象。所以说:每一个增删改查标签就是一个 MappedStatement 对象。
调用 builderAssistant.addMappedStatement(); 方法来到 MapperBuilderAssistant 类中:
public MappedStatement addMappedStatement(...) {
...
//此方法用以构建id
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
...
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
解释:构建出一个 MappedStatement 对象,并通过 addMappedStatement() 方法放入到 configuration 对象的 mappedStatements 属性中:
public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}
解释:这个方法把 MappedStatement 对象放入mappedStatements(Map) 中,拿id充当键,值为它本身。(id属性是由 namespace 的值+方法名组成的,这也就是 dao 接口中的方法不能重载的原因)。
到这儿基本就结束了,此时就一路返回。