有这么一个场景
比如说现在有两个表,一个员工表,一个部门表,员工表内含有部门 code 字段
CREATE TABLE `employee` (
`id` bigint NOT NULL COMMENT '主键',
`name` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '员工姓名',
`dept_code` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '员工所属部门',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE `dept` (
`id` bigint NOT NULL COMMENT '主键',
`p_code` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '父编码',
`code` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '部门编码',
`name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '部门名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
现在需要查询某个部门下的所有员工。
由于部门大多数情况下是树状结构的,所以我们通常的做法是,先根据部门编码查询出自身及所有子部门的编码,作为部门编码集合list,然后
SELECT
*
FROM
employee
WHERE
dept_code IN (
"dept1",
"dept2")
用 mybatis-plus 的写法就是
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
// 查询所有子部门code
List<String> list = new ArrayList<>();
list.add("dept1");
list.add("dept2");
wrapper.in("dept_code", list);
List<Employee> result = mapper.selectList(wrapper);
System.out.println(result);
正常情况下,没有任何问题,逻辑也很简单。
但是,如果部门编码传错了,或者说没有子部门,即 list 为空,sql 相应变为了
SELECT
*
FROM
employee
WHERE
dept_code IN ()
于是自然而然的抛出了
org.springframework.jdbc.BadSqlGrammarException
最容易想到的解决方式:
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
// 查询所有子部门code
List<String> list = new ArrayList<>();
list.add("dept1");
list.add("dept2");
// 如果为空,说明不应该查出任何人,可以加个 1=0 的条件
if (CollectionUtils.isEmpty(list)) {
wrapper.eq("1", "0");
} else {
// 不为空则正常查询
wrapper.in("dept_code", list);
}
List<Employee> result = mapper.selectList(wrapper);
System.out.println(result);
就是如果 list 为空,则 sql 变成了
SELECT
*
FROM
employee
WHERE
1 = 0
不会查询出任何数据,即该部门下没有任何员工,符合查询逻辑。
但是这种在 java 业务代码里添加 if else 逻辑的方式不够优雅。
想到之前看到了 mybatis-plus 的插件,于是想着能不能通过插件的方式完善该功能
直接上代码
@NoArgsConstructor
public class EmptyListPlugin implements InnerInterceptor {
private static final Pattern PATTERN = Pattern.compile("[\"|'](.*?)[\"|']");
@Override
public boolean willDoQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
return !hasEmptyList(boundSql.getSql());
}
/**
* 判断是否存在空list
*/
private static boolean hasEmptyList(String sql) {
sql = removeInterference(sql);
List<String> keywordList = Lists.newArrayList("in", "IN");
boolean hasEmptyList = Boolean.FALSE;
for (String keyword : keywordList) {
List<String> matcherList = Lists.newArrayList();
// 获取关键词,关键词前必须为空白字符,但以关键词开头的单词也会被匹配
// 匹配以" in (" 开头的,")"结尾的最短字符串
Pattern pattern = Pattern.compile("(?<=\\s" + keyword + "\\s{0,10}\\().*?(?=\\))");
Matcher matcher = pattern.matcher(sql);
while (matcher.find()) {
matcherList.add(matcher.group());
}
if (CollectionUtils.isNotEmpty(matcherList)) {
hasEmptyList = checkEmptyList(matcherList);
if (hasEmptyList) {
break;
}
}
}
return hasEmptyList;
}
/**
* 去除字符中的干扰项,避免字符串中的内容干扰判断。
*/
private static String removeInterference(String sql) {
Matcher matcher = PATTERN.matcher(sql);
while (matcher.find()) {
String replaceWord = matcher.group();
sql = sql.replace(replaceWord, "''");
}
return sql;
}
/**
* 校验是否有空的字符串
*/
private static boolean checkEmptyList(List<String> matcherList) {
boolean isHaveEmptyList = Boolean.FALSE;
// 获取()内的内容
for (String subSql : matcherList) {
// 如果关键词之后无任何sql语句,则sql语句结尾为关键词,此时判定为空列表
if (StringUtils.isBlank(subSql)) {
isHaveEmptyList = Boolean.TRUE;
break;
}
}
return isHaveEmptyList;
}
}
记得添加该插件到 MybatisPlusInterceptor
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
List<InnerInterceptor> list = new ArrayList<>();
list.add(emptyListPlugin());
interceptor.setInterceptors(list);
return interceptor;
}
@Bean
public EmptyListPlugin emptyListPlugin() {
return new EmptyListPlugin();
}