插件属于一种常见扩展方式,大多数开源框架也都支持用户通过添加自定义插件方式扩展或者改变原有功能。实际上,MyBatis插件设计为拦截器(Interceptor),通过责任链模式和JDK动态代理实现。
在MyBatis中,Interceptor允许拦截4大核心组件,主要包括执行器(Executor
)、参数处理器(ParameterHandler
)、结果集处理器(ResultSetHandler
)以及StatementHandler
(语句处理器)。
01 接口定义
java
public interface Interceptor {
// 执行拦截逻辑方法
Object intercept(Invocation invocation) throws Throwable;
// 插件对象
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
// 根据配置初始化对象
default void setProperties(Properties properties) {
// NOP
}
}
02 自定义插件
2.1 自定义插件实现
java
// @Signature表示方法签名, 唯一确定一个方法
@Intercepts({
@Signature(
type = Executor.class, // 需要拦截的类型
method = "query", // 需要拦截的方法
// 指定被拦截方法参数列表
args={MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class}
),
@Signature(type = Executor.class, method = "close", args = {boolean.class})
})
public class MyInterceptor implements Interceptor {
private int prop;
// 执行拦截逻辑的方法
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("MyInterceptor 拦截之前 ....");
Object obj = invocation.proceed();
System.out.println("MyInterceptor 拦截之后 ....");
return obj;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target,this);
}
@Override
public void setProperties(Properties properties) {
System.out.println("---->" + properties.get("prop"));
}
public int getTestProp() {
return prop;
}
public void setProp(int prop) {
this.prop = prop;
}
}
2.2 配置自定义插件
xml
<configuration>
<plugins>
<plugin interceptor="com.feiyu.interceptor.MyInterceptor">
<property name="prop" value="1000"/>
</plugin>
</plugins>
</configuration>
03 插件实现原理
3.1 初始化插件
java
public class XMLConfigBuilder extends BaseBuilder {
private void parseConfiguration(XNode root) {
try {
// <properties>
propertiesElement(root.evalNode("properties"));
// <settings>
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 读取文件
loadCustomVfs(settings);
// 日志设置
loadCustomLogImpl(settings);
// 类型别名
typeAliasesElement(root.evalNode("typeAliases"));
// 插件
pluginElement(root.evalNode("plugins"));
// 用于创建对象
objectFactoryElement(root.evalNode("objectFactory"));
// 用于对对象进行加工
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 反射工具箱
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// <settings>子标签赋值
settingsElement(settings);
// 创建数据源
environmentsElement(root.evalNode("environments"));
// 数据库厂商
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 类型处理器
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析Mapper映射器
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
// <plugins>
private void pluginElement(XNode parent) throws Exception {
if (parent == null) {
return;
}
// 获取所有子节点
for (XNode child : parent.getChildren()) {
// <plugin interceptor=?>
String interceptor = child.getStringAttribute("interceptor");
// 获取<plugin>所有<property>子标签
Properties properties = child.getChildrenAsProperties();
// 获取Interceptor对象
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
// 设置属性
interceptorInstance.setProperties(properties);
// Configuration添加Interceptor
configuration.addInterceptor(interceptorInstance);
}
}
}
3.2 插件调用链
java
public class Configuration {
protected final InterceptorChain interceptorChain = new InterceptorChain();
public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}
}
public class InterceptorChain {
// 缓存Interceptor
private final List<Interceptor> interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) { // 获取拦截器链所有拦截器
target = interceptor.plugin(target); // 创建对应拦截器代理对象
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
04 创建插件对象
java
public class Configuration {
// 参数处理器
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
// 参数处理器
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
// 植入插件逻辑, 返回代理对象
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
// 结果处理器
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
// 结果集处理器
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
// 植入插件逻辑, 返回代理对象
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
// 语句处理器
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 植入插件逻辑, 返回代理对象
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
// 执行器
public Executor newExecutor(Transaction transaction) {
return newExecutor(transaction, defaultExecutorType);
}
// 执行器
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
// 批量执行器
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
}
// 重用执行器
else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
// 简单执行器
executor = new SimpleExecutor(this, transaction);
}
// 开启二级缓存
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 植入插件逻辑
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
}
4.1 插件接口
java
public interface Interceptor {
// 执行拦截逻辑方法
Object intercept(Invocation invocation) throws Throwable;
// 插件对象
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
// 根据配置初始化对象
default void setProperties(Properties properties) {
// NOP
}
}
4.2 插件代理对象
xml
public class Plugin implements InvocationHandler {
private final Object target; // 目标对象
private final Interceptor interceptor; // 拦截器
private final Map<Class<?>, Set<Method>> signatureMap; // @Signature注解信息
private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
this.target = target;
this.interceptor = interceptor;
this.signatureMap = signatureMap;
}
// 创建目标代理对象: Executor、ParameterHandler、ResultSetHandler或者StatementHandler
public static Object wrap(Object target, Interceptor interceptor) {
// 获取插件拦截方法(@Intercepts{@Signature})
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
// 获取目标类型
Class<?> type = target.getClass();
// 获取目标类型实现的所有接口
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
// 如果目标类型实现接口创建代理对象
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
// 返回目标对象
return target;
}
// 代理对象方法调用执行代码
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 获取当前方法所在类或接口中,可被当前Interceptor拦截的方法
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
// 当前调用方法需要被拦截, 执行拦截操作
return interceptor.intercept(new Invocation(target, method, args));
}
// 不需要拦截, 则调用目标对象方法
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
// 获取@Intercepts注解
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
// issue #251
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
Signature[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
for (Signature sig: sigs) {
Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
try {
// 获取组件方法
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
} catch (NoSuchMethodException e) {
throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
}
}
return signatureMap;
}
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<>();
while (type != null) {
for (Class<?> c : type.getInterfaces()) {
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class<?>[interfaces.size()]);
}
}
05 分页插件
5.1 引入插件
xml
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>4.1.6</version>
</dependency>
5.2 配置插件
xml
<plugin interceptor="com.github.pagehelper.PageHelper">
<property name="dialect" value="mysql" />
<!-- 设置为true, RowBounds参数offset当成pageNum页码使用, 默认false -->
<property name="offsetAsPageNum" value="true" />
<!-- 设置为true, 使用RowBounds分页进行count查询, 默认false -->
<property name="rowBoundsWithCount" value="true" />
<!-- 设置为true, 如果pageSize=0或者RowBounds.limit = 0就会查询出全部结果 -->
<property name="pageSizeZero" value="true" />
<!-- 3.3.0版本可用: 分页参数合理化, 默认false禁用 -->
<!-- 启用合理化, 如果pageNum < 1会查询第一页, 如果pageNum > pages会查询最后一页 -->
<!-- 禁用合理化, 如果pageNum < 1或pageNum > pages返回空数据 -->
<property name="reasonable" value="false" />
<!-- 3.5.0版本可用: 支持startPage(Object params)方法 -->
<!-- 增加params参数配置参数映射, 用于从Map或ServletRequest取值 -->
<!-- 可以配置pageNum, pageSize, count, pageSizeZero, reasonable, 不配置使用默认值 -->
<property name="params" value="pageNum=start;pageSize=limit;" />
<!-- always: 总是返回PageInfo类型
check: 返回类型是否为PageInfo
none: 返回Page
-->
<property name="returnPageInfo" value="check" />
</plugin>
5.3 原理剖析
5.3.1 初始化
java
@Intercepts(
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
)
)
public class PageHelper implements Interceptor {
// SQL工具类
private SqlUtil sqlUtil;
// 属性参数信息
private Properties properties;
// 配置对象方式
private SqlUtilConfig sqlUtilConfig;
// 自动获取Dialect, 如果没有setProperties或setSqlUtilConfig, 也可以正常进行
private boolean autoDialect = true;
//运 行时自动获取Dialect
private boolean autoRuntimeDialect;
// 多数据源时, 获取jdbcurl后是否关闭数据源
private boolean closeConn = true;
// 缓存
private Map<String, SqlUtil> urlSqlUtilMap = new ConcurrentHashMap<String, SqlUtil>();
private ReentrantLock lock = new ReentrantLock();
// 获取任意查询方法count总数
public static long count(ISelect select) {
Page<?> page = startPage(1, -1, true);
select.doSelect();
return page.getTotal();
}
// 分页
public static <E> Page<E> startPage(int pageNum, int pageSize) {
return startPage(pageNum, pageSize, true);
}
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count) {
return startPage(pageNum, pageSize, count, null);
}
public static <E> Page<E> startPage(int pageNum, int pageSize, String orderBy) {
Page<E> page = startPage(pageNum, pageSize);
page.setOrderBy(orderBy);
return page;
}
public static <E> Page<E> offsetPage(int offset, int limit) {
return offsetPage(offset, limit, true);
}
public static <E> Page<E> offsetPage(int offset, int limit, boolean count) {
Page<E> page = new Page<E>(new int[]{offset, limit}, count);
Page<E> oldPage = SqlUtil.getLocalPage();
if (oldPage != null && oldPage.isOrderByOnly()) {
page.setOrderBy(oldPage.getOrderBy());
}
SqlUtil.setLocalPage(page);
return page;
}
public static <E> Page<E> offsetPage(int offset, int limit, String orderBy) {
Page<E> page = offsetPage(offset, limit);
page.setOrderBy(orderBy);
return page;
}
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable) {
return startPage(pageNum, pageSize, count, reasonable, null);
}
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
Page<E> page = new Page<E>(pageNum, pageSize, count);
page.setReasonable(reasonable);
page.setPageSizeZero(pageSizeZero);
// 当已经执行过orderBy的时候
Page<E> oldPage = SqlUtil.getLocalPage();
if (oldPage != null && oldPage.isOrderByOnly()) {
page.setOrderBy(oldPage.getOrderBy());
}
SqlUtil.setLocalPage(page);
return page;
}
public static <E> Page<E> startPage(Object params) {
Page<E> page = SqlUtil.getPageFromObject(params);
// 当已经执行过orderBy的时候
Page<E> oldPage = SqlUtil.getLocalPage();
if (oldPage != null && oldPage.isOrderByOnly()) {
page.setOrderBy(oldPage.getOrderBy());
}
SqlUtil.setLocalPage(page);
return page;
}
// 排序
public static void orderBy(String orderBy) {
Page<?> page = SqlUtil.getLocalPage();
if (page != null) {
page.setOrderBy(orderBy);
} else {
page = new Page();
page.setOrderBy(orderBy);
page.setOrderByOnly(true);
SqlUtil.setLocalPage(page);
}
}
// 获取orderBy
public static String getOrderBy() {
Page<?> page = SqlUtil.getLocalPage();
if (page != null) {
String orderBy = page.getOrderBy();
if (StringUtil.isEmpty(orderBy)) {
return null;
} else {
return orderBy;
}
}
return null;
}
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
// 设置属性值
public void setProperties(Properties p) {
checkVersion();
// 多数据源时, 获取jdbcurl后是否关闭数据源
String closeConn = p.getProperty("closeConn");
// 解决#97
if(StringUtil.isNotEmpty(closeConn)){
this.closeConn = Boolean.parseBoolean(closeConn);
}
// 初始化SqlUtil的PARAMS
SqlUtil.setParams(p.getProperty("params"));
// 数据库方言
String dialect = p.getProperty("dialect");
String runtimeDialect = p.getProperty("autoRuntimeDialect");
if (StringUtil.isNotEmpty(runtimeDialect) && runtimeDialect.equalsIgnoreCase("TRUE")) {
this.autoRuntimeDialect = true;
this.autoDialect = false;
this.properties = p;
} else if (StringUtil.isEmpty(dialect)) {
autoDialect = true;
this.properties = p;
} else {
autoDialect = false;
sqlUtil = new SqlUtil(dialect);
sqlUtil.setProperties(p);
}
}
private void checkVersion() {
//MyBatis3.2.0版本校验
try {
Class.forName("org.apache.ibatis.scripting.xmltags.SqlNode");//SqlNode是3.2.0之后新增的类
} catch (ClassNotFoundException e) {
throw new RuntimeException("您使用的MyBatis版本太低,MyBatis分页插件PageHelper支持MyBatis3.2.0及以上版本!");
}
}
}
5.3.2 执行代理
java
public class PageHelper implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
if (autoRuntimeDialect) {
SqlUtil sqlUtil = getSqlUtil(invocation);
return sqlUtil.processPage(invocation);
} else {
if (autoDialect) {
initSqlUtil(invocation);
}
return sqlUtil.processPage(invocation);
}
}
// 初始化sqlUtil
public synchronized void initSqlUtil(Invocation invocation) {
if (this.sqlUtil == null) {
this.sqlUtil = getSqlUtil(invocation);
if (!autoRuntimeDialect) {
properties = null;
sqlUtilConfig = null;
}
autoDialect = false;
}
}
// 根据datasource创建对应SqlUtil
public SqlUtil getSqlUtil(Invocation invocation) {
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
// 改为dataSource缓存
DataSource dataSource = ms.getConfiguration().getEnvironment().getDataSource();
String url = getUrl(dataSource);
if (urlSqlUtilMap.containsKey(url)) {
return urlSqlUtilMap.get(url);
}
try {
lock.lock();
if (urlSqlUtilMap.containsKey(url)) {
return urlSqlUtilMap.get(url);
}
if (StringUtil.isEmpty(url)) {
throw new RuntimeException("无法自动获取jdbcUrl,请在分页插件中配置dialect参数!");
}
String dialect = Dialect.fromJdbcUrl(url);
if (dialect == null) {
throw new RuntimeException("无法自动获取数据库类型,请通过dialect参数指定!");
}
SqlUtil sqlUtil = new SqlUtil(dialect);
if (this.properties != null) {
sqlUtil.setProperties(properties);
} else if (this.sqlUtilConfig != null) {
sqlUtil.setSqlUtilConfig(this.sqlUtilConfig);
}
urlSqlUtilMap.put(url, sqlUtil);
return sqlUtil;
} finally {
lock.unlock();
}
}
// 获取url
public String getUrl(DataSource dataSource){
Connection conn = null;
try {
conn = dataSource.getConnection();
return conn.getMetaData().getURL();
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
if(conn != null){
try {
if(closeConn){
conn.close();
}
} catch (SQLException e) {
//ignore
}
}
}
}
}
5.3.3 SqlUtil
数据库类型专用SQL工具类,提供查询数量、执行分页查询、保存Page分页对象操作。SqlUtil内置Parser对象,MySQL数据库引擎为MysqlParser,Oracle数据库引擎为OracleParser。
java
public class SqlUtil implements Constant {
public SqlUtil(String strDialect) {
if (strDialect == null || "".equals(strDialect)) {
throw new IllegalArgumentException("Mybatis分页插件无法获取dialect参数!");
}
Exception exception = null;
try {
Dialect dialect = Dialect.of(strDialect);
parser = AbstractParser.newParser(dialect);
} catch (Exception e) {
exception = e;
// 异常尝试反射, 允许自己写实现类传递进来
try {
Class<?> parserClass = Class.forName(strDialect);
if (Parser.class.isAssignableFrom(parserClass)) {
parser = (Parser) parserClass.newInstance();
}
} catch (ClassNotFoundException ex) {
exception = ex;
} catch (InstantiationException ex) {
exception = ex;
} catch (IllegalAccessException ex) {
exception = ex;
}
}
if (parser == null) {
throw new RuntimeException(exception);
}
}
}
java
public abstract class AbstractParser implements Parser, Constant {
//处理SQL
public static final SqlParser sqlParser = new SqlParser();
public static Parser newParser(Dialect dialect) {
Parser parser = null;
switch (dialect) {
case mysql:
case mariadb:
case sqlite: parser = new MysqlParser(); break;
case oracle: parser = new OracleParser(); break;
case hsqldb: parser = new HsqldbParser(); break;
case sqlserver: parser = new SqlServerParser(); break;
case sqlserver2012: parser = new SqlServer2012Dialect(); break;
case db2: parser = new Db2Parser(); break;
case postgresql: parser = new PostgreSQLParser(); break;
case informix: parser = new InformixParser(); break;
case h2: parser = new H2Parser(); break;
default:
throw new RuntimeException("分页插件" + dialect + "方言错误!");
}
return parser;
}
}
java
public class SqlUtil implements Constant {
public Object processPage(Invocation invocation) throws Throwable {
try {
return _processPage(invocation);
} finally {
clearLocalPage();
}
}
private Object _processPage(Invocation invocation) throws Throwable {
final Object[] args = invocation.getArgs();
Page page = null;
// 支持方法参数时, 先尝试获取Page
if (supportMethodsArguments) {
page = getPage(args);
}
// 分页信息
RowBounds rowBounds = (RowBounds) args[2];
// 支持方法参数时, 如果page == null说明没有分页条件, 不需要分页查询
if ((supportMethodsArguments && page == null)
//当不支持分页参数时,判断LocalPage和RowBounds判断是否需要分页
|| (!supportMethodsArguments && SqlUtil.getLocalPage() == null && rowBounds == RowBounds.DEFAULT)) {
return invocation.proceed();
} else {
// 不支持分页参数
if (!supportMethodsArguments && page == null) {
page = getPage(args);
}
return doProcessPage(invocation, page, args);
}
}
private Page doProcessPage(Invocation invocation, Page page, Object[] args) throws Throwable {
// 保存RowBounds状态
RowBounds rowBounds = (RowBounds) args[2];
// 获取原始ms
MappedStatement ms = (MappedStatement) args[0];
// 判断并处理PageSqlSource
if (!isPageSqlSource(ms)) {
processMappedStatement(ms);
}
// 设置当前parser, 后面每次使用前都会set,ThreadLocal值不会产生不良影响
((PageSqlSource)ms.getSqlSource()).setParser(parser);
try {
// 忽略RowBounds-否则会进行Mybatis自带的内存分页
args[2] = RowBounds.DEFAULT;
// 如果只进行排序 或 pageSizeZero的判断
if (isQueryOnly(page)) {
return doQueryOnly(page, invocation);
}
// 简单通过total值来判断是否进行count查询
if (page.isCount()) {
page.setCountSignal(Boolean.TRUE);
//替换MS
args[0] = msCountMap.get(ms.getId());
//查询总数
Object result = invocation.proceed();
//还原ms
args[0] = ms;
//设置总数
page.setTotal((Integer) ((List) result).get(0));
if (page.getTotal() == 0) {
return page;
}
} else {
page.setTotal(-1l);
}
// pageSize > 0执行分页查询,
// pageSize<=0不执行相当于可能只返回count
if (page.getPageSize() > 0 &&
((rowBounds == RowBounds.DEFAULT && page.getPageNum() > 0)
|| rowBounds != RowBounds.DEFAULT)) {
// 将参数中的MappedStatement替换为新的qs
page.setCountSignal(null);
BoundSql boundSql = ms.getBoundSql(args[1]);
args[1] = parser.setPageParameter(ms, args[1], boundSql, page);
page.setCountSignal(Boolean.FALSE);
// 执行分页查询
Object result = invocation.proceed();
// 得到处理结果
page.addAll((List) result);
}
} finally {
((PageSqlSource)ms.getSqlSource()).removeParser();
}
//返回结果
return page;
}
}
06 应用场景分析
作用 | 实现方式 |
---|---|
水平分表 | 查询或者更新方法添加注解,通过反射获取接口注解进行分表操作 |
数据脱敏 | 拦截执查询方法,数据结果集加密存储,查询解密脱敏返回 |
菜单权限控制 | 拦截查询方法,根据方法权限注解进行拦截处理 |
黑白名单 | 拦截执行器查询和更新方法,进行SQL黑白名单拦截 |
全局唯一ID | 拦截执行器插入方法,通过UUID或者雪花算法生成ID |