在使用 MyBatis-Plus 时,采用动态表名策略后,selectPage 方法无法正常生效。
MyBatis-Plus动态表名插件配置
以下是我项目中 MyBatis - Plus 的插件配置:
import cn.hutool.core.net.NetUtil;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.handlers.PostInitTableInfoHandler;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.dromara.common.core.factory.YmlPropertySourceFactory;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.mybatis.aspect.DataPermissionAspect;
import org.dromara.common.mybatis.handler.InjectionMetaObjectHandler;
import org.dromara.common.mybatis.handler.MybatisExceptionHandler;
import org.dromara.common.mybatis.handler.PlusPostInitTableInfoHandler;
import org.dromara.common.mybatis.interceptor.PlusDataPermissionInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import static org.dromara.common.core.utils.ServletUtils.getLanguage;
/**
* mybatis-plus配置类
*/
@EnableTransactionManagement(proxyTargetClass = true)
@MapperScan("${mybatis-plus.mapperPackage}")
@PropertySource(value = "classpath:common-mybatis.yml", factory = YmlPropertySourceFactory.class)
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 数据权限处理
interceptor.addInnerInterceptor(dataPermissionInterceptor());
// 分页插件
interceptor.addInnerInterceptor(paginationInnerInterceptor());
// 乐观锁插件
interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());
// 动态表名插件
interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor());
return interceptor;
}
/**
* 动态表名插件
*/
public DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor() {
DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();
dynamicTableNameInnerInterceptor.setTableNameHandler((sql, tableName) -> switch (tableName) {
case "device_type","device_model" -> tableName + "_" + getLanguage();
default -> tableName;
});
return dynamicTableNameInnerInterceptor;
}
/**
* 数据权限拦截器
*/
public PlusDataPermissionInterceptor dataPermissionInterceptor() {
return new PlusDataPermissionInterceptor(SpringUtils.getProperty("mybatis-plus.mapperPackage"));
}
/**
* 数据权限切面处理器
*/
@Bean
public DataPermissionAspect dataPermissionAspect() {
return new DataPermissionAspect();
}
/**
* 分页插件,自动识别数据库类型
*/
public PaginationInnerInterceptor paginationInnerInterceptor() {
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
// 分页合理化
paginationInnerInterceptor.setOverflow(true);
return paginationInnerInterceptor;
}
/**
* 乐观锁插件
*/
public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
return new OptimisticLockerInnerInterceptor();
}
/**
* 元对象字段填充控制器
*/
@Bean
public MetaObjectHandler metaObjectHandler() {
return new InjectionMetaObjectHandler();
}
/**
* 使用网卡信息绑定雪花生成器
* 防止集群雪花ID重复
*/
@Bean
public IdentifierGenerator idGenerator() {
return new DefaultIdentifierGenerator(NetUtil.getLocalhost());
}
/**
* 异常处理器
*/
@Bean
public MybatisExceptionHandler mybatisExceptionHandler() {
return new MybatisExceptionHandler();
}
/**
* 初始化表对象处理器
*/
@Bean
public PostInitTableInfoHandler postInitTableInfoHandler() {
return new PlusPostInitTableInfoHandler();
}
}
MyBatis-Plus动态表名失效原因
在运用 com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor
时,其 intercept
方法具备这样一种特性:每进行一次 SQL 查询操作,该方法便会对已加载的全部拦截器进行逐一遍历。在实际应用场景中,尤其是当你需要对 device_type
和 device_model
库开展动态表名查询时,这一机制可能会带来一些影响。
举例来说,若你希望依据语言环境动态拼接表名,当当前语言为 zh_CN
时,就会去查询 device_type_zh_CN
表。经过多次测试后发现,使用 selectById
或者 selectList
方法能够顺利完成查询任务,但使用 selectPage
方法时却会报错。经过深入分析,问题的根源在于拦截器的执行顺序存在不当之处。
实际上,在使用 com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor
执行 SQL 查询时,intercept
方法会对以 List
形式存储的拦截器进行遍历。通常情况下,第一个被调用的拦截器是分页查询拦截器,也就是 com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor
类中的 willDoQuery
方法。在这个 willDoQuery
方法里,会触发一次额外的数据库查询操作,而恰恰是这次额外的查询操作,最终引发了报错问题。
MyBatis-Plus动态表名失效解决办法
通过调整拦截器的顺序,把分页拦截器置于动态表名拦截器之后,便可有效解决当前面临的问题。
import cn.hutool.core.net.NetUtil;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.handlers.PostInitTableInfoHandler;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.dromara.common.core.factory.YmlPropertySourceFactory;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.mybatis.aspect.DataPermissionAspect;
import org.dromara.common.mybatis.handler.InjectionMetaObjectHandler;
import org.dromara.common.mybatis.handler.MybatisExceptionHandler;
import org.dromara.common.mybatis.handler.PlusPostInitTableInfoHandler;
import org.dromara.common.mybatis.interceptor.PlusDataPermissionInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import static org.dromara.common.core.utils.ServletUtils.getLanguage;
/**
* mybatis-plus配置类
*/
@EnableTransactionManagement(proxyTargetClass = true)
@MapperScan("${mybatis-plus.mapperPackage}")
@PropertySource(value = "classpath:common-mybatis.yml", factory = YmlPropertySourceFactory.class)
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 数据权限处理
interceptor.addInnerInterceptor(dataPermissionInterceptor());
// 动态表名插件
interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor());
// 分页插件
interceptor.addInnerInterceptor(paginationInnerInterceptor());
// 乐观锁插件
interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());
return interceptor;
}
/**
* 动态表名插件
*/
public DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor() {
DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();
dynamicTableNameInnerInterceptor.setTableNameHandler((sql, tableName) -> switch (tableName) {
case "device_type","device_model" -> tableName + "_" + getLanguage();
default -> tableName;
});
return dynamicTableNameInnerInterceptor;
}
/**
* 数据权限拦截器
*/
public PlusDataPermissionInterceptor dataPermissionInterceptor() {
return new PlusDataPermissionInterceptor(SpringUtils.getProperty("mybatis-plus.mapperPackage"));
}
/**
* 数据权限切面处理器
*/
@Bean
public DataPermissionAspect dataPermissionAspect() {
return new DataPermissionAspect();
}
/**
* 分页插件,自动识别数据库类型
*/
public PaginationInnerInterceptor paginationInnerInterceptor() {
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
// 分页合理化
paginationInnerInterceptor.setOverflow(true);
return paginationInnerInterceptor;
}
/**
* 乐观锁插件
*/
public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
return new OptimisticLockerInnerInterceptor();
}
/**
* 元对象字段填充控制器
*/
@Bean
public MetaObjectHandler metaObjectHandler() {
return new InjectionMetaObjectHandler();
}
/**
* 使用网卡信息绑定雪花生成器
* 防止集群雪花ID重复
*/
@Bean
public IdentifierGenerator idGenerator() {
return new DefaultIdentifierGenerator(NetUtil.getLocalhost());
}
/**
* 异常处理器
*/
@Bean
public MybatisExceptionHandler mybatisExceptionHandler() {
return new MybatisExceptionHandler();
}
/**
* 初始化表对象处理器
*/
@Bean
public PostInitTableInfoHandler postInitTableInfoHandler() {
return new PlusPostInitTableInfoHandler();
}
}