Mybatis设置动态表名
- Mybatis设置动态表名
- 1.动态表名插件
- 2.传递表名
- 3.注意事项
Mybatis设置动态表名
1.动态表名插件
MybatisPlus中提供了一个动态表名的插件:https://baomidou.com/pages/2a45ff/#dynamictablenameinnerinterceptor
插件的部分源码如下:
可见表名称动态获取就是依赖于 tableNameHandlerMapping 中的具体的TableNameHandler,这个Map如图:
这个Map的key是旧的表名称,value是TableNameHandler,就是表的名称处理器,用于根据旧名称获取新名称。
TableNameHandler的源码如下:
public interface TableNameHandler {
/**
* 生成动态表名
*
* @param sql 当前执行 SQL
* @param tableName 表名
* @return String
*/
String dynamicTableName(String sql, String tableName);
}
OK,因此我们要做的事情就很简单了,定义DynamicTableNameInnterInterceptor
,向其中添加一个TableNameHandler
,将points_board
这个表名,替换为points_board_赛季id
的名称。
不过,新的问题来了,这个插件中的TableNameHandler该如何获取赛季对应的表名称呢?
计算表名的方式是获取获取上赛季时间,查询数据库中上赛季信息,得到上赛季id。然后拼接得到表名。
当我们批量的写数据到数据库时,如果每次插入都计算一次表名,那性能也太差了。因此,我们肯定是希望一次计算,在TableNameHandler
中可以随时获取。
2.传递表名
假设业务流程如下:
流程中,我们会先计算表名,然后去执行持久化,而动态表名插件就会生效,去替换表名。
因此,一旦我们计算完表名,以某种方式传递给插件中的TableNameHandler,那么就无需重复计算表名了。
不过,问题来了:要知道动态表名称插件,以及TableNameHandler,都是由MybatisPlus内部调用的。我们无法传递参数。那么该如何传递表名称呢?
虽然无法传参,但是从计算表名,到动态表名插件执行,调用TableNameHandler,都是在一个线程内完成的。要在一个线程内实现数据共享,该用什么呢?
大家应该很容易想到,就是ThreadLocal
我们可以在定时任务中计算完动态表名后,将表名存入ThreadLocal,然后在插件中从ThreadLocal中读取即可:
定义ThreadLocal
:
public class TableInfoContext {
private static final ThreadLocal<String> TL = new ThreadLocal<>();
public static void setInfo(String info) {
TL.set(info);
}
public static String getInfo() {
return TL.get();
}
public static void remove() {
TL.remove();
}
}
然后在定义一个配置类,用于定义 DynamicTableNameInnterInterceptor
插件:
import com.baomidou.mybatisplus.extension.plugins.handler.TableNameHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor;
import com.tianji.learning.utils.TableInfoContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class MybatisConfiguration {
@Bean
public DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor() {
// 准备一个Map,用于存储TableNameHandler
Map<String, TableNameHandler> map = new HashMap<>(1);
// 存入一个TableNameHandler,用来替换points_board表名称
// 替换方式,就是从TableInfoContext中读取保存好的动态表名
map.put("points_board", (sql, tableName) -> TableInfoContext.getInfo() == null ? tableName : TableInfoContext.getInfo());
return new DynamicTableNameInnerInterceptor(map);
}
}
插件虽然定义好了,但是该如何继承到MybatisPlus中呢?
实现MybatisPlus
的自动装配,并且定义了MP插件,将DynamicTableNameInnerInterceptor
配置进去。
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnClass({MybatisPlusInterceptor.class, BaseMapper.class})
public class MybatisConfig {
/**
* @see MyBatisAutoFillInterceptor 通过自定义拦截器来实现自动注入creater和updater
* @deprecated 存在任务更新数据导致updater写入0或null的问题,暂时废弃
*/
// @Bean
// @ConditionalOnMissingBean
public BaseMetaObjectHandler baseMetaObjectHandler() {
return new BaseMetaObjectHandler();
}
/**
* 配置 mybatis plus拦截器链
*
* @return
*/
@Bean
@ConditionalOnMissingBean
public MybatisPlusInterceptor mybatisPlusInterceptor
//声明 DynamicTableNameInnerInterceptor 并不是必须的,毕竟不是所有的服务都需要这个动态插件
(@Autowired(required = false) DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor) {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInnerInterceptor.setMaxLimit(200L);
if (dynamicTableNameInnerInterceptor != null){
//说明声明了该拦截器,需要加入到 mybatis plus的拦截器链中
interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor);
}
interceptor.addInnerInterceptor(paginationInnerInterceptor);//分页拦截器插件
interceptor.addInnerInterceptor(new MyBatisAutoFillInterceptor());
return interceptor;
}
}
配置自动装配 META-INF/spring.factories
:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tianji.common.autoconfigure.mybatis.MybatisConfig\
3.注意事项
注意:
由于DynamicTableNameInnerInterceptor并不是每一个微服务都用了,所以这里加入了
@Autowired(required = false)
,避免未定义该拦截器的微服务报错。MybatisPlus的插件定义顺序非常重要,必须按照一定的顺序来定义。参考:https://baomidou.com/pages/2976a3/#innerinterceptor