Mybatis特殊字符轉(zhuǎn)義查詢實現(xiàn)
1. 問題描述
MyBatis作為目前最常用的ORM數(shù)據(jù)庫訪問持久層框架,其本身支持動態(tài)SQL存儲映射等高級特性也非常優(yōu)秀,通過Mapper文件采用動態(tài)代理模式使SQL與業(yè)務(wù)代碼相解耦,日常開發(fā)中使用也非常廣泛。
正常模糊匹配查詢時是沒有什么問題的,但是如果需要模糊查詢字段含有特殊字符比如% _ / 等時就會出現(xiàn)查詢不準(zhǔn)確的問題。本文就是通過mybatis攔截器實現(xiàn)特殊字符轉(zhuǎn)義實現(xiàn)mybatis特殊字符查詢問題。
2. 解決方案
MybatisLikeSqlInterceptor:
通過 @Intercepts 注解指定攔截器插件的屬性:分別指定了攔截器類型 Executor, 攔截方法名 query (共有2個query方法)。
攔截方法參數(shù)(方法1)MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class
攔截方法參數(shù)(方法2)MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class,CacheKey.class, BoundSql.class
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
MyBatis 允許使用插件來攔截的方法調(diào)用包括:
Executor 、ParameterHandler、ResultSetHandler 、StatementHandler ,方法時序如下:

