在Mybatis的开发过程中,如果传入的字符串参数包含小于号、大于号、与符号、单引号等特殊字符,直接拼接进SQL语句很容易引发语法错误或者安全风险,因此需要采用合适的方式对这些特殊符号进行处理。

使用XML转义字符处理
Mybatis的XML映射文件本质是XML文件,因此可以直接使用XML预定义的转义字符来处理特殊符号,这种方式适合字符串中特殊符号较少的场景。
常见的XML转义字符对应关系如下:
| 特殊符号 | 转义字符 |
|---|---|
| < | < |
| > | > |
| & | & |
| ' | ' |
| " | " |
示例代码如下:
<select id="selectByContent" parameterType="string" resultType="User">
SELECT * FROM user WHERE content < #{content}
</select>
使用CDATA标签包裹SQL片段
如果字符串中包含大量特殊符号,逐个转义会非常繁琐,此时可以使用<![CDATA[ ]]>标签将包含特殊符号的SQL片段包裹起来,CDATA区域内的内容会被XML解析器当作纯文本处理,不会解析其中的特殊符号。
示例代码:
<select id="selectByRange" parameterType="map" resultType="User">
SELECT * FROM user WHERE age <![CDATA[ > #{minAge} AND age < #{maxAge} ]]>
</select>
需要注意CDATA标签不能包裹整个SQL语句,否则Mybatis的占位符#{ }无法被正确解析,只需要包裹包含特殊符号的部分即可。
使用参数绑定避免特殊符号问题
Mybatis的参数绑定方式#{ }会自动对参数进行预编译处理,会将参数值以占位符的方式传入SQL,不会直接拼接字符串,因此可以有效避免特殊符号导致的语法问题,同时也能防止SQL注入。
示例代码:
<select id="selectByKeyword" parameterType="string" resultType="User">
SELECT * FROM user WHERE name LIKE CONCAT('%', #{keyword}, '%')
</select>
即使传入的keyword参数包含单引号等特殊符号,Mybatis也会自动对其进行转义处理,不会破坏SQL结构。如果是拼接字符串的场景,尽量使用#{ }而不是${ },${ }是直接字符串拼接,不会处理特殊符号,存在安全风险。
自定义拦截器统一处理特殊符号
如果项目中存在大量需要处理特殊符号的场景,可以自定义Mybatis拦截器,在参数传入SQL之前统一对特殊符号进行转义处理,减少重复代码。
示例拦截器代码:
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Properties;
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class SpecialCharInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
if (args.length > 1) {
Object parameter = args[1];
if (parameter instanceof String) {
args[1] = escapeSpecialChar((String) parameter);
} else if (parameter instanceof Map) {
Map<String, Object> paramMap = (Map<String, Object>) parameter;
for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
if (entry.getValue() instanceof String) {
entry.setValue(escapeSpecialChar((String) entry.getValue()));
}
}
} else {
// 处理实体类参数
Class<?> clazz = parameter.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.getType().equals(String.class)) {
field.setAccessible(true);
String value = (String) field.get(parameter);
if (value != null) {
field.set(parameter, escapeSpecialChar(value));
}
}
}
}
}
return invocation.proceed();
}
private String escapeSpecialChar(String str) {
if (str == null) {
return null;
}
return str.replace("<", "<")
.replace(">", ">")
.replace("&", "&")
.replace("'", "'")
.replace(""", """);
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
之后在Mybatis配置文件中注册该拦截器即可生效。
不同场景的选择建议
- 特殊符号较少且固定:优先使用XML转义字符,简单直观
- 特殊符号较多且集中在某段SQL:使用CDATA标签包裹对应片段
- 普通参数传入场景:优先使用
#{ }参数绑定,安全且无需额外处理 - 项目中大量需要处理特殊符号的场景:自定义拦截器统一处理,降低维护成本