回答
支持。 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);
}
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。