3. 設(shè)計實現(xiàn)
3.1 環(huán)境準(zhǔn)備
-- 創(chuàng)建用戶表
CREATE TABLE `user` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID主鍵',
`name` varchar(64) DEFAULT NULL COMMENT '姓名',
`sex` varchar(8) DEFAULT NULL COMMENT '性別',
`age` int(4) DEFAULT NULL COMMENT '年齡',
`born` date DEFAULT NULL COMMENT '出生日期',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='用戶表';
-- 查詢用戶表
select * from user;
-- 新增數(shù)據(jù)
INSERT INTO `category`.`user`(`id`, `name`, `sex`, `age`, `born`) VALUES (1, '%張三%', '男', 18, '2022-04-22');
INSERT INTO `category`.`user`(`id`, `name`, `sex`, `age`, `born`) VALUES (2, '李四', '女', 27, '2022-04-01');
-- 執(zhí)行sql
select id, name, sex, age, born from user WHERE name like concat('%','%','%');
select id, name, sex, age, born from user WHERE name like concat('%','','%');
select id, name, sex, age, born from user WHERE name like concat('%','/','%');
select id, name, sex, age, born from user WHERE name like concat('%','張','%');
3.2 代碼實現(xiàn)
UserController
package com.jerry.market.controller;
import com.jerry.market.entity.User;
import com.jerry.market.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import com.jerry.market.entity.Response;
import java.util.List;
import javax.annotation.Resource;
/**
* 用戶表(User)控制層
*
* @author makejava
* @since 2022-04-22 15:31:00
*/
@RestController
@RequestMapping("/user")
@Api(tags = "UserController", description = "用戶控制器")
public class UserController {
/**
* 服務(wù)對象
*/
@Resource
private UserService userService;
/**
* 通過主鍵查詢單條數(shù)據(jù)
*
* @param user 參數(shù)對象
* @return 單條數(shù)據(jù)
*/
@ApiOperation("通過主鍵查詢單條數(shù)據(jù)")
@RequestMapping(value = "get", method = RequestMethod.GET)
public Response<User> selectOne(User user) {
User result = userService.selectById(user.getId());
if (result != null) {
return Response.success("查詢成功", result);
}
return Response.fail("查詢失敗");
}
/**
* 新增一條數(shù)據(jù)
*
* @param user 實體類
* @return Response對象
*/
@ApiOperation("新增一條數(shù)據(jù)")
@RequestMapping(value = "insert", method = RequestMethod.POST)
public Response<User> insert(@RequestBody User user) {
int result = userService.insert(user);
if (result > 0) {
return Response.success("新增成功", user);
}
return Response.fail("新增失敗");
}
/**
* 批量新增
*
* @param users 實例對象的集合
* @return 影響行數(shù)
*/
@ApiOperation("批量新增")
@RequestMapping(value = "batchInsert", method = RequestMethod.POST)
public Response<Integer> batchInsert(@RequestBody List<User> users) {
int result = userService.batchInsert(users);
if (result > 0) {
return Response.success("新增成功", result);
}
return Response.fail("新增失敗");
}
/**
* 修改一條數(shù)據(jù)
*
* @param user 實體類
* @return Response對象
*/
@ApiOperation("修改一條數(shù)據(jù)")
@RequestMapping(value = "update", method = RequestMethod.PUT)
public Response<User> update(@RequestBody User user) {
User result = userService.update(user);
if (result != null) {
return Response.success("修改成功", result);
}
return Response.fail("修改失敗");
}
/**
* 刪除一條數(shù)據(jù)
*
* @param user 參數(shù)對象
* @return Response對象
*/
@ApiOperation("刪除一條數(shù)據(jù)")
@RequestMapping(value = "delete", method = RequestMethod.DELETE)
public Response<User> delete(User user) {
int result = userService.deleteById(user.getId());
if (result > 0) {
return Response.success("刪除成功", null);
}
return Response.fail("刪除失敗");
}
/**
* 查詢?nèi)?
*
* @return Response對象
*/
@ApiOperation("查詢?nèi)?)
@RequestMapping(value = "selectAll", method = RequestMethod.GET)
public Response<List<User>> selectAll() {
List<User> users = userService.selectAll();
if (users != null) {
return Response.success("查詢成功", users);
}
return Response.fail("查詢失敗");
}
/**
* 通過實體作為篩選條件查詢
*
* @return Response對象
*/
@ApiOperation("通過實體作為篩選條件查詢")
@RequestMapping(value = "selectList", method = RequestMethod.GET)
public Response<List<User>> selectList(User user) {
List<User> users = userService.selectList(user);
if (users != null) {
return Response.success("查詢成功", users);
}
return Response.fail("查詢失敗");
}
/**
* 分頁查詢
*
* @param start 偏移
* @param limit 條數(shù)
* @return Response對象
*/
@ApiOperation("分頁查詢")
@RequestMapping(value = "selectPage", method = RequestMethod.GET)
public Response<List<User>> selectPage(Integer start, Integer limit) {
List<User> users = userService.selectPage(start, limit);
if (users != null) {
return Response.success("查詢成功", users);
}
return Response.fail("查詢失敗");
}
}
UserService
package com.jerry.market.service;
import com.jerry.market.entity.User;
import java.util.List;
import java.util.Map;
/**
* 用戶表(User)表服務(wù)接口
*
* @author makejava
* @since 2022-04-22 15:31:01
*/
public interface UserService {
/**
* 通過ID查詢單條數(shù)據(jù)
*
* @param id 主鍵
* @return 實例對象
*/
User selectById(Object id);
/**
* 分頁查詢
*
* @param start 查詢起始位置
* @param limit 查詢條數(shù)
* @return 對象列表
*/
List<User> selectPage(int start, int limit);
/**
* 查詢?nèi)?
*
* @return 對象列表
*/
List<User> selectAll();
/**
* 通過實體作為篩選條件查詢
*
* @param user 實例對象
* @return 對象列表
*/
List<User> selectList(User user);
/**
* 新增數(shù)據(jù)
*
* @param user 實例對象
* @return 影響行數(shù)
*/
int insert(User user);
/**
* 批量新增
*
* @param users 實例對象的集合
* @return 影響行數(shù)
*/
int batchInsert(List<User> users);
/**
* 修改數(shù)據(jù)
*
* @param user 實例對象
* @return 修改
*/
User update(User user);
/**
* 通過主鍵刪除數(shù)據(jù)
*
* @param id 主鍵
* @return 影響行數(shù)
*/
int deleteById(Object id);
/**
* 查詢總數(shù)據(jù)數(shù)
*
* @return 數(shù)據(jù)總數(shù)
*/
int count();
}
UserServiceImpl
package com.jerry.market.service.impl;
import com.jerry.market.entity.User;
import com.jerry.market.mapper.UserMapper;
import com.jerry.market.service.UserService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
/**
* 用戶表(User表)服務(wù)實現(xiàn)類
*
* @author makejava
* @since 2022-04-22 15:31:01
*/
@Service("userService")
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
/**
* 通過ID查詢單條數(shù)據(jù)
*
* @param id 主鍵
* @return 實例對象
*/
@Override
public User selectById(Object id) {
return this.userMapper.selectById(id);
}
/**
* 分頁查詢
*
* @param start 查詢起始位置
* @param limit 查詢條數(shù)
* @return 對象列表
*/
@Override
public List<User> selectPage(int start, int limit) {
return this.userMapper.selectPage(start, limit);
}
/**
* 查詢所有
*
* @return 實例對象的集合
*/
@Override
public List<User> selectAll() {
return this.userMapper.selectAll();
}
/**
* 根據(jù)條件查詢
*
* @return 實例對象的集合
*/
@Override
public List<User> selectList(User user) {
return this.userMapper.selectList(user);
}
/**
* 新增數(shù)據(jù)
*
* @param user 實例對象
* @return 實例對象
*/
@Override
public int insert(User user) {
return this.userMapper.insert(user);
}
/**
* 批量新增
*
* @param users 實例對象的集合
* @return 生效的條數(shù)
*/
@Override
public int batchInsert(List<User> users) {
return this.userMapper.batchInsert(users);
}
/**
* 修改數(shù)據(jù)
*
* @param user 實例對象
* @return 實例對象
*/
@Override
public User update(User user) {
this.userMapper.update(user);
return this.selectById(user.getId());
}
/**
* 通過主鍵刪除數(shù)據(jù)
*
* @param id 主鍵
* @return 是否成功
*/
@Override
public int deleteById(Object id) {
return this.userMapper.deleteById(id);
}
/**
* 查詢總數(shù)據(jù)數(shù)
*
* @return 數(shù)據(jù)總數(shù)
*/
@Override
public int count() {
return this.userMapper.count();
}
}
UserMapper
package com.jerry.market.mapper;
import com.jerry.market.entity.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
/**
* 用戶表(User)表數(shù)據(jù)庫訪問層
*
* @author makejava
* @since 2022-04-22 15:31:00
*/
public interface UserMapper {
/**
* 通過ID查詢單條數(shù)據(jù)
*
* @param id 主鍵
* @return 實例對象
*/
User selectById(Object id);
/**
* 分頁查詢
*
* @param start 查詢起始位置
* @param limit 查詢條數(shù)
* @return 對象列表
*/
List<User> selectPage(@Param("start") int start, @Param("limit") int limit);
/**
* 查詢?nèi)?
*
* @return 對象列表
*/
List<User> selectAll();
/**
* 通過實體作為篩選條件查詢
*
* @param user 實例對象
* @return 對象列表
*/
List<User> selectList(User user);
/**
* 新增數(shù)據(jù)
*
* @param user 實例對象
* @return 影響行數(shù)
*/
int insert(User user);
/**
* 批量新增
*
* @param users 實例對象的集合
* @return 影響行數(shù)
*/
int batchInsert(List<User> users);
/**
* 修改數(shù)據(jù)
*
* @param user 實例對象
* @return 影響行數(shù)
*/
int update(User user);
/**
* 通過主鍵刪除數(shù)據(jù)
*
* @param id 主鍵
* @return 影響行數(shù)
*/
int deleteById(Object id);
/**
* 查詢總數(shù)據(jù)數(shù)
*
* @return 數(shù)據(jù)總數(shù)
*/
int count();
}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jerry.market.mapper.UserMapper">
<!-- 結(jié)果集 -->
<resultMap type="com.jerry.market.entity.User" id="UserMap">
<result property="id" column="id" jdbcType="VARCHAR"/>
<result property="name" column="name" jdbcType="VARCHAR"/>
<result property="sex" column="sex" jdbcType="VARCHAR"/>
<result property="age" column="age" jdbcType="INTEGER"/>
<result property="born" column="born" jdbcType="VARCHAR"/>
</resultMap>
<!-- 基本字段 -->
<sql id="Base_Column_List">
id, name, sex, age, born </sql>
<!-- 查詢單個 -->
<select id="selectById" resultMap="UserMap">
select
<include refid="Base_Column_List"/>
from user
where id = #{id}
</select>
<!-- 分頁查詢 -->
<select id="selectPage" resultMap="UserMap">
select
<include refid="Base_Column_List"/>
from user
limit #{start},#{limit}
</select>
<!-- 查詢?nèi)?-->
<select id="selectAll" resultMap="UserMap">
select
<include refid="Base_Column_List"/>
from user
</select>
<!--通過實體作為篩選條件查詢-->
<select id="selectList" resultMap="UserMap">
select
<include refid="Base_Column_List"/>
from user
<where>
<if test="id != null">
and id = #{id}
</if>
<if test="name != null and name != ''">
and name like concat('%',#{name},'%')
</if>
<if test="sex != null and sex != ''">
and sex = #{sex}
</if>
<if test="age != null">
and age = #{age}
</if>
<if test="born != null">
and born = #{born}
</if>
</where>
</select>
<!-- 新增所有列 -->
<insert id="insert" keyProperty="id" useGeneratedKeys="true">
insert into user(id, name, sex, age, born)
values ( #{id}, #{name}, #{sex}, #{age}, #{born})
</insert>
<!-- 批量新增 -->
<insert id="batchInsert">
insert into user(id, name, sex, age, born)
values
<foreach collection="users" item="item" index="index" separator=",">
(
#{item.id}, #{item.name}, #{item.sex}, #{item.age}, #{item.born} )
</foreach>
</insert>
<!-- 通過主鍵修改數(shù)據(jù) -->
<update id="update">
update category.user
<set>
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="sex != null and sex != ''">
sex = #{sex},
</if>
<if test="age != null">
age = #{age},
</if>
<if test="born != null">
born = #{born},
</if>
</set>
where id = #{id}
</update>
<!--通過主鍵刪除-->
<delete id="deleteById">
delete from user where id = #{id}
</delete>
<!-- 總數(shù) -->
<select id="count" resultType="int">
select count(*) from user
</select>
</mapper>
3.3 攔截器實現(xiàn)
1 MybatisLikeSqlInterceptor.java mybatis攔截器
package com.jerry.market.config;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
/**
* Mybatis/mybatis-plus fuzzy query statement special character escape interceptor
*
* @author zrj
* @since 2022/4/22
**/
@Slf4j
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),})
public class MybatisLikeSqlInterceptor implements Interceptor {
/**
* SQL statement like
*/
private final static String SQL_LIKE = "like ";
/**
* SQL statement placeholder
*/
private final static String SQL_PLACEHOLDER = "?";
/**
* SQL statement placeholder separated
*/
private final static String SQL_PLACEHOLDER_REGEX = "\\?";
/**
* All escapers
*/
private static Map<Class, AbstractLikeSqlConverter> converterMap = new HashMap<>(4);
static {
converterMap.put(Map.class, new MapLikeSqlConverter());
converterMap.put(Object.class, new ObjectLikeSqlConverter());
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
MappedStatement statement = (MappedStatement) args[0];
Object parameterObject = args[1];
BoundSql boundSql = statement.getBoundSql(parameterObject);
String sql = boundSql.getSql();
this.transferLikeSql(sql, parameterObject, boundSql);
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties arg0) {
System.out.println("aaaaaa");
}
/**
* Modify the SQL statement that contains like
*
* @param sql SQL statement
* @param parameterObject parameter object
* @param boundSql bound SQL object
*/
private void transferLikeSql(String sql, Object parameterObject, BoundSql boundSql) {
if (!isEscape(sql)) {
return;
}
sql = sql.replaceAll(" {2}", "");
//Get the number of keywords (de-duplication)
Set<String> fields = this.getKeyFields(sql, boundSql);
if (fields == null) {
return;
}
//This can be enhanced, not only to support the Map object, the Map object is only used for the incoming condition is Map or the object passed in using @Param is converted to Map by Mybatis
AbstractLikeSqlConverter converter;
//"Clean" keywords with special characters. If there are special characters, add an escape character (\) before the special characters
if (parameterObject instanceof Map) {
converter = converterMap.get(Map.class);
} else {
converter = converterMap.get(Object.class);
}
converter.convert(sql, fields, parameterObject);
}
/**
* Do you need to escape
*
* @param sql SQL statement
* @return true/false
*/
private boolean isEscape(String sql) {
return this.hasLike(sql) && this.hasPlaceholder(sql);
}
/**
* Determine whether the SQL statement contains the like keyword
*
* @param str SQL statement
* @return true/false
*/
private boolean hasLike(String str) {
if (StringUtils.isBlank(str)) {
return false;
}
return str.toLowerCase().contains(SQL_LIKE);
}
/**
* Determine whether the SQL statement contains SQL placeholders
*
* @param str SQL statement
* @return true/false
*/
private boolean hasPlaceholder(String str) {
if (StringUtils.isBlank(str)) {
return false;
}
return str.toLowerCase().contains(SQL_PLACEHOLDER);
}
/**
* Get a collection of all fields that need to be replaced
*
* @param sql complete SQL statement
* @param boundSql bound SQL object
* @return field collection list
*/
private Set<String> getKeyFields(String sql, BoundSql boundSql) {
String[] params = sql.split(SQL_PLACEHOLDER_REGEX);
Set<String> fields = new HashSet<>();
for (int i = 0; i < params.length; i++) {
if (this.hasLike(params[i])) {
String field = boundSql.getParameterMappings().get(i).getProperty();
fields.add(field);
}
}
return fields;
}
}
2 AbstractLikeSqlConverter.java 轉(zhuǎn)換器抽象類
package com.jerry.market.config;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
/**
* SQL statement escape template containing like
*
* @author zrj
* @since 2022/4/22
**/
@Slf4j
public abstract class AbstractLikeSqlConverter<T> {
/**
* SQL statement like uses keyword%
*/
private final static String LIKE_SQL_KEY = "%";
/**
* Keywords that need to be escaped in SQL statements
*/
private final static String[] ESCAPE_CHAR = new String[]{LIKE_SQL_KEY, "_", "\\"};
/**
* SQL statement style like in mybatis-plus
*/
private final static String MYBATIS_PLUS_LIKE_SQL = "like ?";
/**
* Parameter prefix in mybatis-plus
*/
private final static String MYBATIS_PLUS_WRAPPER_PREFIX = "ew.paramNameValuePairs.";
/**
* Parameter key in mybatis-plus
*/
final static String MYBATIS_PLUS_WRAPPER_KEY = "ew";
/**
* Parameter separator in mybatis-plus
*/
final static String MYBATIS_PLUS_WRAPPER_SEPARATOR = ".";
/**
* Parameter separator replacer in mybatis-plus
*/
final static String MYBATIS_PLUS_WRAPPER_SEPARATOR_REGEX = "\\.";
/**
* Marks that have been replaced
*/
final static String REPLACED_LIKE_KEYWORD_MARK = "replaced.keyword";
/**
* Escape special characters
*
* @param sql SQL statement
* @param fields field list
* @param parameter parameter object
*/
public void convert(String sql, Set<String> fields, T parameter) {
for (String field : fields) {
if (this.hasMybatisPlusLikeSql(sql)) {
if (this.hasWrapper(field)) {
//The first case: use QueryWrapper, LambdaQueryWrapper for fuzzy query keywords generated by conditional construction in the business layer
this.transferWrapper(field, parameter);
} else {
//The second case: The condition constructor is not used, but the query keyword and fuzzy query symbol `%` are manually spliced in the service layer
this.transferSelf(field, parameter);
}
} else {
//The third case: Fuzzy queries are spliced in the annotation SQL of the Mapper class
this.transferSplice(field, parameter);
}
}
}
/**
* Special characters constructed by escape conditions
* Use QueryWrapper, LambdaQueryWrapper for fuzzy query keywords generated by conditional construction in the business layer
*
* @param field field name
* @param parameter parameter object
*/
public abstract void transferWrapper(String field, T parameter);
/**
* Escape special characters spliced by custom conditions
* The condition constructor is not used, but the query keyword and fuzzy query symbol `%` are manually spliced in the service layer
*
* @param field field name
* @param parameter parameter object
*/
public abstract void transferSelf(String field, T parameter);
/**
* Escape special characters spliced by custom conditions
* Fuzzy queries are spliced in the annotation SQL of the Mapper class
*
* @param field field name
* @param parameter parameter object
*/
public abstract void transferSplice(String field, T parameter);
/**
* Escape wildcard
*
* @param before the string to be escaped
* @return escaped string
*/
String escapeChar(String before) {
if (StringUtils.isNotBlank(before)) {
before = before.replaceAll("\\\\", "\\\\\\\\");
before = before.replaceAll("_", "\\\\_");
before = before.replaceAll("%", "\\\\%");
}
return before;
}
/**
* Whether it contains characters that need to be escaped
*
* @param obj the object to be judged
* @return true/false
*/
boolean hasEscapeChar(Object obj) {
if (!(obj instanceof String)) {
return false;
}
return this.hasEscapeChar((String) obj);
}
/**
* Deal with object like issues
*
* @param field object field
* @param parameter object
*/
void resolveObj(String field, Object parameter) {
if (parameter == null || StringUtils.isBlank(field)) {
return;
}
try {
PropertyDescriptor descriptor = new PropertyDescriptor(field, parameter.getClass());
Method readMethod = descriptor.getReadMethod();
Object param = readMethod.invoke(parameter);
if (this.hasEscapeChar(param)) {
Method setMethod = descriptor.getWriteMethod();
setMethod.invoke(parameter, this.escapeChar(param.toString()));
} else if (this.cascade(field)) {
int index = field.indexOf(MYBATIS_PLUS_WRAPPER_SEPARATOR) + 1;
this.resolveObj(field.substring(index), param);
}
} catch (IntrospectionException | IllegalAccessException | InvocationTargetException e) {
log.error("Reflected {} {} get/set method is abnormal", parameter, field, e);
}
}
/**
* Determine whether it is a cascade attribute
*
* @param field field name
* @return true/false
*/
boolean cascade(String field) {
if (StringUtils.isBlank(field)) {
return false;
}
return field.contains(MYBATIS_PLUS_WRAPPER_SEPARATOR) && !this.hasWrapper(field);
}
/**
* Whether to include the SQL statement format of mybatis-plus containing like
*
* @param sql complete SQL statement
* @return true/false
*/
private boolean hasMybatisPlusLikeSql(String sql) {
if (StringUtils.isBlank(sql)) {
return false;
}
return sql.toLowerCase().contains(MYBATIS_PLUS_LIKE_SQL);
}
/**
* Determine whether to use mybatis-plus conditional constructor
*
* @param field
* @return true/false
*/
private boolean hasWrapper(String field) {
if (StringUtils.isBlank(field)) {
return false;
}
return field.contains(MYBATIS_PLUS_WRAPPER_PREFIX);
}
/**
* Determine whether the string contains characters that need to be escaped
*
* @param str String to be judged
* @return true/false
*/
private boolean hasEscapeChar(String str) {
if (StringUtils.isBlank(str)) {
return false;
}
for (String s : ESCAPE_CHAR) {
if (str.contains(s)) {
return true;
}
}
return false;
}
}
3 MapLikeSqlConverter.java 轉(zhuǎn)換器類
package com.jerry.market.config;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;
/**
* The parameter object is Map converter
*
* @author zrj
* @since 2022/4/22
**/
@Slf4j
public class MapLikeSqlConverter extends AbstractLikeSqlConverter<Map> {
@Override
public void transferWrapper(String field, Map parameter) {
Object wrapper = parameter.get(MYBATIS_PLUS_WRAPPER_KEY);
try {
Method m = wrapper.getClass().getDeclaredMethod("getParamNameValuePairs");
parameter = (Map<String, Object>) m.invoke(wrapper);
} catch (Exception e) {
log.error("反射異常", e);
return;
}
String[] keys = field.split(MYBATIS_PLUS_WRAPPER_SEPARATOR_REGEX);
//ew.paramNameValuePairs.param1, after intercepting the string, get the third one, which is the parameter name
String paramName = keys[2];
String mapKey = String.format("%s.%s", REPLACED_LIKE_KEYWORD_MARK, paramName);
if (parameter.containsKey(mapKey) && Objects.equals(parameter.get(mapKey), true)) {
return;
}
if (this.cascade(field)) {
this.resolveCascadeObj(field, parameter);
} else {
Object param = parameter.get(paramName);
if (this.hasEscapeChar(param)) {
String paramStr = param.toString();
parameter.put(keys[2],
String.format("%%%s%%", this.escapeChar(paramStr.substring(1, paramStr.length() - 1))));
}
}
parameter.put(mapKey, true);
}
@Override
public void transferSelf(String field, Map parameter) {
if (this.cascade(field)) {
this.resolveCascadeObj(field, parameter);
return;
}
Object param = parameter.get(field);
if (this.hasEscapeChar(param)) {
String paramStr = param.toString();
parameter.put(field,
String.format("%%%s%%", this.escapeChar(paramStr.substring(1, paramStr.length() - 1))));
}
}
@Override
public void transferSplice(String field, Map parameter) {
if (this.cascade(field)) {
this.resolveCascadeObj(field, parameter);
return;
}
Object param = parameter.get(field);
if (this.hasEscapeChar(param)) {
parameter.put(field, this.escapeChar(param.toString()));
}
}
/**
* Handling cascading attributes
*
* @param field cascade field name
* @param parameter parameter Map object
*/
private void resolveCascadeObj(String field, Map parameter) {
int index = field.indexOf(MYBATIS_PLUS_WRAPPER_SEPARATOR);
Object param = parameter.get(field.substring(0, index));
if (param == null) {
return;
}
this.resolveObj(field.substring(index + 1), param);
}
}
4 ObjectLikeSqlConverter.java 轉(zhuǎn)換器類
package com.jerry.market.config;
import lombok.extern.slf4j.Slf4j;
/**
* Universal parameter converter
*
* @author zrj
* @since 2022/4/22
**/
@Slf4j
public class ObjectLikeSqlConverter extends AbstractLikeSqlConverter<Object> {
@Override
public void transferWrapper(String field, Object parameter) {
//No such situation
}
@Override
public void transferSelf(String field, Object parameter) {
//No such situation
}
@Override
public void transferSplice(String field, Object parameter) {
this.resolveObj(field, parameter);
}
}
5 MybatisLikeSqlConfig.java mybatis攔截器注入配置類
package com.jerry.market.config;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.Order;
import java.util.List;
/**
* Mybatis/mybatis-plus fuzzy query statement special character escape configuration
*
* @author zrj
* @since 2022/4/22
**/
@Configuration
@Lazy(false)
@Order
//@DependsOn("pageHelperProperties")
public class MybatisLikeSqlConfig implements InitializingBean {
@Autowired
private List<SqlSessionFactory> sqlSessionFactoryList;
public MybatisLikeSqlInterceptor mybatisSqlInterceptor() {
return new MybatisLikeSqlInterceptor();
}
@Override
public void afterPropertiesSet() throws Exception {
Interceptor interceptor = mybatisSqlInterceptor();
for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
org.apache.ibatis.session.Configuration configuration = sqlSessionFactory.getConfiguration();
List<Interceptor> list = configuration.getInterceptors();
if (!containsInterceptor(configuration, interceptor)) {
configuration.addInterceptor(interceptor);
}
}
}
private boolean containsInterceptor(org.apache.ibatis.session.Configuration configuration,
Interceptor interceptor) {
try {
return configuration.getInterceptors().contains(interceptor);
} catch (Exception var4) {
return false;
}
}
}
4. 測試驗證
mybatis特殊符號處理前,同樣的參數(shù)查詢出多條數(shù)據(jù)。
正常mybatis特殊符號未做轉(zhuǎn)義,導(dǎo)致全部查詢出來
[nio-8080-exec-1] c.j.market.mapper.UserMapper.selectList : ==> Preparing: select id, name, sex, age, born from user WHERE name like concat('%',?,'%')
[nio-8080-exec-1] c.j.market.mapper.UserMapper.selectList : ==> Parameters: %(String)
[nio-8080-exec-1] c.j.market.mapper.UserMapper.selectList : <== Total: 2

