加载Mapper映射文件
XMLMapperBuilder
用来解析XML中的SQL,MapperAnnotationBuilder
用来解析Mapper接口中的注解SQL。这里只分析一下XMLMapperBuilder
。
以解析 resource 为例,即解析XML文件。解析完XML文件中的SQL,还会去解析该XML文件对应的Mapper接口中的注解SQL。
// XMLConfigBuilder.java
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);
// 获取Mapper.xml的文件流
InputStream inputStream = Resources.getResourceAsStream(resource);
// 构造 XMLMapperBuilder 对象
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 解析 Mapper.xml 文件
mapperParser.parse();
}
// 省略......
}
}
}
}
XMLMapperBuilder
parse
解析 Mapper XML 配置文件
// XMLMapperBuilder.java
public void parse() {
// 判断当前 Mapper 是否已经加载过
if (!configuration.isResourceLoaded(resource)) {
// 解析 <mapper /> 节点
configurationElement(parser.evalNode("/mapper"));
// 标记该 Mapper 已经加载过
configuration.addLoadedResource(resource);
// 绑定 Mapper.xml 对应的 Mapper 接口,并解析 Mapper 接口中的SQL注解
// 第一步:
// 该方法会将Mapper接口保存到 Configuration 的 MapperRegistry 属性里面
// MapperRegistry 里面有一个 Map<Class<?>, MapperProxyFactory<?>> knownMappers
// 最终通过 knownMappers.put(type, new MapperProxyFactory<T>(type)); 注册Mapper代理
// 这里为每一个Mapper接口生成了一个 MapperProxyFactory
// 第二步:解析 Mapper 接口中SQL语句并封装成 MappedStatement 对象
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
configurationElement
// XMLMapperBuilder.java
private void configurationElement(XNode context) {
try {
// 获得 namespace 属性
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 设置 namespace 属性
builderAssistant.setCurrentNamespace(namespace);
// 解析 <cache-ref /> 节点
cacheRefElement(context.evalNode("cache-ref"));
// 解析 <cache /> 节点,缓存相关的一些属性设置
cacheElement(context.evalNode("cache"));
// 已废弃!老式风格的参数映射。
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 解析 <resultMap /> 节点
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析 <sql /> 节点
sqlElement(context.evalNodes("/mapper/sql"));
// 解析 <select /> <insert /> <update /> <delete />
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
主要分析buildStatementFromContext解析流程
buildStatementFromContext
解析
// XMLMapperBuilder.java
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
// 遍历 <select />、<insert />、<update />、<delete /> 节点们,
// 依次创建 XMLStatementBuilder 对象,执行解析
for (XNode context : list) {
// 创建 XMLStatementBuilder 对象
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 执行解析
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
XMLStatementBuilder
org.apache.ibatis.builder.xml.XMLStatementBuilder
,继承 BaseBuilder 抽象类,Statement XML 配置构建器,主要负责解析 Statement 配置,即 <select />
、<insert />
、<update />
、<delete />
标签。
构造函数
public class XMLStatementBuilder extends BaseBuilder {
// 保存当前 Mapper.xml 的一些属性信息,如namespace
private final MapperBuilderAssistant builderAssistant;
// 当前 XML 节点,例如:<select />、<insert />、<update />、<delete /> 标签
private final XNode context;
// 当前节点的 databaseId
private final String requiredDatabaseId;
public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context, String databaseId) {
super(configuration);
this.builderAssistant = builderAssistant;
this.context = context;
this.requiredDatabaseId = databaseId;
}
}
parseStatementNode
执行 Statement 解析
// XMLStatementBuilder
public void parseStatementNode() {
// 获取当前节点的 id 属性
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");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
// 获得 SQL 对应的 SqlCommandType 枚举值
// select、insert、update、delete
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
// 判断是否为 select 操作
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 获取一些属性
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// 创建 XMLIncludeTransformer 对象,并替换 <include /> 标签相关的内容
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// 解析 <selectKey /> 标签
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// 创建 SqlSource,保存了完整的SQL信息
// 此时 <selectKey> 和 <include>标签已经被解析过了并且被删除了
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
// 获得 KeyGenerator 对象
// (仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys
// 方法来取出由数据库内部生成的主键
// (比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
// 先从 configuration 中获得 KeyGenerator 对象。如果存在,说明已经配置过了
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
// 根据标签属性,判断是使用Jdbc3KeyGenerator 还是 NoKeyGenerator 对象
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// 创建 MappedStatement 对象
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
解析完节点信息后,最后会生成 MappedStatement 对象保存到Configuration中。构建MappedStatement 对象的方法在MapperBuilderAssistant类里面
MapperBuilderAssistant
构建 MappedStatement 对象
// MapperBuilderAssistant.java
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}
id = applyCurrentNamespace(id, false);
// 判断是否为 select 操作
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 创建 MappedStatement.Builder 对象
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);
// 获得 ParameterMap ,并设置到 MappedStatement.Builder 中
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
// 创建 MappedStatement 对象
MappedStatement statement = statementBuilder.build();
// 添加到 configuration 中的 Map<String, MappedStatement> mappedStatements
// key 为 id
// value 为 MappedStatement
configuration.addMappedStatement(statement);
return statement;
}
MappedStatement
org.apache.ibatis.mapping.MappedStatement
保存了每个 <select />
、<insert />
、<update />
、<delete />
节点的详细信息。
public final class MappedStatement {
// Mapper.xml配置文件名,如:UserMapper.xml
// Mapper接口文件名,如com/example/demo/UserMapper.java
private String resource;
// 全局配置
private Configuration configuration;
// 节点的id属性加命名空间,如:com.example.demo.UserMapper.getUserByUserName
private String id;
private Integer fetchSize;
private Integer timeout;
// 操作SQL的对象的类型
// STATEMENT: 直接操作SQL,不进行预编译
// PREPARED: 预处理参数,进行预编译,获取数据
// CALLABLE: 执行存储过程
private StatementType statementType;
// 返回结果类型
// FORWARD_ONLY:结果集的游标只能向下滚动
// SCROLL_INSENSITIVE:结果集的游标可以上下移动,当数据库变化时当前结果集不变
// SCROLL_SENSITIVE:返回可滚动的结果集,当数据库变化时,当前结果集同步改变
private ResultSetType resultSetType;
// sql语句
private SqlSource sqlSource;
private Cache cache;
private ParameterMap parameterMap;
// 返回结果类型
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
// sql语句的类型,如select、update、delete、insert
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
public BoundSql getBoundSql(Object parameterObject) {
// 获得 BoundSql 对象
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
// 忽略这一步,因为 <parameterMap /> 已经废弃
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.isEmpty()) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
// check for nested result maps in parameter mappings (issue #30)
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
return boundSql;
}
}
SqlSource
org.apache.ibatis.mapping.SqlSource
,代表从 Mapper XML 或接口方法注解上读取的一条 SQL 内容。代码如下:
public interface SqlSource {
// 根据用户传入的入参,返回 BoundSql 对象
BoundSql getBoundSql(Object parameterObject);
}
BoundSql
public class BoundSql {
// 保存sql语句,例如:select * from user where username = ?
private final String sql;
// 保存对#{}字符串的解析结果
private final List<ParameterMapping> parameterMappings;
// 用户传递进来的参数
private final Object parameterObject;
private final Map<String, Object> additionalParameters;
private final MetaObject metaParameters;
}
BoundSql语句的解析主要是通过对#{}字符的解析,将其替换成?。最后均包装成预表达式供PrepareStatement调用执行
#{}中的key属性以及相应的参数映射,比如javaType、jdbcType等信息均保存至BoundSql的parameterMappings属性中供最后的预表达式对象PrepareStatement赋值使用
总结
MyBatis初始化完成后,每一个接口方法都会被封装成了一个MappedStatement
对象,这个对象里面包含了执行SQL语句的详细信息。
本作品采用《CC 协议》,转载必须注明作者和本文链接