提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
需求:
自定义mybatis拦截器,要求可以通过注解的形式灵活配置
踩坑:
自定义拦截器和springboot-starter-pageHelper中的分页拦截器执行顺序问题。正常应该先执行自定义拦截器MapperInterceptor,后执行PageInterceptor。
一、自定义mybatis权限拦截器相关组件
1.自定义注解
@ClassMapperPermission
写在Mapper接口类上,表示此类的所有查询方法都需要数据拦截
@MethodMapperPermission
写在Mapper接口方法上,表示此查询方法需要查询数据拦截
@MapperPermissionIgnore
写在Mapper接口方法上,表示此方法忽略数据权限查询
具体代码如下:
import java.lang.annotation.*;
/**
* 描述:
*
* @author ppliu-life
* created in 2022/7/18 9:03
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ClassMapperPermission {
/**
* 需要维护sql片段,取值于sql片段里面的key
*/
String value();
/**
* 需要排除的查询方法
*/
String[] exclude() default {
"selectByPrimaryKey",
"selectByExample",
"selectOneByExample",
"selectByRowBounds",
"selectOne",
"select",
"selectAll"
};
}
``
# 二、使用步骤
## 1.引入库
代码如下(示例):
```c
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
import java.lang.annotation.*;
/**
* 描述:
*
* @author ppliu-life
* created in 2022/7/18 9:04
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MapperPermissionIgnore {
}
import java.lang.annotation.*;
/**
* 描述:
*
* @author ppliu-life
* created in 2022/7/18 9:02
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MethodMapperPermission {
/**
* 需要维护sql片段,取值于sql片段里面的key
*/
String value();
}
2.自定义拦截器MapperInterceptor
import com.szx.core.annotation.ClassMapperPermission;
import com.szx.core.annotation.MapperPermissionIgnore;
import com.szx.core.annotation.MethodMapperPermission;
import com.szx.core.jwt.bean.UserLocalContext;
import lombok.extern.slf4j.Slf4j;
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.mapping.SqlSource;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Properties;
/**
* 描述: sql拦截器.
*
* @author ppliu-life
* created in 2022/7/18 9:37
*/
@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})
})
@Slf4j
public class MapperInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
final Object[] args = invocation.getArgs();
MappedStatement statement = (MappedStatement) args[0];
//验证是否需要拦截
if (!needToBeIntercepted(statement.getId())) {
return invocation.proceed();
}
Object parameterObject = args[1];
BoundSql boundSql = statement.getBoundSql(parameterObject);
//获取原始sql
String originalSql = boundSql.getSql();
//获取sql片段
String sqlFragment = getSqlFragment(statement.getId());
//专注编辑sql
String newSql = splicing(originalSql, sqlFragment);
//替换原有sql
MappedStatement newStatement = newMappedStatement(statement, new BoundSqlSqlSource(boundSql));
MetaObject msObject = MetaObject.forObject(newStatement, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
msObject.setValue("sqlSource.boundSql.sql", newSql);
args[0] = newStatement;
try {
args[5] = boundSql;
} catch (Exception e) {
//执行分页查询,没有args[5]
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
/**
* 判断是否需要被拦截
*/
private boolean needToBeIntercepted(String methodFullPath) throws ClassNotFoundException {
String classFullPath = methodFullPath.substring(0, methodFullPath.lastIndexOf("."));
String methodSimpleName = methodFullPath.substring(methodFullPath.lastIndexOf(".") + 1);
if ("insert!selectKey".equals(methodSimpleName)) {
return false;
}
ClassMapperPermission classMapperPermission = Class.forName(classFullPath).getAnnotation(ClassMapperPermission.class);
if (classMapperPermission == null) {
for (Method method : Class.forName(classFullPath).getDeclaredMethods()) {
if (method.getName().equals(methodSimpleName)) {
if (method.getAnnotation(MethodMapperPermission.class) == null || method.getAnnotation(MapperPermissionIgnore.class) != null) {
return false;
}
}
}
return true;
}
String[] excludeList = classMapperPermission.exclude();
for (Method method : Class.forName(classFullPath).getDeclaredMethods()) {
if (method.getName().equals(methodSimpleName) && method.getAnnotation(MapperPermissionIgnore.class) != null) {
return false;
}
if (method.getName().equals(methodSimpleName) && Arrays.stream(excludeList).anyMatch(s -> s.equals(method.getName()))) {
return false;
}
}
return true;
}
/**
* 获取注解里面的sql片段
*/
private String getSqlFragment(String methodFullPath) throws ClassNotFoundException {
String classFullPath = methodFullPath.substring(0, methodFullPath.lastIndexOf("."));
String methodSimpleName = methodFullPath.substring(methodFullPath.lastIndexOf(".") + 1);
ClassMapperPermission classMapperPermission = Class.forName(classFullPath).getAnnotation(ClassMapperPermission.class);
String key = null;
if (classMapperPermission != null) {
key = classMapperPermission.value();
}
for (Method method : Class.forName(classFullPath).getDeclaredMethods()) {
if (method.getName().equals(methodSimpleName) && method.getAnnotation(MethodMapperPermission.class) != null) {
MethodMapperPermission methodMapperPermission = method.getAnnotation(MethodMapperPermission.class);
key = methodMapperPermission.value();
break;
}
}
if (key == null) {
throw new RuntimeException("sql 片段丢失");
}
return key;
}
/**
* sql拼接.
*
* @param originalSql 原始sql.
* @param sqlFragment 权限sql片段.
* @return 新的sql.
*/
private String splicing(String originalSql, String sqlFragment) {
return sqlFragment.replaceAll("#userId#", UserLocalContext.getUserSubject().getUserId() + "")
.replaceAll("originalSql", originalSql);
}
public static MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
MappedStatement.Builder builder =
new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
builder.resource(ms.getResource());
builder.fetchSize(ms.getFetchSize());
builder.statementType(ms.getStatementType());
builder.keyGenerator(ms.getKeyGenerator());
if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
StringBuilder keyProperties = new StringBuilder();
for (String keyProperty : ms.getKeyProperties()) {
keyProperties.append(keyProperty).append(",");
}
keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
builder.keyProperty(keyProperties.toString());
}
builder.timeout(ms.getTimeout());
builder.parameterMap(ms.getParameterMap());
builder.resultMaps(ms.getResultMaps());
builder.resultSetType(ms.getResultSetType());
builder.cache(ms.getCache());
builder.flushCacheRequired(ms.isFlushCacheRequired());
builder.useCache(ms.isUseCache());
return builder.build();
}
// 定义一个内部辅助类,作用是包装sq
public static class BoundSqlSqlSource implements SqlSource {
private final BoundSql boundSql;
public BoundSqlSqlSource(BoundSql boundSql) {
this.boundSql = boundSql;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
return boundSql;
}
}
3.注册到spring容器中
踩坑的地方来了:
在执行拦截器的是先注册的后执行,后注册和先执行,类似于现实生活中剥洋葱的例子。
解决思路:一直等pageInterceptor进去注册进去以后我们再注册
代码如下:
import com.github.pagehelper.PageInterceptor;
import com.szx.core.interceptor.MapperInterceptor;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import javax.annotation.PostConstruct;
import java.util.List;
/**
* 描述:
*
* @author ppliu-life
* created in 2022/7/18 14:47
*/
@SpringBootConfiguration
public class MapperPermissionConfiguration {
@Autowired
private List<SqlSessionFactory> sqlSessionFactoryList;
@PostConstruct
private void initInterceptor() {
new Thread(() -> {
boolean hasPageInterceptor = false;
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryList.get(0);
Configuration configuration = sqlSessionFactory.getConfiguration();
while (!hasPageInterceptor) {
if (configuration.getInterceptors().stream().anyMatch(interceptor -> interceptor.getClass().equals(PageInterceptor.class))) {
hasPageInterceptor = true;
}
}
MapperInterceptor mapperInterceptor = new MapperInterceptor();
configuration.addInterceptor(mapperInterceptor);
}).start();
}
}
总结
以上就是mybatis分页插件的创建以及注册时候踩的坑,欢迎留言关注讨论