项目场景:
一个小单体springboot项目,核心业务为计算电力线路损耗,由于一条线路中有几百个元素(终端,表计),每天每一个元素按照规则上报数据,数据内容是一整天每15分钟时间点监测的电流数据,元素太多导致上报的数据也多,这里就需要考虑针对线路进行分表,主要技术是 java+mybatisplus
业务描述
根据线路进行分表,线路表engineering e,终端电流freezing_data f,表计电流meter_data m,效果是
e表插入一条id为123的数据,f表和m表就要新加一个表并且拼接123的后缀=》freezing_data_123,meter_data_123,并且在查询时要自动通过条件查询终端所在的线路并通过线路id找到数据表。
实现思路:
1.首先engineering对象添加时,自动创建两个数据表
这里mapper 可以传表名,tableName就是 表名_id
int createTable(@Param("tableName") String tableName);
<insert id="createTable" parameterType="String">
CREATE TABLE ${tableName}
( ID VARCHAR2(32) NOT NULL ENABLE,
METER_CURRENT VARCHAR2(32),
FREEZING_TIME VARCHAR2(32),
METERBAR_CODE VARCHAR2(128),
PHASE_TYPE VARCHAR2(32),
SAVE_TIME VARCHAR2(32),
CREATE_TIME VARCHAR2(32),
PRIMARY KEY (ID)
)
</insert>
创建表是创建完了,下一步如何在查询时定位到查询表呢
这里我们就需要考虑mybatis的sql执行过程,首先一定是有个拦截器,毕竟一个实体对应一个表
不能无限去添加实体。 这里我们需要找到那个扫描mapper的mp配置类 准备自己添加个拦截器。
再添加拦截器之前肯定有个类需要存 id 以备后续动态拼接
// 这里我们添加一个线程局部变量 防止抢占线路id
public class RequestDataHelper {
/**
* 请求参数存取
*/
private static final ThreadLocal<Map<String, Object>> REQUEST_DATA = new ThreadLocal<>();
/**
* 设置请求参数
*
* @param requestData 请求参数 MAP 对象
*/
public static void setRequestData(Map<String, Object> requestData) {
REQUEST_DATA.set(requestData);
}
/**
* 获取请求参数
*
* @param param 请求参数
* @return 请求参数 MAP 对象
*/
public static <T> T getRequestData(String param) {
Map<String, Object> dataMap = getRequestData();
if (CollectionUtils.isNotEmpty(dataMap)) {
return (T) dataMap.get(param);
}
return null;
}
/**
* 获取请求参数
*
* @return 请求参数 MAP 对象
*/
public static Map<String, Object> getRequestData() {
return REQUEST_DATA.get();
}
}
这里轮到拦截器部分,拦截器部分就是从线程局部变量中取出线路的id
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 新建表名拦截器
DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();
dynamicTableNameInnerInterceptor.setTableNameHandler((sql, tableName) -> {
// 获取参数方法
Map<String, Object> paramMap = RequestDataHelper.getRequestData();
if (CollectionUtils.isNotEmpty(paramMap)&&(tableName.equals("meter_data")||tableName.equals("freezing_data"))) {
// 获取传递的参数
String userId = (String) paramMap.get("line");
// ID 决定表名后缀
String tableNameSuffix = "_" +userId;
// 组装动态表名
return tableName + tableNameSuffix;
}
return tableName;
});
interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor);
xxx
其他拦截器
xxx
return interceptor;
}
拦截器写完我们怎么用呢
我们只需要先查询出来id 然后存到这个map中然后存到线程变量中就可以了
RequestDataHelper.setRequestData(new HashMap<String, Object>() {{
put("line",engineering.getId();
}});
freezingDataService.list();
这样list在调用时会走拦截器,拦截器会返回动态表名给这个表名处理器,最终实现分表动态查询
已知缺陷:
这样动态分表的数据查询必须一条线路一条线路的查询,多条线路需要java拼接等方式。
写完发现 我所用的框架是jeecg,jeecg他本身就写了一个动态表名的拦截器,可以研究一下怎么用,思路大概都差不多