MyBatis 解析CRUD语句 - litter-fish/ReadSource GitHub Wiki
// org/apache/ibatis/builder/xml/XMLMapperBuilder.java
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
// 获取databaseId后统一解析
buildStatementFromContext(list, configuration.getDatabaseId());
}
// 获取
buildStatementFromContext(list, null);
}
// --XMLMapperBuilder
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
// 循环遍历mapper文件下的所有插入、查询、更新、删除节点
for (XNode context : list) {
// 构建解析器XMLStatementBuilder
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 委托XMLStatementBuilder进行解析
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
// 增加到incompleteStatements集合进行二次解析
configuration.addIncompleteStatement(statementParser);
}
}
}
解析映射插入、查询、更新、删除语句
// --XMLStatementBuilder
public void parseStatementNode() {
// 在命名空间中唯一的标识符,可以被用来引用这条语句。
String id = context.getStringAttribute("id");
// 数据库厂商标识(databaseIdProvider)
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);
// 外部 resultMap 的命名引用
String resultMap = context.getStringAttribute("resultMap");
// 从这条语句中返回的期望类型的类的完全限定名或别名
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
Class<?> resultTypeClass = resolveClass(resultType);
// FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖驱动)。
String resultSetType = context.getStringAttribute("resultSetType");
// STATEMENT,PREPARED 或 CALLABLE 中的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String nodeName = context.getNode().getNodeName();
// 解析SQL命令类型,目前主要有UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// insert/delete/update后是否刷新缓存
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
// select是否使用缓存
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
// 这个设置仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组了,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。
// 这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false。 TODO ?????
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
// 解析语句中包含的sql片段,也就是
// <select id="select" resultType="map">
// select
// field1, field2, field3
// <include refid="someinclude"></include>
// </select>
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
// selectKey 元素的解析
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
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 {
// (仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键
// (比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// 构造MappedStatement对象,并添加MappedStatement实例到configuration对象中
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
解析语句中包含的sql片段
// --XMLIncludeTransformer
/**
* <select id="select" resultType="map">
* select
* field1, field2, field3
* <include refid="someinclude"></include>
* </select>
*/
private void applyIncludes(Node source, final Properties variablesContext, boolean included) {
if (source.getNodeName().equals("include")) { // include
/**
* toInclude: <sql> from table </sql>
*/
Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);
Properties toIncludeContext = getVariablesContext(source, variablesContext);
// 解析sql 的子节点,并替换${xxx}
applyIncludes(toInclude, toIncludeContext, true);
if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
toInclude = source.getOwnerDocument().importNode(toInclude, true);
}
// 替换select 节点下面的 include 节点为 sql节点
source.getParentNode().replaceChild(toInclude, source);
while (toInclude.hasChildNodes()) {
// sql节点前插入替换好变量的sql
toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
}
// 删除sql节点
/**
* 最终数据:select field1, field2, field3/r/n from table /r/n
*/
toInclude.getParentNode().removeChild(toInclude);
} else if (source.getNodeType() == Node.ELEMENT_NODE) { // 非include
if (included && !variablesContext.isEmpty()) {
// replace variables in attribute values
NamedNodeMap attributes = source.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Node attr = attributes.item(i);
attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext));
}
}
/**
* 获取select节点的子节点
* select field1, field2, field3/r/n
* <include refid="someinclude"></include>
* /r/n
*
*/
NodeList children = source.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
applyIncludes(children.item(i), variablesContext, included);
}
} else if (included && source.getNodeType() == Node.TEXT_NODE // 文本节点
&& !variablesContext.isEmpty()) {
// replace variables in text node
// 替换sql 节点下的变量
source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
}
}
通过refid查找sql节点
// org/apache/ibatis/builder/xml/XMLIncludeTransformer.java
// 查找sql节点
private Node findSqlFragment(String refid, Properties variables) {
// 获取refid去掉${和}
refid = PropertyParser.parse(refid, variables);
refid = builderAssistant.applyCurrentNamespace(refid, true);
try {
XNode nodeToInclude = configuration.getSqlFragments().get(refid);
return nodeToInclude.getNode().cloneNode(true);
} catch (IllegalArgumentException e) {
throw new IncompleteElementException("Could not find SQL statement to include with refid '" + refid + "'", e);
}
}
selectKey 元素的解析
配置示例:
<selectKey keyProperty="id" resultType="int" order="BEFORE">
select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
</selectKey>
selectKey元素的属性描述
属性 | 描述 |
---|---|
keyProperty | selectKey 语句结果应该被设置的目标属性。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。 |
keyColumn | 匹配属性的返回结果集中的列名称。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表 |
resultType | 结果的类型 |
order | 这可以被设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它会首先生成主键,设置 keyProperty 然后执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 中的语句 - 这和 Oracle 数据库的行为相似,在插入语句内部可能有嵌入索引调用。 |
statementType | MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 语句的映射类型,分别代表 PreparedStatement 和 CallableStatement 类型。 |
解析:
// org/apache/ibatis/builder/xml/XMLStatementBuilder.java
private void processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) {
List<XNode> selectKeyNodes = context.evalNodes("selectKey");
if (configuration.getDatabaseId() != null) {
// 解析selectKey节点
parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());
}
parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);
// 在原来语句中移除selectKey节点
removeSelectKeyNodes(selectKeyNodes);
}
解析selectKey全部节点
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");
// databaseId是否匹配,上面一步parseSelectKeyNodes两次调用而不会出现重复在此做判断
if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) {
parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId);
}
}
}
解析selectKey某个节点
// org/apache/ibatis/builder/xml/XMLStatementBuilder.java
private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) {
// 结果的类型。
String resultType = nodeToHandle.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
// MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 语句的映射类型,分别代表 PreparedStatement 和 CallableStatement 类型。
StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));
// selectKey 语句结果应该被设置的目标属性。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
String keyProperty = nodeToHandle.getStringAttribute("keyProperty");
// 匹配属性的返回结果集中的列名称。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
String keyColumn = nodeToHandle.getStringAttribute("keyColumn");
// 这可以被设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它会首先生成主键,设置 keyProperty 然后执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 中的语句
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;
// 使用语言驱动器创建SqlSource
SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);
// 直接这种sql类型为select
SqlCommandType sqlCommandType = SqlCommandType.SELECT;
// MappedStatement 构建
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);
// 为SelectKey对应的sql语句创建并维护一个KeyGenerator
configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
}
最终解析selectKey节点的结果是在configuration对象中的keyGenerators集合中增加一个SelectKeyGenerator对象
使用语言驱动器XMLLanguageDriver解析SQL主体
默认使用:XMLLanguageDriver
// --XMLLanguageDriver
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
// 委托XMLScriptBuilder对象进行解析
return builder.parseScriptNode();
}
硬编码添加动态元素对应的处理器
// --XMLScriptBuilder
public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
super(configuration);
this.context = context;
this.parameterType = parameterType;
initNodeHandlerMap();
}
// --XMLScriptBuilder
private void initNodeHandlerMap() {
nodeHandlerMap.put("trim", new TrimHandler());
nodeHandlerMap.put("where", new WhereHandler());
nodeHandlerMap.put("set", new SetHandler());
nodeHandlerMap.put("foreach", new ForEachHandler());
nodeHandlerMap.put("if", new IfHandler());
nodeHandlerMap.put("choose", new ChooseHandler());
nodeHandlerMap.put("when", new IfHandler());
nodeHandlerMap.put("otherwise", new OtherwiseHandler());
nodeHandlerMap.put("bind", new BindHandler());
}
解析标签
// --XMLScriptBuilder
public SqlSource parseScriptNode() {
// 解析动态标签
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource = null;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
判断是否是动态的语句
isDynamic解析时序图:
最后一步的handleToken其实调用的是子类DynamicCheckerTokenParser中的实现,即设置isDynamic标识为true
// org/apache/ibatis/scripting/xmltags/TextSqlNode.java
public boolean isDynamic() {
// 创建动态标识解析器
DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
// 创建占位符解析器
GenericTokenParser parser = createParser(checker);
// 解析字符串
parser.parse(text);
return checker.isDynamic();
}
private GenericTokenParser createParser(TokenHandler handler) {
// 创建占位符解析器,GenericTokenParser 是一个通用解析器,并非只能解析 ${}
return new GenericTokenParser("${", "}", handler);
}
private static class DynamicCheckerTokenParser implements TokenHandler {
// 省略其他
@Override
public String handleToken(String content) {
// 如果存在${
this.isDynamic = true;
return null;
}
}
动态标签解析
// --XMLScriptBuilder
protected MixedSqlNode parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<SqlNode>();
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
// 文本节点
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
// 判断文本节点中是否包含了${},如果包含则为动态文本节点,否则为静态文本节点,静态文本节点在运行时不需要二次处理
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);
}
动态节点类型
节点类型解析器
if标签的解析
// --XMLScriptBuilder
private class IfHandler implements NodeHandler {
public IfHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
// 获取if属性的值,将值设置为IfSqlNode的属性,便于运行时解析
String test = nodeToHandle.getStringAttribute("test");
IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
targetContents.add(ifSqlNode);
}
}
trim标签的解析
/**
* select * from user
* <trim prefix="WHERE" prefixoverride="AND |OR">
* <if test="name != null and name.length()>0"> AND name=#{name}</if>
* <if test="gender != null and gender.length()>0"> AND gender=#{gender}</if>
* </trim>
* update user
* <trim prefix="set" suffixoverride="," suffix=" where id = #{id} ">
* <if test="name != null and name.length()>0"> name=#{name} , </if>
* <if test="gender != null and gender.length()>0"> gender=#{gender} , </if>
* </trim>
*/
private class TrimHandler implements NodeHandler {
public TrimHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
// 包含的子节点解析后SQL文本不为空时要添加的前缀内容
String prefix = nodeToHandle.getStringAttribute("prefix");
// 要覆盖的子节点解析后SQL文本前缀内容
String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");
// 包含的子节点解析后SQL文本不为空时要添加的后缀内容
String suffix = nodeToHandle.getStringAttribute("suffix");
// 要覆盖的子节点解析后SQL文本后缀内容
String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");
TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
targetContents.add(trim);
}
}
choose标签解析,子节点when使用IfHandler处理器进行解析
/**
* <choose>
* <when test="title != null">
* AND title like #{title}
* </when>
* <when test="author != null and author.name != null">
* AND author_name like #{author.name}
* </when>
* <otherwise>
* AND featured = 1
* </otherwise>
* </choose>
*/
private class ChooseHandler implements NodeHandler {
public ChooseHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
List<SqlNode> whenSqlNodes = new ArrayList<SqlNode>();
List<SqlNode> otherwiseSqlNodes = new ArrayList<SqlNode>();
// 拆分出when 和 otherwise 节点
handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes);
SqlNode defaultSqlNode = getDefaultSqlNode(otherwiseSqlNodes);
ChooseSqlNode chooseSqlNode = new ChooseSqlNode(whenSqlNodes, defaultSqlNode);
targetContents.add(chooseSqlNode);
}
private void handleWhenOtherwiseNodes(XNode chooseSqlNode, List<SqlNode> ifSqlNodes, List<SqlNode> defaultSqlNodes) {
List<XNode> children = chooseSqlNode.getChildren();
for (XNode child : children) {
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler instanceof IfHandler) {
handler.handleNode(child, ifSqlNodes);
} else if (handler instanceof OtherwiseHandler) {
handler.handleNode(child, defaultSqlNodes);
}
}
}
private SqlNode getDefaultSqlNode(List<SqlNode> defaultSqlNodes) {
SqlNode defaultSqlNode = null;
if (defaultSqlNodes.size() == 1) {
defaultSqlNode = defaultSqlNodes.get(0);
} else if (defaultSqlNodes.size() > 1) {
throw new BuilderException("Too many default (otherwise) elements in choose statement.");
}
return defaultSqlNode;
}
}
foreach标签解析
/**
* <foreach item="item" index="index" collection="list"
* open="(" separator="," close=")">
* #{item}
* </foreach>
*/
private class ForEachHandler implements NodeHandler {
public ForEachHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
String collection = nodeToHandle.getStringAttribute("collection");
String item = nodeToHandle.getStringAttribute("item");
String index = nodeToHandle.getStringAttribute("index");
String open = nodeToHandle.getStringAttribute("open");
String close = nodeToHandle.getStringAttribute("close");
String separator = nodeToHandle.getStringAttribute("separator");
ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration, mixedSqlNode, collection, index, item, open, close, separator);
targetContents.add(forEachSqlNode);
}
}
经过上面的动态标签解析,可以将sql语句拆分成不同 SqlNode 节点,最后将这些 SqlNode 节点集合封装成一个 MixedSqlNode 对象
SqlSource是XML文件或者注解方法中映射语句的实现时表示,通过SqlSourceBuilder.parse()方法创建,SqlSourceBuilder中符号解析器将mybatis中的查询参数#{}转换为?,并记录了参数的顺序。
SqlSouce类型
StaticSqlSource:最终静态SQL语句的封装,其他类型的SqlSource最终都委托给StaticSqlSource。 RawSqlSource:原始静态SQL语句的封装,在加载时就已经确定了SQL语句,没有、等动态标签和${} SQL拼接,比动态SQL语句要快,因为不需要运行时解析SQL节点。 DynamicSqlSource:动态SQL语句的封装,在运行时需要根据参数处理、等标签或者${} SQL拼接之后才能生成最后要执行的静态SQL语句。 ProviderSqlSource:当SQL语句通过指定的类和方法获取时(使用@XXXProvider注解),需要使用本类,它会通过反射调用相应的方法得到SQL语句。
如果sql语句被检测到是一个非动态的语句,则会创建一个RawSqlSource对象,RawSqlSource也是通过DynamicSqlSource创建
首先查询RawSqlSource解析时序图:
同样handleToken方法也是调用TokenHandler子类ParameterMappingTokenHandler的handleToken方法
// org/apache/ibatis/builder/SqlSourceBuilder.ParameterMappingTokenHandler
public String handleToken(String content) {
// 获取 content 的对应的 ParameterMapping
parameterMappings.add(buildParameterMapping(content));
// 返回 ?
return "?";
}
创建非动态的sql处理对象RawSqlSource
// org/apache/ibatis/scripting/defaults/RawSqlSource.java
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
// 创建SqlSourceBuilder处理器
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> clazz = parameterType == null ? Object.class : parameterType;
// #{} 占位符处理器
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());
}
委托SqlSourceBuilder处理静态语句
// org/apache/ibatis/builder/SqlSourceBuilder.java
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
// 创建 #{} 占位符处理器
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
// 创建 #{} 占位符解析器
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
// 解析 #{} 占位符,并返回解析结果
String sql = parser.parse(originalSql);
// 封装解析结果到 StaticSqlSource 中,并返回
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
ParameterMappingTokenHandler的作用:
- 将 #{xxx} 占位符中的内容解析成 Map,然后保存在parseParameterMapping集合中
- 替换#{xxx}为?占位符
将 #{xxx} 占位符中的内容解析成 Map
// org/apache/ibatis/builder/SqlSourceBuilder.ParameterMappingTokenHandler
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;
// metaParameters 为 DynamicContext 成员变量 bindings 的元信息对象
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)) {
// 如果 property 为空,或 parameterType 是 Map 类型,则将 propertyType 设为 Object.class
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;
}
}
// 将 propertyType 赋值给 javaType
ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
Class<?> javaType = propertyType;
String typeHandlerAlias = null;
// 遍历 propertiesMap
for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
String name = entry.getKey();
String value = entry.getValue();
if ("javaType".equals(name)) {
// 如果用户明确配置了 javaType,则以用户的配置为准
javaType = resolveClass(value);
builder.javaType(javaType);
} else if ("jdbcType".equals(name)) {
// 解析 jdbcType
builder.jdbcType(resolveJdbcType(value));
} else if ("mode".equals(name)) {
builder.mode(resolveParameterMode(value));
} else if ("numericScale".equals(name)) {
builder.numericScale(Integer.valueOf(value));
} else if ("resultMap".equals(name)) {
builder.resultMapId(value);
} else if ("typeHandler".equals(name)) {
typeHandlerAlias = value;
} else if ("jdbcTypeName".equals(name)) {
builder.jdbcTypeName(value);
} else if ("property".equals(name)) {
// Do Nothing
} else if ("expression".equals(name)) {
throw new BuilderException("Expression based parameters are not supported yet");
} else {
throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}. Valid properties are " + parameterProperties);
}
}
if (typeHandlerAlias != null) {
// 解析 TypeHandler
builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
}
// 构建 ParameterMapping 对象
return builder.build();
}
构造MappedStatement对象,并添加MappedStatement实例到configuration对象中,每个select|insert|update|delete语句都会创建一个
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 对象,并设置一些属性
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.addMappedStatement(statement);
return statement;
}
至此crud解析结束
最终解析mapper文件解析完成后的MappedStatement列表
具体的MappedStatement对象