您的位置:首页 > Web前端 > Node.js

MyBatis源码解读之配置

2018-06-20 19:33 309 查看

1. 目的

本文主要介绍MyBatis配置文件解析,通过源码解读mybatis-config.xml(官方默认命名)、Mapper.xml 与Java对象的映射。

2. MyBatis结构

查看大图



MyBatis结构图,原图实在太模糊了,所以重绘。

MyBatis结构图说明:

Configuration 对象初始化来源于 1个mybatis-config.xml 配置、多个Mapper注解类配置,mybatis-config.xml 包含了多个Mapper.xml配置

配置解析完成后 Configuration 包含多个Mapper对象,每一个Mapper对象包含多个MappedStatement对象

MappedStatement对象包含要执行的SQL语句、SQL操作类型,参数类型等信息

MapperdStatement接收执行参数,执行数据库调用,返回结果对象

3. Configuration 功能介绍

Configuration 是 MyBatis 的核心配置对象,是一个贯穿全剧的对象。

3.1 mybatis-config.xml 配置介绍

Configuration 与 mybatis-config.xml 文件内容一一对应,XML的配置影响着MyBatis行为。

下面具体看下mybatis-config.xml 结构



3.2 源码解析

mybatis-config.xml 对应解析类为 XMLConfigBuilder

Configuration 解析入口:

/**
* #mark 配置解析入口 #20170821
* http://www.mybatis.org/mybatis-3/zh/configuration.html * @return
*/
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}

<configuration> 子元素解析:

private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
//settings转化为properties变量
Properties settings = settingsAsProperties(root.evalNode("settings"));
//指定VFS的实现
loadCustomVfs(settings);
//解析对象别名
typeAliasesElement(root.evalNode("typeAliases"));
//插件
pluginElement(root.evalNode("plugins"));
//对象工厂(objectFactory)
objectFactoryElement(root.evalNode("objectFactory"));
//对象包装工厂
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//反射工厂
reflectorFactoryElement(root.evalNode("reflectorFactory"));
//设置settings
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
//配置环境(environments)
environmentsElement(root.evalNode("environments"));
//数据库多厂商配置
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//类型处理器配置
typeHandlerElement(root.evalNode("typeHandlers"));
//映射配置 mapper配置文件解析、注解解析
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

4. Mapper.xml配置介绍

Mapper.xml是SQL映射语句配置

Mapper.xml是配置在configuration -> mappers

4.1 Mapper.xml结构



4.2 源码解析

Mapper.xml 解析有2种方式:

XML解析方式 对应 XMLMapperBuilder

<mappers>
<mapper resource="org/apache/ibatis/builder/BlogMapper.xml"/>
<mapper url="file:./src/test/java/org/apache/ibatis/builder/NestedBlogMapper.xml"/>
</mappers>


注解方式解析 对应 MapperAnnotationBuilder

<mappers>
<mapper class="org.apache.ibatis.builder.CachedAuthorMapper"/>
<!-- 包自动检索 -->
<package name="org.apache.ibatis.builder.mapper"/>
</mappers>

mapper 解析入口:

//映射配置 mapper配置文件解析、注解解析
mapperElement(root.evalNode("mappers"));

/**
* http://www.mybatis.org/mybatis-3/zh/configuration.html#mappers * @param parent
* @throws Exception
*/
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
//包自动检索,注解式Mapper解析
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
// classpath
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) {
//注解式Mapper解析
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.");
}
}
}
}
}

4.3 XMLMapperBuilder 源码解析

/**
* 解析 mapper
* http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html */
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}

//解析未完成的,由于解析异常未找到对应资源
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}

/**
* 解析 mapper 子元素
* @param context
*/
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"));
//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);
}
}

在Mapper.xml配置中有个非常重要元素,那就是ResultMap。

官方文档定义:

resultMap 元素是 MyBatis 中最重要最强大的元素。它就是让你远离 90%的需要从结果 集中取出数据的 JDBC 代码的那个东西, 而且在一些情形下允许你做一些 JDBC 不支持的事 情。

可能resultMap很多配置参数都没在实际中使用,需要了解还是去查阅官方文档

ResultMap 解析入口

/**
* http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html#Result_Maps * resultMap 元素是 MyBatis 中最重要最强大的元素。
* @param list
* @throws Exception
*/
private void resultMapElements(List<XNode> list) throws Exception {
for (XNode resultMapNode : list) {
try {
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
//获取ResultMap类型
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
//继承ResultMap
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
//解析type
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
//
List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
resultMappings.addAll(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
//解析构造方法配置
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
//解析鉴别器配置
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List<ResultFlag> flags = new ArrayList<ResultFlag>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException  e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}

/**
* 解析嵌套 ResultMap
* @param context
* @param resultMappings
* @return
* @throws Exception
*/
private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings) throws Exception {
// association 关联对象  collection 集合对象  case(discriminator子元素) 按条件构建对象
if ("association".equals(context.getName())
|| "collection".equals(context.getName())
|| "case".equals(context.getName())) {
if (context.getStringAttribute("select") == null) {
ResultMap resultMap = resultMapElement(context, resultMappings);
return resultMap.getId();
}
}
return null;
}

/**
* SQL 映射语句解析
* @param list
* @param requiredDatabaseId
*/
private void buildStatementFromContext(List<XNode> 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);
}
}
}

