springboot+mybatis拦截器+自定义注解实现业务数据权限控制、逻辑删除
当【指定查询字段】且【分页查询】时,查询过滤存在问题:查询字段必须包含【deleteFlag】和【权限字段】,否则分页计数SQL会报错。
分页查询SQL如下:
SELECT COUNT(*) FROM (SELECT 指定字段1,指定字段2 FROM 表名) TOTAL
在经过逻辑删除过滤和业务数据权限过滤方法的修改后,SQL语句会变成:
SELECT COUNT(*) FROM (SELECT 指定字段1,指定字段2 FROM 表名) TOTAL WHERE TOTAL.delete_flag = ‘0’ AND TOTAL.权限字段 = ‘xxx’
因为TOTAL中不存在【deleteFlag】和【权限字段】,所以会报错。
解决思路:
- 计数 SQL 应该把过滤条件写在子查询内部,而不是加在外层。
- 因此拦截器里必须判断:当前 SQL 是不是“分页计数”场景,如果是,就把条件塞进子查询 () 内部;否则保持原逻辑即可。
代码实现:
修改查询过滤(select)方法
public Object select(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
// 获取当前查询的实体类类型
Class<?> clazz = getEntityClass(mappedStatement);
if (clazz == null) {
return invocation.proceed();
}
// 解析和修改 SQL
BoundSql boundSql = statementHandler.getBoundSql();
String originalSql = boundSql.getSql();
try {
Statement statement = CCJSqlParserUtil.parse(originalSql);
if (statement instanceof Select) {
Select selectStatement = (Select) statement;
SelectBody selectBody = selectStatement.getSelectBody();
if (selectBody instanceof PlainSelect) {
PlainSelect ps = (PlainSelect) selectBody;
//---------------------------------------- 修改开始 ----------------------------------------
// 判断是否是分页计数查询
if (isPageCountSql(ps)) {
// 分页计数 SQL,把条件塞进子查询
SubSelect subSelect = (SubSelect) ps.getFromItem();
SelectBody innerSelectBody = subSelect.getSelectBody();
if (innerSelectBody instanceof PlainSelect) {
PlainSelect innerPs = (PlainSelect) innerSelectBody;
String innerAlias = innerPs.getFromItem().getAlias() == null ? null : innerPs.getFromItem().getAlias().getName();
Expression innerWhere = innerPs.getWhere();
innerWhere = addDeleteFlag(innerAlias, innerPs, innerWhere, clazz);
innerWhere = addBusinessDataAuto(innerAlias, innerPs, innerWhere, clazz);
// 子查询已经包含过滤条件,外层无需再过滤
ps.setWhere(null);
}
} else {
// 普通查询,按原逻辑处理
String aliasName = ps.getFromItem().getAlias() == null ? null : ps.getFromItem().getAlias().getName();
Expression where = ps.getWhere();
where = addDeleteFlag(aliasName, ps, where, clazz);
where = addBusinessDataAuto(aliasName, ps, where, clazz);
}
//---------------------------------------- 修改结束 ----------------------------------------
// 使用反射更新 BoundSql 中的 SQL
updateBoundSqlSql(boundSql, selectStatement.toString());
}
}
} catch (Exception e) {
throw new SQLException("解析SQL失败!\n" +
"SQL:" + originalSql, e);
}
return invocation.proceed();
}
/**
* 判断是否是分页计数 SQL:
* 最外层是 SELECT COUNT(*) FROM (子查询) TOTAL
*/
public boolean isPageCountSql(PlainSelect plainSelect) {
// 只有一项 selectItem 且是 COUNT(*)
if (plainSelect.getSelectItems().size() != 1) {
return false;
}
String selectItem = plainSelect.getSelectItems().get(0).toString();
if (!selectItem.matches("(?i)COUNT\\(\\s*\\*\\s*\\)")) {
return false;
}
// FROM 是一个子查询
if (!(plainSelect.getFromItem() instanceof net.sf.jsqlparser.statement.select.SubSelect)) {
return false;
}
// 子查询别名是 TOTAL
net.sf.jsqlparser.statement.select.SubSelect subSelect =
(net.sf.jsqlparser.statement.select.SubSelect) plainSelect.getFromItem();
Alias alias = subSelect.getAlias();
return alias != null && "TOTAL".equalsIgnoreCase(alias.getName());
}