3.2.2.6 sqlSource生成
先看看 SqlSource 是什么,从源码看,它就是一个接口类,只有一个方法,获取 BoundSql ,BoundSql 是真正包装sql的类,里面有原 sql 字符以及对应的参数信息。
public interface SqlSource {
BoundSql getBoundSql(Object parameterObject);
}
public class BoundSql {
private final String sql;
private final List<ParameterMapping> parameterMappings;
private final Object parameterObject;
private final Map<String, Object> additionalParameters;
private final MetaObject metaParameters;
}
章节3.2.2.5
中提到,生成SqlSource需要 langDriver,由上文得知,默认的 langDriver 是 XMLLanguageDriver,那么我们进入该类中创建SqlSource的方法
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
// 结合前面的内容,发现有很多地方用到Builder,看图`3-1`,它们的基类都是BaseBuilder
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
// 开始解析,继续往里探
return builder.parseScriptNode();
}
图3-1
走进**XMLScriptBuilder 类的 parseScriptNode **方法
private void initNodeHandlerMap() {
// 先看这个方法,看到下面的key是不是很熟悉,写sql时,经常会用到,sql中包含这些标签的,都会被当作 Dynamic 的 sqlSource,需要继续解析,还有${}包装的条件语句,也会被当作 Dynamic 的
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());
}
public SqlSource parseScriptNode() {
// 解析 Dynamic 的 sqlSource,最终拿到半成品 MixedSqlNode(组合sql节点)
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
// 是否 Dynamic,在 initNodeHandlerMap 方法中已经介绍过了
if (isDynamic) {
// 这一步就是创建了 DynamicSqlSource,并且 DynamicSqlSource 内部也没有做什么,就是对 configuration, rootSqlNode 两个赋值
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
// parameterType 是上面调用链一路带过来的,就是标签 parameterType 中设置的字符器对应的 Class,继续往里探,看后面`RawSqlSource 的创建`描述
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
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));
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
// 文本节点,也就是非<foreach>、<choose>等的标签节点
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
// 这里主要是判断语句是不是包含${},如果有,则也定义为 Dynamic,并放到contents集合中,作下一步处理
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
// Static 的,也存放到contents集合中
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
// <foreach>、<choose>、<where>等的标签节点
String nodeName = child.getNode().getNodeName();
// 取出 initNodeHandlerMap 方法中定义的 nodeName 对应的 handler,这里以<where>标签为例
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);
}
// <where>标签的继续解析
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
// 又回到parseDynamicTags方法
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
// 包装成 WhereSqlNode,也存放到contents集合中
WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
targetContents.add(where);
}
RawSqlSource 的创建
public class RawSqlSource implements SqlSource {
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 对象
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
// 参数类型
Class<?> clazz = parameterType == null ? Object.class : parameterType;
// 将sql解析后封装成sqlSource,继续下探,看`关联1`
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
}
private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
// 创建 DynamicContext 对象,DynamicContext 可以理解成拼接一个完整sql的过程类,所以叫context上下文,内部主要作用就是拼接sql和获取sql
DynamicContext context = new DynamicContext(configuration, null);
// rootSqlNode 此时应该是 MixedSqlNode,继续下探
rootSqlNode.apply(context);
// 再取出更完整的半成品sql,供下一步使用
return context.getSql();
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
return sqlSource.getBoundSql(parameterObject);
}
}
public boolean apply(DynamicContext context) {
// 遍历List,此时node对应的是 StaticTextSqlNode,继续下探
contents.forEach(node -> node.apply(context));
return true;
}
public class StaticTextSqlNode implements SqlNode {
private final String text;
public StaticTextSqlNode(String text) {
this.text = text;
}
@Override
public boolean apply(DynamicContext context) {
// 通过 DynamicContext 将sql拼接起来,内部其实就是一个StringBuilder在工作
context.appendSql(text);
return true;
}
}
// 关联1
public class SqlSourceBuilder extends BaseBuilder {
private static final String PARAMETER_PROPERTIES = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";
public SqlSourceBuilder(Configuration configuration) {
super(configuration);
}
// 这个方法其实就是把#{}包起来的部分全部解析成jdbc时PreparedStatement表示语句时的参数,用?来表示
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 返回,handler.getParameterMappings() 这个要解释一下,后续做参数赋值时需要用到,也就是维护#{}里面的参数名、类型等信息,后续设置参数值时会根据名称和类型来处理
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
}
sqlSource生成后,最终还要创建MappedStatement,前者只是把mapper中的sql都解析出来了,后者是要把整个sql相关的全部串起来,包括参数值的设置、缓存、keyGenerator、结果集等,最终执行器执行时,都跟这有关。那么,接下来看看如何生成MappedStatement,先看看 MappedStatement 有什么,能做什么。
// 部分代码省略了,比如get/set方法
public final class MappedStatement {
// 所对应的mapper文件地址,比如xxxx/xx/xx/xxMapper.xml
private String resource;
// 全局配置信息维护类
private Configuration configuration;
// id 也就是命名空间
private String id;
// 下面的这些属性值在前面的章节都有介绍,这里就不赘述了
private Integer fetchSize;
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType;
private SqlSource sqlSource;
private Cache cache;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType;
// 这个介绍下,简单点,就把它想成类似主键值的生成的行为
private KeyGenerator keyGenerator;
// keyProperty : selectKey 语句结果应该被设置到的目标属性。如果生成列不止一个,可以用逗号分隔多个属性名称
private String[] keyProperties;
// keyColumn 返回结果集中生成列属性的列名。如果生成列不止一个,可以用逗号分隔多个属性名称。
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
// resultSets 这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。一般用在存储过程调用
private String[] resultSets;
MappedStatement() {
// constructor disabled
}
public static class Builder {
// 创建 MappedStatement 对象
private MappedStatement mappedStatement = new MappedStatement();
public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
// 根据传参设置 MappedStatement 对象属性值
mappedStatement.configuration = configuration;
mappedStatement.id = id;
mappedStatement.sqlSource = sqlSource;
mappedStatement.statementType = StatementType.PREPARED;
mappedStatement.resultSetType = ResultSetType.DEFAULT;
mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<>()).build();
mappedStatement.resultMaps = new ArrayList<>();
mappedStatement.sqlCommandType = sqlCommandType;
mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
String logId = id;
if (configuration.getLogPrefix() != null) {
logId = configuration.getLogPrefix() + id;
}
mappedStatement.statementLog = LogFactory.getLog(logId);
mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
}
}
public BoundSql getBoundSql(Object parameterObject) {
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
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;
}
private static String[] delimitedStringToArray(String in) {
if (in == null || in.trim().length() == 0) {
return null;
} else {
return in.split(",");
}
}
}
介绍完 MappedStatement 后,现在从创建它的入口开始介绍,如何创建它,继续往下看,看 MapperBuilderAssistant.addMappedStatement 方法
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 创建 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);
// 获取参数映射数据,看`关联1`
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
// 将 parameterMap 赋值到 mappedStatement 中,后面有用
statementBuilder.parameterMap(statementParameterMap);
}
// 这一步也没做什么,就是把 mappedStatement.resultMaps 的List,设置为不可修改的List
MappedStatement statement = statementBuilder.build();
// 将 MappedStatement 对象,添加到 configuration 对应的 HashMap中,key = 语句id(命名空间+标签语句定义的 id),value = statement
configuration.addMappedStatement(statement);
return statement;
}
// 关联1
private ParameterMap getStatementParameterMap(
String parameterMapName,
Class<?> parameterTypeClass,
String statementId) {
parameterMapName = applyCurrentNamespace(parameterMapName, true);
ParameterMap parameterMap = null;
// 这一块是对 parameterMap 的处理,前面已经讲了,这个属性已经被废弃了,就不浪费篇章来讲了
if (parameterMapName != null) {
try {
parameterMap = configuration.getParameterMap(parameterMapName);
} catch (IllegalArgumentException e) {
throw new IncompleteElementException("Could not find parameter map " + parameterMapName, e);
}
} else if (parameterTypeClass != null) {
// 用 parameterType 设置的值的 Class 来构建一个 parameterMap,此时还没有对参数进行赋值处理
List<ParameterMapping> parameterMappings = new ArrayList<>();
parameterMap = new ParameterMap.Builder(
configuration,
statementId + "-Inline",
parameterTypeClass,
parameterMappings).build();
}
return parameterMap;
}
到此,MappedStatement 的创建已经完成了,为执行sql前的准备,全部做足了。
后续将通过抖音视频/直播的形式分享技术,由于前期要做一些准备和规划,预计2024年6月开始,欢迎关注,如有需要或问题咨询,也可直接抖音沟通交流。