4.4 XMLStatementBuilder 源码解析

XMLStatementBuilder 主要用于解析添删改查语句映射

public void parseStatementNode() {
String id = context.getStringAttribute("id");
//当前的databaseId
String databaseId = context.getStringAttribute("databaseId");
//与全局的databaseId不匹配,不解析返回
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}

Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
//官方已废除
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
//解析参数类型
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
//SQL解析驱动
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
//返回值类型
Class<?> resultTypeClass = resolveClass(resultType);
//结果集类型,默认值为 unset (依赖驱动)
String resultSetType = context.getStringAttribute("resultSetType");
//SQL语句类型,默认值:PREPARED
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

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);
//resultOrdered设置仅针对嵌套结果 select 语句适用
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

// Include Fragments before parsing
//解析include
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());

// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);

// Parse the SQL (pre: <selectKey> and <include> were parsed and removed) #mark 解析动态SQL
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
//多结果集
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
//主键生成语句id
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;
}

//构建mappedStatement并添加到Configuration对象
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

4.5 MapperAnnotationBuilder源码解析

/**
* #mark 注解Mapper方式构建
* @author Clinton Begin
*/
public class MapperAnnotationBuilder {

/** SQL注解类型 */
private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<Class<? extends Annotation>>();
/** SQL Provider 注解类型 */
private final Set<Class<? extends Annotation>> sqlProviderAnnotationTypes = new HashSet<Class<? extends Annotation>>();

private final Configuration configuration;
private final MapperBuilderAssistant assistant;
private final Class<?> type;

/**
* 构造
* @param configuration 配置对象
* @param type Mapper 类型
*/
public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
//记录解析资源文件,用于记录到上下文日志
String resource = type.getName().replace('.', '/') + ".java (best guess)";
//Mapper构建工具类
this.assistant = new MapperBuilderAssistant(configuration, resource);
this.configuration = configuration;
this.type = type;
//初始化注解
sqlAnnotationTypes.add(Select.class);
sqlAnnotationTypes.add(Insert.class);
sqlAnnotationTypes.add(Update.class);
sqlAnnotationTypes.add(Delete.class);

sqlProviderAnnotationTypes.add(SelectProvider.class);
sqlProviderAnnotationTypes.add(InsertProvider.class);
sqlProviderAnnotationTypes.add(UpdateProvider.class);
sqlProviderAnnotationTypes.add(DeleteProvider.class);
}

/**
* 注解解析
*/
public void parse() {
String resource = type.toString();
//资源是否已经加载
if (!configuration.isResourceLoaded(resource)) {
//加载Xml资源文件
loadXmlResource();
//设置到已加载资源
configuration.addLoadedResource(resource);
//设置当前命名空间
assistant.setCurrentNamespace(type.getName());
//解析缓存
parseCache();
//解析缓存引用
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237 非桥接方法
if (!method.isBridge()) {
//解析语句映射注解
parseStatement(method);
}
} catch (IncompleteElementException e) {
//异常暂存未完配置
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
//解析未完成的配置
parsePendingMethods();
}

//省略其他
}

4.6 XML 解析 和 注解解析关联

实际上我们在使用MyBatis的时候,配置文件和注解式同时使用的,具体来看看源码实现:

XMLMapperBuilder,通过获取 Mapper 配置的 namespace , 通过 namespace 反射获取到对应的 Mapper Interface , 然后在使用注解解析。

/**
* 从命名空间绑定Mapper
*/
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
//通过命名空间找到对应的Mapper Interface
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
//mapper添加到 Configuration , 内部包含注解解析
configuration.addMapper(boundType);
}
}
}
}


MapperAnnotationBuilder : 通过获取 Mapper Interface 类路径,将路径转换成xml路径 , 然后在使用Xml解析

/**
* 加载Xml配置文件
*/
private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
//将 Mapper Interface 路径转换为 xml 路径
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e) {
// ignore, resource is not required
}
if (inputStream != null) {
//解析Xml配置Mapper
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}

5. 参考资料

MyBatis官方文档 - http://www.mybatis.org/mybatis-3/zh/index.html

MyBatis-Spring官方文档 - http://www.mybatis.org/spring/zh/index.html

MyBatis源码 - https://gitee.com/rainwen/mybatis

MyBatis-Spring源码 - https://github.com/rainwen/spring

关于MyBatis源码解读之配置就介绍到这里。如有疑问,欢迎留言,谢谢。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  xNode MyBatis