上文提到解析mapper文件,解析了cacheRef、cache、resultMap、parameterMap、sql标签,本文主要介绍mybatis是如何解析select、update、insert、delete等标签的,以及对mapper接口代理的准备工作。
1 解析sql
解析sql的过程中,mybatis会对#{}
和${}
进行相应处理,相信看完sql解析的流程,对#{}和${}的区别有一个更清晰的认识。
public class XMLMapperBuilder extends BaseBuilder {
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) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
/**
* 解析select update insert delete节点
*/
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
/**
* sql语句有问题时(比如顺序),存储到集合中,等解析完能解析的再重新解析
*/
configuration.addIncompleteStatement(statementParser);
}
}
}
}
XMLStatementBuilder#parseStatementNode mybatis使用MappedStatement封装一条<select|update|delete|insert>节点的信息, 有了它Mybatis就知道如何去调度四大组件顺利的完成用户请求
public class XMLStatementBuilder extends BaseBuilder {
/**
* 解析 #{} ${}
*/
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("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);
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);
/**
* 是否需要处理嵌套查询结果 group by
*/
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
/**
* 替换include标签为对应的sql标签里面的值
*/
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
/**
* 1.1 解析selectKey
*/
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
/**
* 解析sql #{}在此时被替换为 ? ${}未作处理
* 根据sql文本来判断是否需要动态解析 如果没有动态sql语句且只有#{}的时候,直接静态解析 使用?占位
* ${}暂不解析
*/
/**
* 实现类 XMLLanguageDriver
*/
//1.2 解析sql
//XMLLanguageDriver.createSqlSource()
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
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);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// 1.3
/**
* MappedStatement : 保存sql的所有信息
*/
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
}
1.1 解析selectKey标签
假如有些数据库不支持自增主键,或者说我们想插入自定义的主键,而又不想在业务代码中编写逻辑,那么就可以通过MyBatis的selectKey来获取。
<insert id="add" parameterType="com.demo.pojo.User">
<!--通过mybatis框架提供的selectKey标签获得自增产生的ID值-->
<selectKey resultType="java.lang.Integer" order="AFTER" keyProperty="id">
select LAST_INSERT_ID()
</selectKey>
insert into user(code,name,remark,sex)
values
(#{code},#{name},#{remark},#{sex})
</insert>
selectKey 会将 SELECT LAST_INSERT_ID()的结果放入到传入的model的主键里面,即获取数据库里自动生成的id
-
keyProperty:对应的model中的主键的属性名,跟数据库的主键对应
-
order:有2个选择:BEFORE和AFTER。
AFTER 表示 SELECT LAST_INSERT_ID() 在insert执行之后执行,多用于自增主键 BEFORE 表示 SELECT LAST_INSERT_ID() 在insert执行之前执行,这样的话就拿不到主键了,适合那种主键不是自增的类型,这样我们就可以插入自定义的主键
-
resultType:主键类型
selectKey中返回的值只能有一条数据,如果满足条件的数据有多条会报错,所以一般都是用于生成主键,确保唯一,或者在selectKey后面的语句加上条件,确保唯一
public class XMLStatementBuilder extends BaseBuilder {
private void processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) {
List<XNode> selectKeyNodes = context.evalNodes("selectKey");
if (configuration.getDatabaseId() != null) {
parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());
}
parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);
removeSelectKeyNodes(selectKeyNodes);
}
private void parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass, LanguageDriver langDriver, String skRequiredDatabaseId) {
for (XNode nodeToHandle : list) {
String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
String databaseId = nodeToHandle.getStringAttribute("databaseId");
if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) {
//处理
parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId);
}
}
}
private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) {
String resultType = nodeToHandle.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));
String keyProperty = nodeToHandle.getStringAttribute("keyProperty");
String keyColumn = nodeToHandle.getStringAttribute("keyColumn");
//
boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER"));
//defaults
boolean useCache = false;
boolean resultOrdered = false;
KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
Integer fetchSize = null;
Integer timeout = null;
boolean flushCache = false;
String parameterMap = null;
String resultMap = null;
ResultSetType resultSetTypeEnum = null;
//解析sql
SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);
SqlCommandType sqlCommandType = SqlCommandType.SELECT;
//将sql信息封装成MappedStatement对象 添加到Configuration
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);
id = builderAssistant.applyCurrentNamespace(id, false);
MappedStatement keyStatement = configuration.getMappedStatement(id, false);
configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
}
}
1.2 解析sql
XMLLanguageDriver#createSqlSource()
处理sql中包含#{}和${}的情况
public class XMLLanguageDriver implements LanguageDriver {
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
}
XMLScriptBuilder#parseScriptNode
sql解析步骤:
1、根据sql中有无${}判断是否是动态的
有${} 就是动态的
无${}(不管有没有#{}) 就是非动态的
2、${}在运行时在替换成具体的值(即拼接),此时不做任何处理
#{}在此时替换成? 运行期间由PreparedStatementHandler设置具体的值
public class XMLScriptBuilder extends BaseBuilder {
public SqlSource parseScriptNode() {
/**
* 根据sql中是否有${}来判断
* 有${} isDynamic返回true
* 无${}(不管有没有#{}) isDynamic返回false
*/
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource = null;
if (isDynamic) {
//1.2.1 解析${}
/**
* 当 SQL 配置中包含 ${}(不是 #{})占位符,或者包含 <if>、<where> 等标签时,会被认为是动态 SQL,此时使用 DynamicSqlSource 存储 SQL 片段(可能会包含很多SqlNode)
* 具体来说 ${}占位符由TextSqlNode存储
* ${} 由DynamicCheckerTokenParser来解析 其解析动作就是不解析,在运行时替换成具体的值 在parseDynamicTags()方法就完成了
*/
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
//1.2.2 解析#{}
/**
* 使用占位符方式来解析 #{} 由ParameterMappingTokenHandler.handlerToken来解析 在RawSqlSource的构造方法中解析
* #{} 现在就替换成 ? 不必等到运行期间再去替换 运行期间使用PreparedStatement来操作占位符
*/
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
}
解析dynamicTags的流程
如果是CDATA域或纯文本,调用TextSqlNode#isDynamic进行判断
public class XMLScriptBuilder extends BaseBuilder {
private boolean isDynamic;
/**
* StaticTextSqlNode 用于存储静态文本,TextSqlNode 用于存储带有 ${} 占位符的文本,
* IfSqlNode 则用于存储 <if> 节点的内容。MixedSqlNode 内部维护了一个 SqlNode 集合,用于存储各种各样的 SqlNode
* @param node
* @return
*/
protected MixedSqlNode parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<>();
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
/**
* 判断是否是cdata或纯文本
*/
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
/**
* 1.2.1.1 isDynamic():会去判断${}并做解析(实际上啥都不会做)
*/
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents);
isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
}
在看TextSqlNode#isDynamic方法之前,先来了解一下GenericTokenParser占位符解析器, GenericTokenParser 是一个通用解析器,并非只能解析 {xxx},#{xxx} 等标记。 GenericTokenParser 负责将标记中的内容抽取出来,并将标记内容交给相应的 TokenHandler 去处理。
GenericTokenParser负责查找占位符,找到占位符后交给TokenHandler处理
${} -> DynamicCheckerTokenParser#handleToken()
#{} -> ParameterMappingTokenHandler#handleToken()
public class GenericTokenParser {
private final String openToken;
private final String closeToken;
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
// search open token
int start = text.indexOf(openToken);
if (start == -1) {
return text;
}
char[] src = text.toCharArray();
int offset = 0;
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
while (start > -1) {
if (start > 0 && src[start - 1] == '\') {
// this open token is escaped. remove the backslash and continue.
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// found open token. let's search close token.
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '\') {
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {
expression.append(src, offset, end - offset);
offset = end + closeToken.length();
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
/**
* 找到#{}或${},调用handler.handleToken()完成解析
*
* 不同的handler处理的不一样 有处理${}的 有处理#{}的
*
* ${} -> DynamicCheckerTokenParser#handleToken()
* #{} -> ParameterMappingTokenHandler#handleToken()
*/
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
}
1.2.1 ${}解析
1.2.1.1 mybatis启动时${}解析
${}是在TextSqlNode#isDynamic方法中解析的。
TextSqlNode#isDynamic
创建GenericTokenParser时指定解析{},调用GenericTokenParser#parse查找文本{},找到后交给DynamicCheckerTokenParser处理
public class TextSqlNode implements SqlNode {
/**
* 判断是否是动态sql
* @return
*/
public boolean isDynamic() {
//${}对应的TokenHandler
DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
//指定判断是否动态sql的依据 即是否包含${} 重要
GenericTokenParser parser = createParser(checker);
parser.parse(text);
return checker.isDynamic();
}
private GenericTokenParser createParser(TokenHandler handler) {
return new GenericTokenParser("${", "}", handler);
}
}
DynamicCheckerTokenParser#handleToken的处理逻辑
private static class DynamicCheckerTokenParser implements TokenHandler {
private boolean isDynamic;
public DynamicCheckerTokenParser() {
// Prevent Synthetic Access
}
public boolean isDynamic() {
return isDynamic;
}
/**
* 当扫描到${}的时候调用此方法,其实就是不解析 在运行时替换成具体的值
* @param content
* @return
*/
@Override
public String handleToken(String content) {
this.isDynamic = true;
return null;
}
}
}
1.2.1.2 mapper接口方法调用时${}解析
SqlSource.getBoundSql方法在调用mapper接口时会对sql进行解析
${}
对应的SqlSource为DynamicSqlSource
,DynamicSqlSource#getBoundSql在CachingExecutor#query()方法中被调用,即在执行mapper接口中的方法时,解析${}做拼接操作
public class DynamicSqlSource implements SqlSource {
private final Configuration configuration;
private final SqlNode rootSqlNode;
@Override
public BoundSql getBoundSql(Object parameterObject) {
//创建DynamicContext
DynamicContext context = new DynamicContext(configuration, parameterObject);
/**
* 解析sql
*/
//rootSqlNode ==> MixedSqlNode
/**
* ${} 在MixedSqlNode.apply()方法中被映射
*/
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
// 调用 StaticSqlSource 的 getBoundSql 获取 BoundSql
//StaticSqlSource
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
// 将 DynamicContext 的 ContextMap 中的内容拷贝到 BoundSql 中
for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
}
return boundSql;
}
}
1.2.2 #{}解析
1.2.2.1 mybatis启动时#{}解析
#{}
对应的SqlSource为RawSqlSource
,RawSqlSource对象是在mybatis启动过程中创建的
public class RawSqlSource implements SqlSource {
//StaticSqlSource
private final SqlSource sqlSource;
public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
this(configuration, getSql(configuration, rootSqlNode), parameterType);
}
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> clazz = parameterType == null ? Object.class : parameterType;
/**
* 解析#{}
*/
//sqlSource类型为StaticSqlSource
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
}
}
在RawSqlSource的构造器中,调用SqlSourceBuilder.parse对sql进行解析
SqlSourceBuilder.parse
public class SqlSourceBuilder extends BaseBuilder {
private static final String parameterProperties = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";
public SqlSourceBuilder(Configuration configuration) {
super(configuration);
}
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
/**
#{}对应的TokenHandler
* 创建 #{} 占位符处理器
*/
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
/**
* 创建 #{} 占位符解析器
*/
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
/**
* 去找#{} 找到后调用ParameterMappingTokenHandler.handleToken()方法去解析
* 把#{}替换为?
*/
String sql = parser.parse(originalSql);
/**
* originalSql: select * from person where id = #{id}
* sql: select * from person where id = ?
*/
// 封装解析结果到 StaticSqlSource 中,并返回
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
}
ParameterMappingTokenHandler#handleToken处理#{}的流程
在mybatis启动时将 #{} 替换为 ?
private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {
private List<ParameterMapping> parameterMappings = new ArrayList<>();
private Class<?> parameterType;
private MetaObject metaParameters;
public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) {
super(configuration);
this.parameterType = parameterType;
this.metaParameters = configuration.newMetaObject(additionalParameters);
}
public List<ParameterMapping> getParameterMappings() {
return parameterMappings;
}
@Override
public String handleToken(String content) {
//构建 content 的对应的 ParameterMapping
parameterMappings.add(buildParameterMapping(content));
return "?";
}
private ParameterMapping buildParameterMapping(String content) {
/**
* 将 #{xxx} 占位符中的内容解析成 Map。大家可能很好奇一个普通的字符串是怎么解析成 Map 的,
* 举例说明一下。如下:
*
* #{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
*
* 上面占位符中的内容最终会被解析成如下的结果:
*
* {
* "property": "age",
* "typeHandler": "MyTypeHandler",
* "jdbcType": "NUMERIC",
* "javaType": "int"
* }
*
* parseParameterMapping 内部依赖 ParameterExpression 对字符串进行解析,ParameterExpression 的
* 逻辑不是很复杂,这里就不分析了。大家若有兴趣,可自行分析
*/
Map<String, String> propertiesMap = parseParameterMapping(content);
String property = propertiesMap.get("property");
Class<?> propertyType;
if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
propertyType = metaParameters.getGetterType(property);
/**
* parameterType 是运行时参数的类型。如果用户传入的是单个参数,比如 Article 对象,此时
* parameterType 为 Article.class。如果用户传入的多个参数,比如 [id = 1, author = "coolblog"],
* MyBatis 会使用 ParamMap 封装这些参数,此时 parameterType 为 ParamMap.class。如果
* parameterType 有相应的 TypeHandler,这里则把 parameterType 设为 propertyType
*/
} else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
propertyType = parameterType;
} else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
propertyType = java.sql.ResultSet.class;
} else if (property == null || Map.class.isAssignableFrom(parameterType)) {
propertyType = Object.class;
} else {
/**
* 代码逻辑走到此分支中,表明 parameterType 是一个自定义的类,
* 比如 Article,此时为该类创建一个元信息对象
*/
MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
// 检测参数对象有没有与 property 想对应的 getter 方法
if (metaClass.hasGetter(property)) {
// 获取成员变量的类型
propertyType = metaClass.getGetterType(property);
} else {
propertyType = Object.class;
}
}
//省略部分代码
}
}
1.2.2.2 调用mapper接口方法时#{} 解析
SqlSource.getBoundSql方法在调用mapper接口时会对sql进行解析
RawSqlSource#getBoundSql
public class RawSqlSource implements SqlSource {
//StaticSqlSource
private final SqlSource sqlSource;
public BoundSql getBoundSql(Object parameterObject) {
//StaticSqlSource#getBoundSql
return sqlSource.getBoundSql(parameterObject);
}
}
StaticSqlSource.getBoundSql
public class StaticSqlSource implements SqlSource {
//创建StaticSqlSource时传入的sql
private final String sql;
public BoundSql getBoundSql(Object parameterObject) {
return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}
}
1.3 将sql信息封装到MappedStatement
MapperBuilderAssistant#addMappedStatement()
public class MapperBuilderAssistant extends BaseBuilder {
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 = 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);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
//添加到全局的Configuration中
configuration.addMappedStatement(statement);
return statement;
}
}
Configuration#addMappedStatement
在执行mapper中的方法时,可以根据id从mappedStatements取出sql对应的MappedStatement对象
public class Configuration {
//添加到集合中
public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}
}
MappedStatement封装了sql的哪些信息?
public final class MappedStatement {
/**
* 对应所属mapper的资源路径,如我们示例中的CompanyMapper.xml
*/
private String resource;
/**
* mybatis全局的配置对象
*/
private Configuration configuration;
/**
* 当前MappedStatement的唯一识别ID,并且在同一个Configuration中是唯一的
* 它由Mapper类的完全限定名和Mapper方法名称拼接而成
*/
private String id;
/**
* mybatis每次从数据库中返回记录的大小,通过对该值的优化,可以提升查询效率
*/
private Integer fetchSize;
/**
* 当前MappedStatement执行时,数据库操作的超时时间
*/
private Integer timeout;
/**
* SQL声明的类型,决定当前MappedStatement由哪种类型的StatementHandler执行
* StatementType枚举有:STATEMENT, PREPARED, CALLABLE
* 默认值是:PREPARED
*/
private StatementType statementType;
/**
* 结果集处理类型,决定了结果集游标的移动方式:
* 只能向前移动、双向移动且对修改敏感、双向移动对修改不敏感。
*/
private ResultSetType resultSetType;
/**
* 存储我们定义的经过mybatis初步解析处理的sql语句,由若干sql节点构成,包含一些动态节点,如If条件语句。
* 在生成SqlSource之前,已经把<include></include>标签的内容转为了实际的文本对象
*/
private SqlSource sqlSource;
/**
* 二级缓存策略配置对象
*/
private Cache cache;
/**
* 参数映射,外部以何种形式对当前MappedStatement传参
*/
private ParameterMap parameterMap;
/**
* 结果映射列表,应该是只有一个的,不明白为啥是列表,可能是多个结果集返回时使用的。
*/
private List<ResultMap> resultMaps;
/**
* 是否要刷新缓存,将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空
* 对select命令,默认值为false,对insert、update、delete默认为true。
*/
private boolean flushCacheRequired;
/**
* 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
*/
private boolean useCache;
private boolean resultOrdered;
/**
* sql命令类型:如select、update、insert、delete等
*/
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
/**
* 语言驱动,如xml。
*/
private LanguageDriver lang;
/**
* 结果集类型列表
*/
private String[] resultSets;
}
至此,mapper对象解析完毕。
public class XMLMapperBuilder extends BaseBuilder {
public void parse() {
//判断是否解析过
if (!configuration.isResourceLoaded(resource)) {
//解析mapper.xml文件
configurationElement(parser.evalNode("/mapper"));
//代表已经解析过了
configuration.addLoadedResource(resource);
/**
* 绑定namespace里面的class对象
*/
bindMapperForNamespace();
}
/**
* 重新解析之前解析不了的节点
*/
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
}
此时,configurationElement(parser.evalNode("/mapper"))这行代码执行完了。下面就会绑定namespace里面的class对象
2 mapper接口的代理逻辑
public class XMLMapperBuilder extends BaseBuilder {
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
//根据mapper的全限路径生成对应的Class对象
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);
//添加到Configuration的mapper集合
configuration.addMapper(boundType);
}
}
}
}
}
Configuration#addMapper
public class Configuration {
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
}
public class MapperRegistry {
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
//是否已经解析过
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
/**
* k - mapper接口的Class对象
* v - MapperProxyFactory mapper代理工厂
*/
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
//解析接口的注解 前面已做过说明 这里不在重复
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
}
至此,mybatis的配置文件解析完毕,下面,我们将会分析mapper接口的代理对象的生成及其代理逻辑。