Mybatis特殊字符轉(zhuǎn)義查詢實(shí)現(xiàn)
1. 問題描述
MyBatis作為目前最常用的ORM數(shù)據(jù)庫訪問持久層框架,其本身支持動(dòng)態(tài)SQL存儲(chǔ)映射等高級(jí)特性也非常優(yōu)秀,通過Mapper文件采用動(dòng)態(tài)代理模式使SQL與業(yè)務(wù)代碼相解耦,日常開發(fā)中使用也非常廣泛。
正常模糊匹配查詢時(shí)是沒有什么問題的,但是如果需要模糊查詢字段含有特殊字符比如% _ / 等時(shí)就會(huì)出現(xiàn)查詢不準(zhǔn)確的問題。本文就是通過mybatis攔截器實(shí)現(xiàn)特殊字符轉(zhuǎn)義實(shí)現(xiàn)mybatis特殊字符查詢問題。
2. 解決方案
MybatisLikeSqlInterceptor:
通過 @Intercepts 注解指定攔截器插件的屬性:分別指定了攔截器類型 Executor, 攔截方法名 query (共有2個(gè)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 ,方法時(shí)序如下:
3. 設(shè)計(jì)實(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 代碼實(shí)現(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 實(shí)體類 * @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 實(shí)例對象的集合 * @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 實(shí)體類 * @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("查詢失敗"); } /** * 通過實(shí)體作為篩選條件查詢 * * @return Response對象 */ @ApiOperation("通過實(shí)體作為篩選條件查詢") @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 實(shí)例對象 */ User selectById(Object id); /** * 分頁查詢 * * @param start 查詢起始位置 * @param limit 查詢條數(shù) * @return 對象列表 */ List<User> selectPage(int start, int limit); /** * 查詢?nèi)? * * @return 對象列表 */ List<User> selectAll(); /** * 通過實(shí)體作為篩選條件查詢 * * @param user 實(shí)例對象 * @return 對象列表 */ List<User> selectList(User user); /** * 新增數(shù)據(jù) * * @param user 實(shí)例對象 * @return 影響行數(shù) */ int insert(User user); /** * 批量新增 * * @param users 實(shí)例對象的集合 * @return 影響行數(shù) */ int batchInsert(List<User> users); /** * 修改數(shù)據(jù) * * @param user 實(shí)例對象 * @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ù)實(shí)現(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 實(shí)例對象 */ @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 實(shí)例對象的集合 */ @Override public List<User> selectAll() { return this.userMapper.selectAll(); } /** * 根據(jù)條件查詢 * * @return 實(shí)例對象的集合 */ @Override public List<User> selectList(User user) { return this.userMapper.selectList(user); } /** * 新增數(shù)據(jù) * * @param user 實(shí)例對象 * @return 實(shí)例對象 */ @Override public int insert(User user) { return this.userMapper.insert(user); } /** * 批量新增 * * @param users 實(shí)例對象的集合 * @return 生效的條數(shù) */ @Override public int batchInsert(List<User> users) { return this.userMapper.batchInsert(users); } /** * 修改數(shù)據(jù) * * @param user 實(shí)例對象 * @return 實(shí)例對象 */ @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 實(shí)例對象 */ 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(); /** * 通過實(shí)體作為篩選條件查詢 * * @param user 實(shí)例對象 * @return 對象列表 */ List<User> selectList(User user); /** * 新增數(shù)據(jù) * * @param user 實(shí)例對象 * @return 影響行數(shù) */ int insert(User user); /** * 批量新增 * * @param users 實(shí)例對象的集合 * @return 影響行數(shù) */ int batchInsert(List<User> users); /** * 修改數(shù)據(jù) * * @param user 實(shí)例對象 * @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> <!-- 查詢單個(gè) --> <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> <!--通過實(shí)體作為篩選條件查詢--> <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 攔截器實(shí)現(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. 測試驗(yàn)證
mybatis特殊符號(hào)處理前,同樣的參數(shù)查詢出多條數(shù)據(jù)。
正常mybatis特殊符號(hào)未做轉(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特殊符號(hào)處理后
通過mybatis攔截器將特殊符號(hào)過濾后,%作為轉(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)義查詢實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Mybatis特殊字符轉(zhuǎn)義查詢內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mybatis批量插入返回成功的數(shù)目實(shí)例
這篇文章主要介紹了Mybatis批量插入返回成功的數(shù)目實(shí)例,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12Java countDownLatch如何實(shí)現(xiàn)多線程任務(wù)阻塞等待
這篇文章主要介紹了Java countDownLatch如何實(shí)現(xiàn)多線程任務(wù)阻塞等待,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10Spring的連接數(shù)據(jù)庫以及JDBC模板(實(shí)例講解)
下面小編就為大家?guī)硪黄猄pring的連接數(shù)據(jù)庫以及JDBC模板(實(shí)例講解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-10-10Java實(shí)戰(zhàn)之小蜜蜂擴(kuò)音器網(wǎng)上商城系統(tǒng)的實(shí)現(xiàn)
這篇文章主要介紹了如何利用Java實(shí)現(xiàn)簡單的小蜜蜂擴(kuò)音器網(wǎng)上商城系統(tǒng),文中采用到的技術(shù)有JSP、Servlet?、JDBC、Ajax等,感興趣的可以動(dòng)手試一試2022-03-03Java使用pulsar-flink-connector讀取pulsar catalog元數(shù)據(jù)代碼剖析
這篇文章主要介紹了Java使用pulsar-flink-connector讀取pulsar catalog元數(shù)據(jù)代碼剖析,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08在IDEA中安裝scala、maven、hadoop遇到的問題小結(jié)
這篇文章主要介紹了在IDEA中安裝scala、maven、hadoop遇到的問題小結(jié),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10Java實(shí)現(xiàn)一個(gè)簡單的線程池代碼示例
線程池是管理線程的一個(gè)池子,通過阻塞隊(duì)列管理任務(wù),主要參數(shù)包括corePoolSize、maximumPoolSize、keepAliveTime等,這篇文章主要介紹了Java實(shí)現(xiàn)一個(gè)簡單的線程池的相關(guān)資料,需要的朋友可以參考下2024-09-09