mybatis特殊符號處理后
通過mybatis攔截器將特殊符號過濾后,%作為轉(zhuǎn)義字符串正常查詢
[nio-8080-exec-1] c.j.market.mapper.UserMapper.selectList : ==> Preparing: select id, name, sex, age, born from user WHERE name like concat('%',?,'%')
[nio-8080-exec-1] c.j.market.mapper.UserMapper.selectList : ==> Parameters: \%(String)
[nio-8080-exec-1] c.j.market.mapper.UserMapper.selectList : <== Total: 1

到此這篇關(guān)于Mybatis特殊字符轉(zhuǎn)義查詢實現(xiàn)的文章就介紹到這了,更多相關(guān)Mybatis特殊字符轉(zhuǎn)義查詢內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot左腳進(jìn)門之Maven管理家具體步驟
Maven 是一個項目管理和整合工具,通過對 目錄結(jié)構(gòu)和構(gòu)建生命周期 的標(biāo)準(zhǔn)化, 使開發(fā)團(tuán)隊用極少的時間就能夠自動完成工程的基礎(chǔ)構(gòu)建配置,本文介紹SpringBoot左腳進(jìn)門之Maven管理家具體步驟,感興趣的朋友一起看看吧2024-12-12
Spring Boot 參數(shù)校驗的具體實現(xiàn)方式
這篇文章主要介紹了Spring Boot 參數(shù)校驗的具體實現(xiàn)方式,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-06-06

