先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Web前端全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip1024c (备注前端)
正文
private final String prefix; // SQL语句的前缀
private final String suffix; // SQL语句的后缀
private final List prefixesToOverride; // 待重写的前缀
private final List suffixesToOverride; // 待重写的后缀
private final Configuration configuration;
public TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, String prefixesToOverride, String suffix, String suffixesToOverride) {
this(configuration, contents, prefix, parseOverrides(prefixesToOverride), suffix, parseOverrides(suffixesToOverride));
}
protected TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, List prefixesToOverride, String suffix, List suffixesToOverride) {
this.contents = contents;
this.prefix = prefix;
this.prefixesToOverride = prefixesToOverride;
this.suffix = suffix;
this.suffixesToOverride = suffixesToOverride;
this.configuration = configuration;
}
@Override
public boolean apply(DynamicContext context) {
// 创建过滤动态上下文
FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
// 调用委托的SqlNode的apply()方法来解析FilteredDynamicContext
boolean result = contents.apply(filteredDynamicContext);
// 过滤掉脚本中的prefixesToOverride和suffixesToOverride部分
filteredDynamicContext.applyAll();
return result;
}
private static List parseOverrides(String overrides) {
if (overrides != null) {
final StringTokenizer parser = new StringTokenizer(overrides, “|”, false);
final List list = new ArrayList<>(parser.countTokens());
while (parser.hasMoreTokens()) {
list.add(parser.nextToken().toUpperCase(Locale.ENGLISH));
}
return list;
}
return Collections.emptyList();
}
private class FilteredDynamicContext extends DynamicContext {
private DynamicContext delegate;
private boolean prefixApplied;
private boolean suffixApplied;
private StringBuilder sqlBuffer;
public FilteredDynamicContext(DynamicContext delegate) {
super(configuration, null);
this.delegate = delegate;
this.prefixApplied = false;
this.suffixApplied = false;
this.sqlBuffer = new StringBuilder();
}
public void applyAll() {
sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
if (trimmedUppercaseSql.length() > 0) {
applyPrefix(sqlBuffer, trimmedUppercaseSql);
applySuffix(sqlBuffer, trimmedUppercaseSql);
}
delegate.appendSql(sqlBuffer.toString());
}
@Override
public Map<String, Object> getBindings() {
return delegate.getBindings();
}
@Override
public void bind(String name, Object value) {
delegate.bind(name, value);
}
@Override
public int getUniqueNumber() {
return delegate.getUniqueNumber();
}
@Override
public void appendSql(String sql) {
sqlBuffer.append(sql);
}
@Override
public String getSql() {
return delegate.getSql();
}
private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
if (!prefixApplied) {
prefixApplied = true;
if (prefixesToOverride != null) {
// 将sql中开头部分的prefixesToOverride删掉
for (String toRemove : prefixesToOverride) {
if (trimmedUppercaseSql.startsWith(toRemove)) {
sql.delete(0, toRemove.trim().length());
break;
}
}
}
if (prefix != null) {
sql.insert(0, " ");
sql.insert(0, prefix);
}
}
}
private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) {
if (!suffixApplied) {
suffixApplied = true;
if (suffixesToOverride != null) {
// 将sql中结尾部分的suffixesToOverride删掉
for (String toRemove : suffixesToOverride) {
if (trimmedUppercaseSql.endsWith(toRemove) || trimmedUppercaseSql.endsWith(toRemove.trim())) {
int start = sql.length() - toRemove.trim().length();
int end = sql.length();
sql.delete(start, end);
break;
}
}
}
if (suffix != null) {
sql.append(" ");
sql.append(suffix);
}
}
}
}
}
然后来看WhereSqlNode的实现,代码如下:
public class WhereSqlNode extends TrimSqlNode {
private static List prefixList = Arrays.asList("AND ","OR ",“AND\n”, “OR\n”, “AND\r”, “OR\r”, “AND\t”, “OR\t”);
public WhereSqlNode(Configuration configuration, SqlNode contents) {
// SQL脚本中的前缀是WHERE,需要
super(configuration, contents, “WHERE”, prefixList, null, null);
}
}
在WhereSqlNode对象的apply()函数中,先调用委托SqlNode对象的apply()函数,然后执行FilteredDynamicContext的函数applyAll()时,会将SQL脚本中开头的AND和OR删掉。
public class SetSqlNode extends TrimSqlNode {
private static final List COMMA = Collections.singletonList(“,”);
public SetSqlNode(Configuration configuration,SqlNode contents) {
super(configuration, contents, “SET”, COMMA, null, COMMA);
}
}
在SetSqlNode对象的apply()函数中,先调用委托SqlNode对象的apply()函数,然后执行FilteredDynamicContext的函数applyAll()时,会将SQL脚本中开头和结尾的","符号删掉。
最后我们了解一下实现动态SQL比较关键的SqlNode实现类之一——IfSqlNode的实现,代码如下:
public class IfSqlNode implements SqlNode {
// evaluator属性用于解析OGNL表达式
private final ExpressionEvaluator evaluator;
// 保存标签test属性内容
private final String test;
// 标签内的SQL内容
private final SqlNode contents;
public IfSqlNode(SqlNode contents, String test) {
this.test = test;
this.contents = contents;
this.evaluator = new ExpressionEvaluator();
}
@Override
public boolean apply(DynamicContext context) {
// 如果OGNL表达式值为true,则调用标签内容对应的SqlNode的apply()方法
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}
}
如上面的代码所示,IfSqlNode中维护了一个ExpressionEvaluator类的实例,该实例用于根据当前参数对象解析OGNL表达式。另外,IfSqlNode维护了标签test属性指定的表达式内容和标签中的SQL内容对应的SqlNode对象。在IfSqlNode类的apply()方法中,首先解析test属性指定的OGNL表达式,只有当表达式值为true的情况下,才会执行标签中SQL内容对应的SqlNode的apply()方法。这样就实现了只有当标签test属性表达式值为true的情况下,才会追加标签中配置的SQL信息。
动态SQL解析过程
SqlSource用于描述通过XML文件或者Java注解配置的SQL资源信息;SqlNode用于描述动态SQL中、等标签信息;LanguageDriver用于对Mapper SQL配置进行解析,将SQL配置转换为SqlSource对象。要了解MyBatis动态SQL的解析过程,我们可以从XMLLanguageDriver类的createSqlSource()方法出发进行分析,该方法代码如下:
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
如上面的代码所示,在XMLLanguageDriver类createSqlSource()方法中,Mapper SQL配置的解析实际上是委托给XMLScriptBuilder类来完成的,该方法中首先创建了一个XMLScriptBuilder对象,然后调用XMLScriptBuilder对象的parseScriptNode()方法完成解析工作。XMLScriptBuilder类的构造函数如下:
public class XMLScriptBuilder extends BaseBuilder {
private final XNode context;
private boolean isDynamic;
private final Class<?> parameterType;
private final Map<String, NodeHandler> nodeHandlerMap = new HashMap<>();
public XMLScriptBuilder(Configuration configuration, XNode context) {
this(configuration, context, null);
}
public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
super(configuration);
this.context = context;
this.parameterType = parameterType;
initNodeHandlerMap();
}
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类的parseScriptNode()方法代码如下:
public SqlSource parseScriptNode() {
// 调用parseDynamicTags()方法将SQL配置转换为SqlNode对象
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
// 判断Mapper SQL配置中是否包含动态SQL元素,如果是,就创建DynamicSqlSource对象,否则创建RawSqlSource对象
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
如上面的代码所示,在XMLScriptBuilder类的parseScriptNode()方法中,调用parseDynamicTags()方法将SQL配置转换为SqlNode对象,然后判断SQL配置是否为动态SQL,如果为动态SQL,则创建DynamicSqlSource对象,否则创建RawSqlSource对象。需要注意的是,MyBatis中判断SQL配置是否属于动态SQL的标准是SQL配置是否包含、、等元素或者${}参数占位符。
接下来,我们再来看一下XMLScriptBuilder类的parseDynamicTags()方法的实现,代码如下:
protected MixedSqlNode parseDynamicTags(XNode node) {
List contents = new ArrayList<>();
NodeList children = node.getNode().getChildNodes();
// 对XML子元素进行遍历
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
// 如果子元素为SQL文本内容,则使用TextSqlNode描述该节点
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody(“”);
TextSqlNode textSqlNode = new TextSqlNode(data);
// 若SQL脚本中包含KaTeX parse error: Expected '}', got 'EOF' at end of input: … {
// 如果SQL中不包含{}参数占位符,则不是动态SQL
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
// 如果子元素为、等标签,则使用对应的NodeHandler处理
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);
}
如上面的代码所示,XMLScriptBuilder类的parseDynamicTags()方法的逻辑相当复杂,在该方法中对SQL配置的所有子元素进行遍历,如果子元素类型为SQL文本,则使用TextSqlNode对象描述SQL节点信息,若SQL节点中存在${}参数占位符,则设置XMLScriptBuilder对象的isDynamic属性值为true;如果子元素为、等标签,则使用对应的NodeHandler处理。
XMLScriptBuilder类中定义了一个私有的NodeHandler接口,并为每种动态SQL标签提供了一个NodeHandler接口的实现类,通过实现类处理对应的动态SQL标签,把动态SQL标签转换为对应的SqlNode对象。
XMLScriptBuilder类中为NodeHandler接口提供了8个实现类,每个实现类用于处理对应的动态SQL标签,例如IfHandler用于处理动态SQL配置中的标签,将标签内容转换为IfSqlNode对象。
接下来我们来看一下NodeHandler接口的定义,代码如下:
private interface NodeHandler {
void handleNode(XNode nodeToHandle, List targetContents);
}
如上面的代码所示,NodeHandler接口中只有一个handleNode()方法,该方法接收一个动态SQL标签对应的XNode对象和一个存放SqlNode对象的List对象,handleNode()方法中对XML标签进行解析后,把生成的SqlNode对象添加到List对象中。我们可以参考一下IfHandler类的实现,代码如下:
private class IfHandler implements NodeHandler {
public IfHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List targetContents) {
// 继续调用parseDynamicTags方法解析标签中的子节点
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
// 获取标签test属性
String test = nodeToHandle.getStringAttribute(“test”);
IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
// 将IfSqlNode对象添加到targetContents集合中
targetContents.add(ifSqlNode);
}
}
在IfHandler类的handleNode()方法中会继续调用XMLScriptBuilder类的parseDynamicTags()方法完成标签子节点的解析,将子节点转换为MixedSqlNode对象,然后获取标签test属性对应的OGNL表达式,接着创建IfSqlNode对象并添加到List对象中。parseDynamicTags()方法的内容前面我们已经分析过了,该方法中会获取当前节点的所有子节点,如果子节点内容为动态SQL标签,继续调用动态SQL标签对应的NodeHandler进行处理,这样就“递归”地完成了所有动态SQL标签的解析。
其他SqlNode实现类的处理逻辑与之类似。例如,下面是ForEachHandler类的实现代码:
private class ForEachHandler implements NodeHandler {
public ForEachHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
String collection = nodeToHandle.getStringAttribute(“collection”);
Boolean nullable = nodeToHandle.getBooleanAttribute(“nullable”);
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, nullable, index, item, open, close, separator);
targetContents.add(forEachSqlNode);
}
}
如上面的代码所示,ForEachHandler类的handleNode()方法中也会调用XMLScriptBuilder类的parseDynamicTags()解析标签所有子元素,如果子元素中包含标签或标签,则继续调用IfHandler或者ForEachHandler对象的handleNode()方法进行处理,直到所有的动态SQL元素全部被转换成SqlNode对象。
需要注意的是,XMLScriptBuilder类的构造方法中,会调用initNodeHandlerMap()方法将所有NodeHandler的实例注册到Map中,代码如下:
public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
super(configuration);
this.context = context;
this.parameterType = parameterType;
initNodeHandlerMap();
}
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());
}
需要解析动态SQL标签时,只需要根据标签名称获取对应的NodeHander对象进行处理即可,而不用每次都创建对应的NodeHandler实例,这也是享元思想的应用。上面是动态SQL配置转换为SqlNode对象的过程,那么SqlNode对象是如何根据调用Mapper时传入的参数动态生成SQL语句的呢?接下来我们回顾一下XMLScriptBuilder类的parseScriptNode()方法,代码如下:
public SqlSource parseScriptNode() {
// 调用parseDynamicTags()方法将SQL配置转换为SqlNode对象
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
// 判断Mapper SQL配置中是否包含动态SQL元素,如果是,就创建DynamicSqlSource对象,否则创建RawSqlSource对象
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
动态SQL标签解析完成后,将解析后生成的SqlNode对象封装在SqlSource对象中。通过前面的学习我们知道,MyBatis中的MappedStatement用于描述Mapper中的SQL配置,SqlSource创建完毕后,最终会存放在MappedStatement对象的sqlSource属性中,Executor组件操作数据库时,会调用MappedStatement对象的getBoundSql()方法获取BoundSql对象,代码如下:
public final class MappedStatement {
private SqlSource sqlSource; // 解析SQL语句生成的SqlSource实例
// 调用SqlSource类的getBoundSql()函数获取BoundSql对象实例
public BoundSql getBoundSql(Object parameterObject) {
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
List 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;
}
如上面的代码所示,MappedStatement对象的getBoundSql()方法会调用SqlSource对象的getBoundSql()方法,这个过程就完成了SqlNode对象解析成SQL语句的过程。我们可以了解一下DynamicSqlSource类的getBoundSql()方法的实现,代码如下:
public class DynamicSqlSource implements SqlSource {
private final Configuration configuration;
private final SqlNode rootSqlNode;
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
// 通过参数对象创建动态SQL上下文对象
DynamicContext context = new DynamicContext(configuration, parameterObject);
// 以动态SQL上下文对象作为参数调用SqlNode的apply()函数
rootSqlNode.apply(context);
// 创建SqlSourceBuilder对象实例
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
// 调用DynamicContext的getSql()方法获取动态SQL解析后的SQL内容
// 然后调用SqlSourceBuilder的parse()方法对SQL内容做进一步处理,生成StaticSqlSource对象
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
// 调用StaticSqlSource对象的getBoundSql()方法获得BoundSql实例
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
// 将标签绑定的参数添加到BoundSql对象中
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
}
如上面的代码所示,在DynamicSqlSource类的getBoundSql()方法中,首先根据参数对象创建DynamicContext对象,然后调用SqlNode对象的apply()方法对动态SQL进行解析。动态SQL解析完成后,调用DynamicContext对象的getSql()方法获取动态SQL解析后的结果。接着调用SqlSourceBuilder对象的parse()方法对动态SQL解析后的结果进一步解析处理,该方法返回一个StaticSqlSource对象,StaticSqlSource用于描述动态SQL解析后的静态SQL资源。
接下来,我们再来了解一下SqlSourceBuilder类的parse()方法对动态SQL解析后的结果到底做了什么操作。该方法的代码如下:
public class SqlSourceBuilder extends BaseBuilder {
private static final String PARAMETER_PROPERTIES = “javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName”;
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
// ParameterMappingTokenHandler为Mybatis参数映射处理器,用于处理SQL中的#{}参数占位符
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType,
additionalParameters);
// Token解析器,用于解析#{}参数
GenericTokenParser parser = new GenericTokenParser(“#{”, “}”, handler);
String sql;
// 调用GenericTokenParser对象的parse()方法将#{}参数占位符替换为?
if (configuration.isShrinkWhitespacesInSql()) {
sql = parser.parse(removeExtraWhitespaces(originalSql));
} else {
sql = parser.parse(originalSql);
}
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
如上面的代码所示,在SqlSourceBuilder类的parse()方法中,首先创建了一个ParameterMappingTokenHandler对象,ParameterMappingTokenHandler为MyBatis参数映射处理器,用于处理SQL中的#{}参数占位符。接着创建了一个GenericTokenParser对象,GenericTokenParser用于对SQL中的#{}参数占位符进行解析,获取#{}参数占位符中的内容。
针对上述函数,如果输入的原始SQL脚本是
INSERT INTO BLOG (ID, NAME, NOTE, COMMENT)
VALUES ( #{uuu.u}, #{__frch_u_0.id}, #{__frch_u_0,typeHandler=org.apache.ibatis.type.StringTypeHandler}, #{__frch_u_0:VARCHAR,typeHandler=org.apache.ibatis.type.StringTypeHandler} )
则GenericTokenParser对象调用函数parse()后解析结果如下:
INSERT INTO BLOG (ID, NAME, NOTE, COMMENT) VALUES ( ?, ?, ?, ? )
SqlSourceBuilder类的parse()方法调试过程中,局部变量和参数的值如下:
我们首先来看GenericTokenParser类解析#{}参数占位符的过程,代码如下:
public class GenericTokenParser {
private final String openToken; // 上述样例中为#{
private final String closeToken; // 上述样例中为}
private final TokenHandler handler; // TokenHandler对象实例
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
public String parse(String text) {
if (text == null || text.isEmpty()) {
return “”;
}
// 获取第一个#{在SQL中的位置
int start = text.indexOf(openToken);
start为-1说明SQL中不存在任何#{}参数占位符
if (start == -1) {
return text;
}
// 将SQL转换为char数组
char[] src = text.toCharArray();
int offset = 0; // 用offset记录已解析的#{或者}的偏移量,避免重复解析
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null; // expression为#{}中的参数内容
// 遍历获取所有#{}参数占位符的内容,然后调用TokenHandler对象的handleToken()方法替换参数占位符
do {
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] != ‘\’)) {
expression.append(src, offset, end - offset);
break;
}
// 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);
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
// 调用TokenHandler对象的handleToken()方法替换参数占位符
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
} while (start > -1);
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
}
从上面的代码可以看出,SQL配置中的所有#{}参数占位符内容都被替换成了“?”字符,为什么要替换成一个“?”字符呢?读者可能会联想到JDBC中的PreparedStatement,MyBatis默认情况下会使用PreparedStatement对象与数据库进行交互,因此#{}参数占位符内容被替换成了问号,然后调用PreparedStatement对象的setXXX()方法为参数占位符设置值。除此之外,ParameterMappingTokenHandler的handleToken()方法中还做了另一件事情,就是调用buildParameterMapping()方法对占位符内容进行解析,将占位符内容转换为ParameterMapping对象。
private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {
private final List parameterMappings = new ArrayList<>();
private final Class<?> parameterType;
private final MetaObject metaParameters;
public ParameterMappingTokenHandler(Configuration configuration
, Class<?> parameterType,Map<String, Object> additionalParameters) {
super(configuration);
this.parameterType = parameterType;
this.metaParameters = configuration.newMetaObject(additionalParameters);
}
@Override
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return “?”;
}
ParameterMapping对象用于描述MyBatis参数映射信息,便于后续根据参数映射信息获取对应的TypeHandler为PreparedStatement对象设置值。buildParameterMapping()方法解析参数占位符生成ParameterMapping对象的过程如下:
private ParameterMapping buildParameterMapping(String content) {
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);
} 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 {
MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
if (metaClass.hasGetter(property)) {
propertyType = metaClass.getGetterType(property);
} else {
propertyType = Object.class;
}
}
ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
Class<?> javaType = propertyType;
String typeHandlerAlias = null;
for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
String name = entry.getKey();
String value = entry.getValue();
if (“javaType”.equals(name)) {
javaType = resolveClass(value);
builder.javaType(javaType);
} else if (“jdbcType”.equals(name)) {
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 " + PARAMETER_PROPERTIES);
}
}
if (typeHandlerAlias != null) {
builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
}
return builder.build();
}
private Map<String, String> parseParameterMapping(String content) {
try {
return new ParameterExpression(content);
} catch (BuilderException ex) {
throw ex;
} catch (Exception ex) {
throw new BuilderException(“Parsing error was found in mapping #{” + content
- "}. Check syntax #{property|(expression), var1=value1, var2=value2, …} ", ex);
}
}
}
如上面的代码所示,在ParameterMappingTokenHandler类的buildParameterMapping()方法中首先将参数占位符内容转换为Map对象,例如参数占位符内容如下:#{userId,javaType=long,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
将会转换成如下Map对象:
然后通过一系列的逻辑判断参数的类型(javaType属性值),具体逻辑读者可参考上面代码中的注释内容。最后通过建造者模式构建ParameterMapping对象。到此为止,动态SQL的解析已经全部完成。
我们首先来看参数占位符的解析过程。当动态SQL配置中存在{}参数占位符的解析过程。当动态SQL配置中存在参数占位符的解析过程。当动态SQL配置中存在{}参数占位符时,MyBatis会使用TextSqlNode对象描述对应的SQL节点,在调用TextSqlNode对象的apply()方法时会完成动态SQL的解析。也就是说,${}参数占位符的解析是在TextSqlNode类的apply()方法中完成的,下面是该方法的实现:
public class TextSqlNode implements SqlNode {
private final String text;
private final Pattern injectionFilter;
@Override
public boolean apply(DynamicContext context) {
GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
context.appendSql(parser.parse(text));
return true;
}
最后
面试一面会问很多基础问题,而这些基础问题基本上在网上搜索,面试题都会很多很多。最好把准备一下常见的面试问题,毕竟面试也相当与一次考试,所以找工作面试的准备千万别偷懒。面试就跟考试一样的,时间长了不复习,现场表现肯定不会太好。表现的不好面试官不可能说,我猜他没发挥好,我录用他吧。
96道前端面试题:
常用算法面试题:
前端基础面试题:
内容主要包括HTML,CSS,JavaScript,浏览器,性能优化
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
boolean apply(DynamicContext context) {
GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
context.appendSql(parser.parse(text));
return true;
}
最后
面试一面会问很多基础问题,而这些基础问题基本上在网上搜索,面试题都会很多很多。最好把准备一下常见的面试问题,毕竟面试也相当与一次考试,所以找工作面试的准备千万别偷懒。面试就跟考试一样的,时间长了不复习,现场表现肯定不会太好。表现的不好面试官不可能说,我猜他没发挥好,我录用他吧。
96道前端面试题:
- [外链图片转存中…(img-Tz2sbdrY-1713704373371)]
常用算法面试题:
- [外链图片转存中…(img-WqpDv6Dd-1713704373371)]
前端基础面试题:
内容主要包括HTML,CSS,JavaScript,浏览器,性能优化
- [外链图片转存中…(img-wg2TQTpo-1713704373371)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-xBePqJQE-1713704373372)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!