`DynamicTableNameInterceptor` 是 MyBatis Plus 提供的一个拦截器,用于动态修改 SQL 中的表名。它允许你在运行时根据某些条件动态地替换表名,这在多租户、分表场景中非常有用。
一、源码解析
`DynamicTableNameInterceptor` 实现了 MyBatis 的 `Interceptor` 接口,通过实现 `intercept` 方法来拦截 SQL 的执行过程。
1. 主要方法
- `intercept(Invocation invocation)`:这是拦截器的核心方法,它在 SQL 被执行之前调用。在这个方法中,`DynamicTableNameInterceptor` 会对 SQL 进行解析,并根据动态表名规则对 SQL 中的表名进行替换。
- `setTableNameHandlerMap(Map<String, ITableNameHandler> tableNameHandlerMap)`:这个方法用于设置表名处理器的映射。`ITableNameHandler` 是一个接口,用户可以实现该接口,并通过这个映射关系将表名与处理器关联。
- `parseSingleSqlParser(MPBoundSql sqlSource, ITableNameHandler handler)`:这个方法用于解析和处理单条 SQL 中的表名,它会通过传入的 `ITableNameHandler` 来决定新的表名。
2. 代码片段解析
下面是一个简化的源码片段,用于展示 `DynamicTableNameInterceptor` 的主要逻辑:
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
BoundSql boundSql = (BoundSql) invocation.getArgs()[2];
String originalSql = boundSql.getSql();
// 根据拦截器中的逻辑判断是否需要动态修改表名
if (needModifyTableName()) {
// 解析 SQL 语句,并动态替换表名
String newSql = parseSql(originalSql);
// 修改 SQL 语句
MetaObject metaObject = SystemMetaObject.forObject(boundSql);
metaObject.setValue("sql", newSql);
}
return invocation.proceed();
}
二、用法
1. 配置拦截器
首先,你需要在 MyBatis 配置中注册 `DynamicTableNameInterceptor`,并提供一个表名处理器映射:
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
DynamicTableNameInterceptor dynamicTableNameInterceptor = new DynamicTableNameInterceptor();
// 设置表名处理器映射
Map<String, ITableNameHandler> tableNameHandlerMap = new HashMap<>();
tableNameHandlerMap.put("user", (metaObject, sql, tableName) -> {
// 在这里根据业务逻辑返回不同的表名
return tableName + "_" + getTenantId();
});
dynamicTableNameInterceptor.setTableNameHandlerMap(tableNameHandlerMap);
interceptor.addInnerInterceptor(dynamicTableNameInterceptor);
return interceptor;
}
2. 使用自定义表名处理器
你可以通过实现 `ITableNameHandler` 接口来自定义表名的动态生成逻辑:
public class CustomTableNameHandler implements ITableNameHandler {
@Override
public String dynamicTableName(MetaObject metaObject, String sql, String tableName) {
// 根据业务逻辑返回不同的表名
return tableName + "_" + getCurrentUserId();
}
}
然后将这个自定义处理器加入到拦截器中:
tableNameHandlerMap.put("order", new CustomTableNameHandler());
三、与分页插件一起使用时分页失效问题处理
在 MyBatis Plus 中,`DynamicTableNameInterceptor` 与分页插件共同使用时,可能会导致分页失效。这通常是因为拦截器的执行顺序或 SQL 解析和处理的顺序出现了问题。
原因分析
MyBatis Plus 的分页插件和 `DynamicTableNameInterceptor` 都是通过拦截器的方式来修改 SQL 的。如果两者的执行顺序不当,可能会导致分页插件无法正确解析和处理动态表名后的 SQL,进而导致分页失效。
具体原因可能包括:
1. 拦截器顺序问题:如果 `DynamicTableNameInterceptor` 在分页插件之后执行,分页插件可能已经生成了分页的 SQL 语句,`DynamicTableNameInterceptor` 对表名的动态修改可能会影响分页插件生成的 SQL 语句的正确性。
2. SQL 解析问题:如果 `DynamicTableNameInterceptor` 修改了 SQL 语句的表名,分页插件可能无法正确解析修改后的 SQL,从而导致分页查询失败。
解决方案
1. 调整拦截器的顺序
你可以通过调整 `MybatisPlusInterceptor` 中拦截器的顺序来确保 `DynamicTableNameInterceptor` 在分页插件之前执行。可以在分页插件前添加 `DynamicTableNameInterceptor`,确保表名在分页逻辑处理之前已经正确替换。
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加 DynamicTableNameInterceptor
DynamicTableNameInterceptor dynamicTableNameInterceptor = new DynamicTableNameInterceptor();
// 设置动态表名处理器(省略详细配置)
interceptor.addInnerInterceptor(dynamicTableNameInterceptor);
// 添加分页插件
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor();
interceptor.addInnerInterceptor(paginationInterceptor);
return interceptor;
}
在这个配置中,`DynamicTableNameInterceptor` 被优先添加,确保在分页插件执行前就已动态替换表名。
2. 确保 SQL 解析正确
确保 `DynamicTableNameInterceptor` 中的 `ITableNameHandler` 实现不会产生影响 SQL 解析的表名变化。必要时,可以在 SQL 解析时保留分页的结构,比如使用别名等技巧避免分页插件识别错误。
四、总结
1.`DynamicTableNameInterceptor` 通过动态修改 SQL 中的表名,使得在多租户、分表等场景下更加灵活。通过实现 `ITableNameHandler` 接口,你可以自定义表名的生成逻辑,并将其应用于特定的表。注册和配置该拦截器相对简单,在 MyBatis Plus 中使用时也非常方便。
2.当 `DynamicTableNameInterceptor` 与分页插件一起使用时,分页失效的主要原因是拦截器执行顺序不当。通过调整拦截器的顺序,使得 `DynamicTableNameInterceptor` 在分页插件之前执行,可以解决这个问题。如果问题仍然存在,检查 `ITableNameHandler` 的实现,确保其对 SQL 的修改不会影响分页插件的正常工作。