作者简介:大家好,我是码炫码哥,前中兴通讯、美团架构师,现任某互联网公司CTO,兼职码炫课堂主讲源码系列专题
代表作:《jdk源码&多线程&高并发》,《深入tomcat源码解析》,《深入netty源码解析》,《深入dubbo源码解析》,《深入springboot源码解析》,《深入spring源码解析》,《深入redis源码解析》等
联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬。码炫课堂的个人空间-码炫码哥个人主页-面试,源码等
回答
支持。 Mybatis 内置了一些动态 SQL 的标签,允许开发者根据不同的条件构建不同的 SQL,提高代码的灵活性和可维护性。如<if>、<choose>、<when>、<otherwise>、<trim>、<where>、<set>和<foreach>等。

<if>元素:根据条件动态包含 SQL 片段。
<mapper namespace="com.damingge.mapper.EmployeeMapper">
<select id="selectEmployeeByCondition" resultType="com.damingge.model.Employee">
SELECT * FROM t_employee
<where>
<if test="id != null">
AND id = #{id}
</if>
<if test="name != null">
AND name = #{name}
</if>
</where>
</select>
</mapper>
<choose>、<where>、<when>、<otherwise>元素:跟 Java 的 switch 类似。
<mapper namespace="com.damingge.mapper.EmployeeMapper">
<select id="findUsersByCondition" resultType="com.damingge.model.Employee">
SELECT * FROM t_employee
<where>
<choose>
<when test="id != null">
AND id = #{id}
</when>
<when test="name != null">
AND name = #{name}
</when>
<otherwise>
AND status = 'ACTIVE'
</otherwise>
</choose>
</where>
</select>
</mapper>
<trim>、<set>元素:用在动态更新场景。
<mapper namespace="com.damingge.mapper.EmployeeMapper">
<update id="updateEmployeeById">
UPDATE t_employee
<set>
<if test="name != null">
name = #{name},
</if>
<if test="email != null">
email = #{email},
</if>
</set>
WHERE id = #{id}
</update>
</mapper>
<foreach>元素:元素遍历场景。
<mapper namespace="com.damingge.mapper.EmployeeMapper">
<select id="findUsersByIds" resultType="com.damingge.model.Employee">
SELECT * FROM t_employee
WHERE id IN
<foreach item="id" index="index" collection="idList" open="(" separator="," close=")">
#{id}
</foreach>
</select>
</mapper>
扩展
动态 SQL 注解
除了 XML 配置动态 SQL 外,mybatis 也提供了对应注解来满足动态 SQL 功能。
- @Insert
- @Update
- @Delete
- @Select
- @InsertProvider
- @UpdateProvider
- @DeleteProvider
- @SelectProvider
其中带有 Provider 后缀的区别在于,使用 Provider 需要自身实现方法。
@Update({"<script>",
"update t_employee",
" <set>",
" <if test='name != null'>name=#{name},</if>",
" <if test='email != null'>email=#{email},</if>",
" </set>",
"where id=#{id}",
"</script>"})
int updateEmployeeById(Employee employee);
@SelectProvider(type = EmployeeDaoProvider.class, method = "selectEmployeeByName")
Employee selectEmployeeByName(Map<String, Object> map);
public String selectEmployeeByName(Map<String, Object> map) {
String name = (String) map.get("name");
String s = new SQL() {
{
SELECT("*");
FROM("Employee");
if(map.get("name")!=null)
WHERE("name=#{name}");
}
}.toString();
return s;
}
}
SQL 的实现原理
Mybatis 动态 SQL 的实现是通过 XML 标签处理和 OGNL 表达式解析实现的。其中 OGNL 表达式读写 Java Bean 属性值、执行 Java Bean 方法。核心组件:
- SQL 脚本解析器:负责解析和动态处理 SQL 脚本。
public class XMLScriptBuilder extends BaseBuilder {
private void initNodeHandlerMap() {
// 初始化动态SQL片段处理器
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());
}
/**
* 解析 XML 中的 SQL 脚本
*/
public SqlSource parseScriptNode() {
// 解析动态 SQL 标签
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
if (isDynamic) {
// 动态 SQL
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
// 非动态SQL
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) {
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
// 标记为动态SQL
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);
}
}
- OGNL 表达式解析与替换:对 OGNL 表达式进行求值,将表达式结果替换到 SQL 模板中对应位置。OGNL(Object-Graph Navigation Language) 是一种强大的表达式语言,可以访问对象属性、调用方法以及遍历集合。
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;
}
/**
* 获取执行SQL(动态SQL解析入口)
*/
@Override
public BoundSql getBoundSql(Object parameterObject) {
// 创建动态SQL上下文,保存解析后的 SQL 语句
DynamicContext context = new DynamicContext(configuration, parameterObject);
// 解析 SQL 节点(rootSqlNode 是 Mapper XML 文件中定义的动态SQL节点)
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());
// 参数绑定
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
// 额外参数绑定
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
}
public class ExpressionEvaluator {
/**
* 计算满足条件,如IfSqlNode
*/
public boolean evaluateBoolean(String expression, Object parameterObject) {
Object value = OgnlCache.getValue(expression, parameterObject);
if (value instanceof Boolean) {
return (Boolean) value;
}
if (value instanceof Number) {
return new BigDecimal(String.valueOf(value)).compareTo(BigDecimal.ZERO) != 0;
}
return value != null;
}
/**
* 计算满足条件,ForEachSqlNode
*/
public Iterable<?> evaluateIterable(String expression, Object parameterObject) {
Object value = OgnlCache.getValue(expression, parameterObject);
if (value == null) {
throw new BuilderException("The expression '" + expression + "' evaluated to a null value.");
}
if (value instanceof Iterable) {
return (Iterable<?>) value;
}
if (value.getClass().isArray()) {
int size = Array.getLength(value);
List<Object> answer = new ArrayList<>();
for (int i = 0; i < size; i++) {
Object o = Array.get(value, i);
answer.add(o);
}
return answer;
}
if (value instanceof Map) {
return ((Map) value).entrySet();
}
throw new BuilderException("Error evaluating expression '" + expression + "'. Return value (" + value + ") was not iterable.");
}
}
- 动态 SQL 节点:动态 SQL 节点表示动态 SQL 语句的各个部分。
SqlNode是动态 Sql 节点的基类,不同的动态 SQL 标签有不同的实现类。
public interface SqlNode {
// apply()方法根据传入的实参,解析该 SqlNode 所表示的动态 SQL 内容
// 将解析之后的 SQL 片段追加到 DynamicContext.sqlBuilder 字段中暂存。
// 当 SQL 语句中全部的动态 SQL 片段都解析完成后,可以从 DynamicContext.sqlBuilder 字段中得到一条完整的、可用的SQL语句了
boolean apply(DynamicContext context);
}
629

被折叠的 条评论
为什么被折叠?



