问题背景

在项目开发过程中,基本都会有列表条件查询,例如用户管理会有通过用户姓名模糊查询用户,课程管理会有课程名称模糊查询课程等等。

而查询过程中如果用户在界面上输入一些特殊字符,例如:%_等等,这时可以发现最终生成的SQL大致如下:

select * from t_user where name like '%%%'
select * from t_user where name like '%_%'

这样就会导致将用户表的所有数据都查询出来,这显然不是用户想看到的结果,用户的本意只是想找名字叫%的人,而由于这些字符因为在数据库(Mysql等)中有着特殊含义,

%表示任意字符,_表示任意一个字符,所有导致了这样的结果。

如何解决

很容易想到的解决方案就是在生成SQL的时候对这些个字符进行转义,让数据库将这些字符当作普通的字符串来处理,而不是字符通配符。

例如如果是使用mybatisplus可以这样写:

LambdaQueryWrapper<User> queryWrapper = Wrappers.lambdaQuery(User.class)
				//使用对用户姓名里面的%_\进行替换
                .like(StringUtils.isNotBlank(qo.getName()), User::getName, replaceStr(qo.getName()));

  private String replaceStr(String str) {
        if (str != null && !"".equals(str)) {
            str = str.replaceAll("\\\\", "\\\\\\\\")
                    .replace("_", "\\_").replace("\'", "\\'")
                    .replace("%", "\\%").replace("*", "\\*");

        }
        return str;
    }

 

这样如果输入的name=%,最终生成的SQL如下:

select * from t_user where name like '%\%%'

这里的\%就会当作普通的字符串进行条件匹配,如果有name里面含有%就会被查询出来,满足了用户的期望。

但是,一个系统中这样的条件查询肯定不止一个,会有几十、几百个,每一个都这样处理会显得很麻烦,那有什么办法可以统一处理吗?

当然是有的,咱们不是有AOP嘛,对所有分页查询的入参做个拦截,对这些个特殊字符进行处理,然后再去调用接口就可以了,可以这样处理:

/**
 * 统一处理列表分页条件查询的入参的特殊字符
 * 再Mysql查询中有%_\三个特殊字符
 * %:代表通配符任意字符
 * _: 代表任意一个字符
 * \: 在Java代码中需要四个\\\\才能查出数据库中一个\
 *
 **/
@Component
@Aspect
public class QueryRequestAspect {
    
    /**
     * 定义切面,根据自己的情况定义
     * 所有controller下面所有page开头的接口
     */
    @Pointcut(value = "execution(* com.xxx.xxx.controller..*.page*(..))")
    private void queryPointCut() {
    }
	//可以使用Before类型的通知、也可以使用Around类型通知
    @SneakyThrows
    @Before(value = "queryPointCut()")
    public void process(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        if (args != null && args.length != 0) {
            //对入参进行处理
            for (int i = 0; i < args.length; i++) {
                Field[] declaredFields = args[i].getClass().getDeclaredFields();
                for (Field declaredField : declaredFields) {
                    declaredField.setAccessible(true);
                    Object o = declaredField.get(args[i]);
                    if (o instanceof String) {
                        declaredField.set(args[i], replaceStr((String) o));
                    }
                }
            }
        }
    }

    private String replaceStr(String str) {
        if (str != null && !"".equals(str)) {
            str = str.replaceAll("\\\\", "\\\\\\\\")
                    .replace("_", "\\_").replace("\'", "\\'")
                    .replace("%", "\\%").replace("*", "\\*");

        }
        return str;
    }

}

这样通过这个QueryRequestAspect 切面就可以统一处理分页查询的条件入参了。