引言
随着互联网应用的快速发展,数据量呈爆炸式增长。传统的单表设计在面对海量数据时显得力不从心,容易出现性能瓶颈、查询效率低下等问题。为了提高数据库的扩展性和响应速度,分表(Sharding)成为了一种常见的解决方案。
在最近的工作中,遇到了日志过大导致查询过慢的问题(你可能疑问为啥日志要放到mysql中,是因为这个日志只是跨系统之间接口的请求日志)本文主要介绍Mybatis-plus动态表名处理器分表业务的实现。
1. 什么是分表,为什么要分表?
分表是指将一个大型的数据库表拆分成多个较小的表的技术,每个小表存储部分原始表的数据。通过这种方式,可以有效地减少单个表的数据量,从而提升查询效率和系统整体性能。
为什么要分表啊?Mysql是当前互联网系统中使用非常广泛的关系数据库,具有ACID的特性。但是mysql的单表性能会受到表中数据量的限制,主要原因是B+树索引过大导致查询时索引无法全部加载到内存。读取磁盘的次数变多,而磁盘的每次读取对性能都有很大的影响。
这时一个简单可行的方案就是分表(当然土豪也可以堆硬件),将一张数据量庞大的表的数据,拆分到多个表中,这同时也减少了B+树索引的大小,减少磁盘读取次数,提高性能。
2. 分表策略
2.1 水平分表(Horizontal Sharding)
(1)水平分表是根据一定的规则将数据行分散到不同的物理表中。
(2)这种方式保持了表结构的一致性,但数据被分布到了不同的物理位置。
(3)适用于数据量大、写操作频繁的场景。
2.2 垂直分表(Vertical Sharding)
(1)垂直分表是按照列来分割表,将不同字段分配到不同的表中。
(2)主要目的是分离热点数据和非热点数据,或减少单表的宽度。
(3)适用于某些字段访问频率远高于其他字段的情况。
3. 常见的分表技术
3.1 MyCat (原名:MySQL Proxy)
MyCat 是一个开源的分布式数据库系统,它兼容 MySQL 协议,支持 SQL 解析、SQL 路由等功能,能够帮助开发者轻松实现读写分离、分库分表等复杂功能。对于使用 MySQL 数据库的 Java 应用来说,MyCat 提供了一套完整的分表解决方案。
3.2 Apache ShardingSphere
Apache ShardingSphere 是一套开源的分布式数据库中间件,提供了透明化分片、分布式事务、数据加密等功能。它不仅支持 MySQL 和 PostgreSQL 等多种数据库,还为应用程序屏蔽了底层复杂的分片逻辑,使得开发人员可以专注于业务逻辑的实现。
3.3 TDDL (Taobao Distributed Database Layer)
TDDL 是阿里巴巴内部使用的分布式数据库服务层,主要用于解决高并发下的数据库连接池问题和分库分表。它提供了一个简单的 API 接口,允许应用程序以一种非常自然的方式进行分片配置和管理。
3.4 分布式对象关系映射工具(如 Hibernate Shards)
一些 ORM 框架也提供了对分表的支持,例如 Hibernate 的 Shards 扩展。这些工具可以在一定程度上简化分表逻辑的编码工作,使开发者能够更加关注于业务模型的设计而非底层的数据管理细节。
4、使用Mybatis-plus的TableNameHandler动态表名处理器实现分表业务
4.1 优劣
(1)优势
简单易用 :对于已采用 MyBatis 或 MyBatis-Plus 的项目来说,使用
TableNameHandler
实现分表逻辑非常直接,不需要引入额外的中间件或大幅修改现有代码架构,降低了迁移成本和技术复杂度。轻量级集成:由于是基于 MyBatis-Plus 进行扩展,因此在集成方面更为轻便,不需要复杂的配置过程,能快速应用于开发环境。
灵活性高 :通过自定义
TableNameHandler
,可以根据具体的业务需求灵活地设置分表策略,比如按时间、用户ID或其他业务维度进行分表。
(2)劣势
功能局限性 :与专门设计用于分布式数据库管理的解决方案(如 Apache ShardingSphere 或 MyCat)相比,MyBatis-Plus 的
TableNameHandler
在处理复杂分库分表场景时显得力不从心。例如,在需要跨多个数据库实例进行数据操作时,TableNameHandler
可能无法提供足够的支持。缺乏高级特性:MyBatis-Plus 主要关注于简化数据库访问层的操作,对于一些高级特性如分布式事务、全局唯一ID生成等支持有限,这些往往是大规模分布式系统中不可或缺的部分。
性能优化空间有限:虽然 MyBatis-Plus 提供了多种性能优化措施,但在面对超大规模数据集或极高并发请求时,可能还需要依赖更专业的数据库中间件来进行深层次的性能调优。
维护成本 :当分表规则变得越来越复杂时,使用
TableNameHandler
可能会导致维护难度增加,尤其是在需要频繁调整分表策略的情况下。
总结而言,如果项目的需求相对简单,主要集中在单个数据库实例内,并且团队对 MyBatis-Plus 已经有一定的熟悉度,那么利用 TableNameHandler
实现分表是一个高效的选择。然而,对于那些需要跨数据库实例、具备复杂查询要求或需要更多高级数据库管理特性的应用场景,选择像 Apache ShardingSphere 或 MyCat 这样的专业工具可能是更好的解决方案。
4.2 详细实现介绍
描述:
(1)这里以跨系统交互日志记录表为例,实现分表业务
(2)当日志表数据越来越大,查询效率越来越低,我们除了添加索引之外,也可以分表操作,提升查询性能
ps:其实也可以将之前的分页查询,改为列表查询,只会进行上一页下一页操作。因为分页查询会计算总数,当数据量过大时也会导致查询过慢
4.2.1 表信息
sql
CREATE TABLE `ihm_system_interaction_log` (
`id` bigint(20) NOT NULL COMMENT '主键',
`path` varchar(255) NOT NULL COMMENT '路径',
`request_part` tinyint(4) DEFAULT NULL COMMENT '请求方:1-患者端 2-医生端 3-his 4-京通(微信)支付 5-首信(医保)支付',
`response_part` tinyint(4) DEFAULT NULL COMMENT '响应方:1-患者端 2-医生端 3-his 4-京通(微信)支付 5-首信(医保)支付 6-114公众号支付 7-支付宝支付',
`recording_part` tinyint(4) DEFAULT NULL COMMENT '记录方:1-患者端 2-医生端\n同一次请求,请求方和响应方都为患者端或医生端时,需要分别记录,如果请求方和响应方为医生端和第三方his、首信等时,仅记录医生端日志',
`request_time` datetime DEFAULT NULL COMMENT '请求时间',
`response_time` datetime DEFAULT NULL COMMENT '响应时间',
`day` varchar(50) DEFAULT NULL COMMENT '日期:根据请求时间生成,方便检查、统计',
`month` int(11) DEFAULT NULL COMMENT '月份',
`loss_time` int(11) DEFAULT NULL COMMENT '耗时',
`result_code` int(11) DEFAULT NULL COMMENT '响应结果 0-成功 -1 失败',
`err_info` longtext COMMENT '错误堆栈:限制长度,超长的截取',
`request_param` varchar(64) DEFAULT NULL COMMENT '请求参数',
`request_body` longtext COMMENT '请求体:可能是json、可能是xml',
`response_body` longtext COMMENT '响应体:可能是json、可能是xml',
`message_id` varchar(50) DEFAULT NULL COMMENT '消息id:一个流程的接口日志,应该是同一个message_id',
`order_seq` varchar(50) DEFAULT NULL COMMENT '订单编号:涉及订单接口、应该要有对应信息',
`user_id_type` tinyint(4) DEFAULT NULL COMMENT '证件类型',
`user_id_no` varchar(50) DEFAULT NULL COMMENT '用户证件号码',
`user_id` bigint(20) DEFAULT NULL COMMENT '用户id',
`patient_id_type` tinyint(4) DEFAULT NULL COMMENT '证件类型',
`patient_id_no` varchar(50) DEFAULT NULL COMMENT '用户证件号码',
`patient_id` bigint(20) DEFAULT NULL COMMENT '用户id',
`trace_id` varchar(255) DEFAULT NULL COMMENT '日志traceId,接口流程的trace_id根据不同方,存不同的trace_id,方便在kibana查看',
`hospital_id` bigint(20) DEFAULT NULL COMMENT '医院id',
`org_code` varchar(50) DEFAULT NULL COMMENT '机构编码',
`deleted` int(11) DEFAULT NULL,
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`create_by` varchar(20) DEFAULT NULL COMMENT '创建人',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`update_by` varchar(20) DEFAULT NULL COMMENT '更新人',
`version` int(11) DEFAULT NULL COMMENT '版本',
PRIMARY KEY (`id`),
KEY `order_seq` (`order_seq`),
KEY `patient_id_no` (`patient_id_no`),
KEY `user_id_no` (`user_id_no`),
KEY `ihm_system_interaction_log_create_time_index` (`create_time`),
KEY `ihm_system_interaction_log_hospital_id_index` (`hospital_id`),
KEY `org_code` (`org_code`),
KEY `trace_id` (`trace_id`),
KEY `message_id` (`message_id`),
KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='跨系统交互日志记录表';
4.2.2 表名按月处理器
月份动态表名处理器
java
package com.chinaunicom.medical.ihm.handler;
import com.baomidou.mybatisplus.extension.plugins.handler.TableNameHandler;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
import java.util.List;
@Slf4j
public class MonthTableNameHandler implements TableNameHandler {
//用于记录哪些表可以使用该月份动态表名处理器(即哪些表按月分表)
private List<String> tableNames;
//构造函数,构造动态表名处理器的时候,传递tableNames参数
public MonthTableNameHandler(String ...tableNames) {
this.tableNames = Arrays.asList(tableNames);
}
//每个请求线程维护一个month数据,避免多线程数据冲突。所以使用ThreadLocal
private static final ThreadLocal<String> MONTH_DATA = new ThreadLocal<>();
//设置请求线程的month数据
public static void setData(String month) {
MONTH_DATA.set(month);
}
//删除当前请求线程的month数据
public static void removeData() {
MONTH_DATA.remove();
}
//动态表名接口实现方法
@Override
public String dynamicTableName(String sql, String tableName) {
if (this.tableNames.contains(tableName)){
//表名增加月份后缀
return tableName + "_" + MONTH_DATA.get();
}else{
//表名原样返回
return tableName;
}
}
}
4.2.3 MybatisPlusConfig配置
这里只添加动态表名处理器 (DynamicTableNameInnerInterceptor
)即可。
动态表名处理器 (
DynamicTableNameInnerInterceptor
):通过实现动态表名处理逻辑,使得某些表在执行 SQL 操作时能够根据特定规则(如时间)选择不同的物理表。乐观锁插件 (
OptimisticLockerInnerInterceptor
):提供乐观锁机制,防止并发修改数据时出现的数据覆盖问题。分页插件 (
PaginationInnerInterceptor
):支持数据库查询结果的分页操作,便于处理大量数据。
java
package com.chinaunicom.medical.ihm.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
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 com.chinaunicom.medical.ihm.core.model.DateMetaObjectHandler;
import com.chinaunicom.medical.ihm.handler.MonthTableNameHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
public class PortalMybatisPlusConfig {
@Bean
@Primary
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();
dynamicTableNameInnerInterceptor.setTableNameHandler(
//可以传多个表名参数,指定哪些表使用MonthTableNameHandler处理表名称
new MonthTableNameHandler("ihm_system_interaction_log")
);
//以拦截器的方式处理表名称
interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor);
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
4.2.4 加载Mybatis-Plus配置
这里只需要添加@AutoConfigureBefore(value = PortalMybatisPlusConfig.class)即可。如果你还有别的通用mybatis-plus配置,可以使用@ComponentScan中的excludeFilters排除。避免配置冲突。
主要注解及功能
@SpringBootApplication:
- 这是一个组合注解,包含了
@Configuration
,@EnableAutoConfiguration
和@ComponentScan
。它简化了 Spring Boot 应用程序的基本设置和自动配置。@EnableScheduling:
- 开启对计划任务的支持,允许使用
@Scheduled
注解来定义定时执行的任务。@ComponentScan:
- 指定了组件扫描的基础包为
com.chinaunicom.medical
,同时通过excludeFilters
排除了MybatisPlusConfig
类的扫描。这意味着在组件扫描过程中不会加载或处理MybatisPlusConfig
这个类。@AutoConfigureBefore(value = PortalMybatisPlusConfig.class):
- 表明当前的自动配置类将在
PortalMybatisPlusConfig
之前被加载和配置。这种配置顺序控制对于确保某些特定的 Bean 在其他 Bean 之前初始化是必要的。核心类和方法
PortalApplication 类:
- 包含了应用程序的入口方法
main
,通过调用SpringApplication.run(PortalApplication.class, args)
来启动 Spring Boot 应用程序。PortalMybatisPlusConfig:
- 提供了 MyBatis-Plus 相关的配置,如动态表名处理器、乐观锁插件和分页插件等。
java
@ComponentScan(value = {"com.chinaunicom.medical"}, excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {com.chinaunicom.medical.ihm.core.web.MybatisPlusConfig.class})})
@SpringBootApplication
@EnableScheduling
@AutoConfigureBefore(value = PortalMybatisPlusConfig.class)
public class PortalApplication {
public static void main(String[] args) {
SpringApplication.run(PortalApplication.class, args);
}
}
4.2.5 定时按月分表
定时创建下月表,已存在不会创建
启动时创建当月与下月表
java
package com.chinaunicom.medical.ihm.securityaudit.repository.schedule;
import com.chinaunicom.medical.ihm.securityaudit.repository.SystemInteractionLogService;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Service
public class IhmSystemInteractionLogSchedule {
private static final Logger logger = LoggerFactory.getLogger(IhmSystemInteractionLogSchedule.class);
@Resource
private SystemInteractionLogService systemInteractionLogService;
@PostConstruct
public void executeOnStartup() {
// 启动时执行的任务逻辑,启动后执行一次,创建当月与下月表
createIhmSystemInteractionLogNowTable();
createIhmSystemInteractionLogTable();
}
/**
* 启动后,创建一次当月表,已存在不会创建
*/
public void createIhmSystemInteractionLogNowTable() {
String tableName = "`ihm_system_interaction_log_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMM")) + "`";
logger.info("----开始创建当月表:{}", tableName);
systemInteractionLogService.createIhmSystemInteractionLogTable(tableName);
logger.info("----创建当月表结束:{}", tableName);
}
/**
* 每天尝试创建下个月跨系统交互日志表,已存在的不会创建
*/
@Scheduled(cron = "0 0 4 * * ?")
public void createIhmSystemInteractionLogTable() {
String tableName = "`ihm_system_interaction_log_" + LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ofPattern("yyyyMM")) + "`";
logger.info("----开始创建下月表:{}", tableName);
systemInteractionLogService.createIhmSystemInteractionLogTable(tableName);
logger.info("----创建下月表结束:{}", tableName);
}
}
java
/**
* @author Administrator
* @description 针对表【ihm_system_interaction_log(跨系统交互日志记录表)】的数据库操作Service实现
* @createDate 2024-07-12 20:20:18
*/
@Service
@Slf4j
public class SystemInteractionLogService extends ServiceImpl<SystemInteractionLogMapper, SystemInteractionLog>{
@Resource
private SystemInteractionLogMapper systemInteractionLogMapper;
/**
* 创建跨系统交互日志表
* @param
* @return
*/
public void createIhmSystemInteractionLogTable(String tableName) {
systemInteractionLogMapper.createSystemInteractionLogTable(tableName);
}
}
java
package com.chinaunicom.medical.ihm.securityaudit.repository.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.chinaunicom.medical.ihm.securityaudit.repository.model.SystemInteractionLog;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Update;
/**
* @author Administrator
* @description 针对表【ihm_system_interaction_log(跨系统交互日志记录表)】的数据库操作Mapper
* @createDate 2024-07-12 20:20:18
* @Entity com.chinaunicom.medical.ihm.models.po.SystemInteractionLog
*/
@Mapper
public interface SystemInteractionLogMapper extends BaseMapper<SystemInteractionLog> {
/**
*
* @param tableName
*/
@Update("CREATE TABLE if not EXISTS ${tableName} \n" +
" (`id` bigint(20) NOT NULL COMMENT '主键',\n" +
" `path` varchar(255) NOT NULL COMMENT '路径',\n" +
" `request_part` tinyint(4) DEFAULT NULL COMMENT '请求方:1-患者端 2-医生端 3-his 4-京通(微信)支付 5-首信(医保)支付',\n" +
" `response_part` tinyint(4) DEFAULT NULL COMMENT '响应方:1-患者端 2-医生端 3-his 4-京通(微信)支付 5-首信(医保)支付 6-114公众号支付 7-支付宝支付',\n" +
" `recording_part` tinyint(4) DEFAULT NULL COMMENT '记录方:1-患者端 2-医生端\\n同一次请求,请求方和响应方都为患者端或医生端时,需要分别记录,如果请求方和响应方为医生端和第三方his、首信等时,仅记录医生端日志',\n" +
" `request_time` datetime DEFAULT NULL COMMENT '请求时间',\n" +
" `response_time` datetime DEFAULT NULL COMMENT '响应时间',\n" +
" `day` varchar(50) DEFAULT NULL COMMENT '日期:根据请求时间生成,方便检查、统计',\n" +
" `month` int(11) DEFAULT NULL COMMENT '月份',\n" +
" `loss_time` int(11) DEFAULT NULL COMMENT '耗时',\n" +
" `result_code` int(11) DEFAULT NULL COMMENT '响应结果 0-成功 -1 失败',\n" +
" `err_info` longtext COMMENT '错误堆栈:限制长度,超长的截取',\n" +
" `request_param` varchar(64) DEFAULT NULL COMMENT '请求参数',\n" +
" `request_body` longtext COMMENT '请求体:可能是json、可能是xml',\n" +
" `response_body` longtext COMMENT '响应体:可能是json、可能是xml',\n" +
" `message_id` varchar(50) DEFAULT NULL COMMENT '消息id:一个流程的接口日志,应该是同一个message_id',\n" +
" `order_seq` varchar(50) DEFAULT NULL COMMENT '订单编号:涉及订单接口、应该要有对应信息',\n" +
" `user_id_type` tinyint(4) DEFAULT NULL COMMENT '证件类型',\n" +
" `user_id_no` varchar(50) DEFAULT NULL COMMENT '用户证件号码',\n" +
" `user_id` bigint(20) DEFAULT NULL COMMENT '用户id',\n" +
" `patient_id_type` tinyint(4) DEFAULT NULL COMMENT '证件类型',\n" +
" `patient_id_no` varchar(50) DEFAULT NULL COMMENT '用户证件号码',\n" +
" `patient_id` bigint(20) DEFAULT NULL COMMENT '用户id',\n" +
" `trace_id` varchar(255) DEFAULT NULL COMMENT '日志traceId,接口流程的trace_id根据不同方,存不同的trace_id,方便在kibana查看',\n" +
" `hospital_id` bigint(20) DEFAULT NULL COMMENT '医院id',\n" +
" `org_code` varchar(50) DEFAULT NULL COMMENT '机构编码',\n" +
" `deleted` int(11) DEFAULT NULL,\n" +
" `create_time` datetime DEFAULT NULL COMMENT '创建时间',\n" +
" `create_by` varchar(20) DEFAULT NULL COMMENT '创建人',\n" +
" `update_time` datetime DEFAULT NULL COMMENT '更新时间',\n" +
" `update_by` varchar(20) DEFAULT NULL COMMENT '更新人',\n" +
" `version` int(11) DEFAULT NULL COMMENT '版本',\n" +
" PRIMARY KEY (`id`),\n" +
" KEY `order_seq` (`order_seq`),\n" +
" KEY `patient_id_no` (`patient_id_no`),\n" +
" KEY `user_id_no` (`user_id_no`),\n" +
" KEY `ihm_system_interaction_log_create_time_index` (`create_time`),\n" +
" KEY `ihm_system_interaction_log_hospital_id_index` (`hospital_id`),\n" +
" KEY `org_code` (`org_code`),\n" +
" KEY `trace_id` (`trace_id`),\n" +
" KEY `message_id` (`message_id`),\n" +
" KEY `user_id` (`user_id`)\n" +
") COMMENT='跨系统交互日志记录表';")
void createSystemInteractionLogTable(String tableName);
}
4.2.6 查询功能举例
怎么能查到指定表中的数据呢?其实核心在于MonthTableNameHandler日期的处理,这里指定日期后,会被Mybatis-Plus拦截器处理,拼上指定的后缀,得到正确表名。
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM"); String data = simpleDateFormat.format(new Date()); if (StrUtil.isNotBlank(dto.getRequestTime())) { try { Date dt = simpleDateFormat.parse(dto.getRequestTime()); data = simpleDateFormat.format(dt); } catch (ParseException e) { log.error("时间转换异常",e); } } MonthTableNameHandler.setData(data.replace("-", ""));
java
@Override
public Page<SystemInteractionLogInfoVO> queryPage(LogQueryPageDTO dto) {
Validator.validateNotNull(dto, "传参不能为空");
log.info("传参信息:{}", JSONUtil.toJsonPrettyStr(dto));
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM");
String data = simpleDateFormat.format(new Date());
if (StrUtil.isNotBlank(dto.getRequestTime())) {
try {
Date dt = simpleDateFormat.parse(dto.getRequestTime());
data = simpleDateFormat.format(dt);
} catch (ParseException e) {
log.error("时间转换异常",e);
}
}
MonthTableNameHandler.setData(data.replace("-", ""));
//1、构建分页条件
Page<SystemInteractionLog> page = new Page<>(dto.getCurrent(), dto.getSize());
//2、分页查询
Page<SystemInteractionLog> logPage = lambdaQuery()
.eq(ObjectUtil.isNotEmpty(dto.getHospitalId()), SystemInteractionLog::getHospitalId, dto.getHospitalId())
.eq(ObjectUtil.isNotEmpty(dto.getOrgCode()), SystemInteractionLog::getOrgCode, dto.getOrgCode())
.eq(ObjectUtil.isNotEmpty(dto.getOrderSeq()), SystemInteractionLog::getOrderSeq, dto.getOrderSeq())
.eq(ObjectUtil.isNotEmpty(dto.getUserId()), SystemInteractionLog::getUserId, dto.getUserId())
.eq(StrUtil.isNotBlank(dto.getTraceId()), SystemInteractionLog::getTraceId,dto.getTraceId())
.eq(StrUtil.isNotBlank(dto.getMessageId()), SystemInteractionLog::getMessageId,dto.getMessageId())
.eq(ObjectUtil.isNotEmpty(dto.getRequestPart()), SystemInteractionLog::getRequestPart, dto.getRequestPart())
.eq(ObjectUtil.isNotEmpty(dto.getResponsePart()), SystemInteractionLog::getResponsePart, dto.getResponsePart())
.eq(ObjectUtil.isNotEmpty(dto.getRecordingPart()), SystemInteractionLog::getRecordingPart, dto.getRecordingPart())
.eq(ObjectUtil.isNotEmpty(dto.getResultCode()), SystemInteractionLog::getResultCode, dto.getResultCode())
.like(StrUtil.isNotBlank(dto.getPath()), SystemInteractionLog::getPath, dto.getPath())
.ge(ObjectUtil.isNotEmpty(dto.getRequestTime()), SystemInteractionLog::getRequestTime, dto.getRequestTime())
.le(ObjectUtil.isNotEmpty(dto.getResponseTime()), SystemInteractionLog::getResponseTime, dto.getResponseTime())
.orderByDesc(SystemInteractionLog::getCreateTime)
.page(page);
//3、封装返回结果
Page<SystemInteractionLogInfoVO> returnPage = new Page<>();
BeanUtil.copyProperties(logPage,returnPage);
List<SystemInteractionLog> records = logPage.getRecords();
if(CollectionUtil.isNotEmpty(records)){
returnPage.setRecords(BeanUtil.copyToList(records,SystemInteractionLogInfoVO.class));
}
return returnPage;
}
4.2.7 保存功能举例
怎么能保存到指定表中的数据呢?其实核心在于MonthTableNameHandler日期的处理,这里指定日期后,会被Mybatis-Plus拦截器处理,拼上指定的后缀,得到正确表名。
//保存数据到,本月对应的表中
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMM");
String data = simpleDateFormat.format(new Date());
MonthTableNameHandler.setData(data);
java
@Override
public boolean saveLog(SystemInteractionLogDto systemInteractionLogDto) {
SystemInteractionLog systemInteractionLog = BeanUtil.copyProperties(systemInteractionLogDto, SystemInteractionLog.class);
if (StringUtils.isNotBlank(systemInteractionLog.getDay()) && systemInteractionLog.getDay().length() > 10) {
systemInteractionLog.setDay(systemInteractionLog.getDay().substring(0, 10));
}
systemInteractionLog.setMonth(Integer.valueOf(LocalDate.now().format(MONTH_FORMAT)));
systemInteractionLog.setOrgCode(systemInteractionLogDto.getHospCode());
String orderSeq = systemInteractionLog.getOrderSeq();
String inquiryNo = null;
String hospCode = null;
if (StringUtils.isBlank(orderSeq)) {
if (StringUtils.isNotBlank(systemInteractionLog.getRequestParam()) && systemInteractionLog.getRequestParam().contains("orderSeq")) {
String requestParam = systemInteractionLog.getRequestParam();
String[] params = requestParam.split("=", -1);
for (String param : params) {
if (StringUtils.isBlank(orderSeq) && ("orderSeq".equals(param) || "order_seq".equals(param))) {
orderSeq = param.split("=")[1];
}
if (StringUtils.isBlank(inquiryNo) && ("inquiryNo".equals(param) || "inquiry_no".equals(param))) {
inquiryNo = param.split("=")[1];
}
if (StringUtils.isBlank(hospCode) && ("hosp_code".equals(param) || "hospCode".equals(param))) {
hospCode = param.split("=")[1];
}
}
}
}
if (StringUtils.isNotBlank(systemInteractionLog.getRequestBody())) {
String requestBody = systemInteractionLog.getRequestBody();
if (StringUtils.isBlank(orderSeq)) {
orderSeq = getTargetValueByKey(requestBody, "orderSeq");
}
if (StringUtils.isBlank(orderSeq)) {
orderSeq = getTargetValueByKey(requestBody, "order_seq");
}
if (StringUtils.isBlank(inquiryNo)) {
inquiryNo = getTargetValueByKey(requestBody, "inquiryNo");
}
if (StringUtils.isBlank(inquiryNo)) {
inquiryNo = getTargetValueByKey(requestBody, "inquiry_no");
}
if (StringUtils.isBlank(hospCode)) {
hospCode = getTargetValueByKey(requestBody, "hospCode");
}
if (StringUtils.isBlank(hospCode)) {
hospCode = getTargetValueByKey(requestBody, "hosp_code");
}
}
if (StringUtils.isNotBlank(systemInteractionLog.getResponseBody())) {
String responseBody = systemInteractionLog.getResponseBody();
if (StringUtils.isBlank(orderSeq)) {
orderSeq = getTargetValueByKey(responseBody, "orderSeq");
}
if (StringUtils.isBlank(orderSeq)) {
orderSeq = getTargetValueByKey(responseBody, "order_seq");
}
if (StringUtils.isBlank(inquiryNo)) {
inquiryNo = getTargetValueByKey(responseBody, "inquiryNo");
}
if (StringUtils.isBlank(inquiryNo)) {
inquiryNo = getTargetValueByKey(responseBody, "inquiry_no");
}
if (StringUtils.isBlank(hospCode)) {
hospCode = getTargetValueByKey(responseBody, "hospCode");
}
if (StringUtils.isBlank(hospCode)) {
hospCode = getTargetValueByKey(responseBody, "hosp_code");
}
}
if (StringUtils.isBlank(systemInteractionLog.getOrgCode())) {
systemInteractionLog.setOrgCode(hospCode);
}
if (StringUtils.isBlank(orderSeq) && (StringUtils.isNotBlank(systemInteractionLog.getOrgCode()) && StringUtils.isNotBlank(inquiryNo))) {
Result<OrderDetailVO> result = orderApi.queryOrderDetailByInquiryNo(systemInteractionLog.getOrgCode(), inquiryNo);
if (Objects.nonNull(result) && Objects.nonNull(result.getData())) {
OrderDetailVO orderDetailVO = result.getData();
systemInteractionLog.setPatientId(orderDetailVO.getPatientId());
systemInteractionLog.setPatientIdNo(orderDetailVO.getIdCard());
systemInteractionLog.setPatientIdType(orderDetailVO.getIdCardType());
systemInteractionLog.setUserId(orderDetailVO.getUserId());
systemInteractionLog.setHospitalId(orderDetailVO.getHospitalId());
systemInteractionLog.setOrgCode(orderDetailVO.getOrgCode());
systemInteractionLog.setOrderSeq(orderDetailVO.getOrderSeq());
}
}
try {
//补充字段
if (StringUtils.isNotBlank(orderSeq)) {
systemInteractionLog.setOrderSeq(orderSeq);
if (systemInteractionLog.getPath().contains("/reg/occupy")) {
Thread.sleep(3000L);
}
Result<OrderDetailVO> result = orderApi.queryOrderDetailBySeq(orderSeq);
if (Objects.nonNull(result) && Objects.nonNull(result.getData())) {
OrderDetailVO orderDetailVO = result.getData();
systemInteractionLog.setPatientId(orderDetailVO.getPatientId());
systemInteractionLog.setPatientIdNo(orderDetailVO.getIdCard());
systemInteractionLog.setPatientIdType(orderDetailVO.getIdCardType());
systemInteractionLog.setUserId(orderDetailVO.getUserId());
systemInteractionLog.setHospitalId(orderDetailVO.getHospitalId());
systemInteractionLog.setOrgCode(orderDetailVO.getOrgCode());
}
}
} catch (Exception e) {
log.warn("错误参数:{}", e.getMessage());
}
if (StringUtils.isBlank(systemInteractionLog.getOrgCode()) && StringUtils.isNotBlank(systemInteractionLogDto.getTenantId())) {
Result<HospitalVo> result = hospitalApi.queryByTenantId(systemInteractionLogDto.getTenantId());
if (Objects.nonNull(result) && Objects.nonNull(result.getData())) {
systemInteractionLog.setHospitalId(result.getData().getHospitalId());
systemInteractionLog.setOrgCode(result.getData().getOrgCode());
}
} else if (StringUtils.isNotBlank(systemInteractionLog.getOrgCode()) && systemInteractionLog.getOrgCode().length() >= 8) {
Result<HospitalVo> result = hospitalApi.queryByOrgCode(systemInteractionLog.getOrgCode());
if (Objects.nonNull(result) && Objects.nonNull(result.getData())) {
systemInteractionLog.setHospitalId(result.getData().getHospitalId());
systemInteractionLog.setOrgCode(result.getData().getOrgCode());
}
}
//保存数据到,本月对应的表中
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMM");
String data = simpleDateFormat.format(new Date());
MonthTableNameHandler.setData(data);
// 自增id
Long id = redisTemplate.opsForValue().increment("ihm_system_interaction_log");
systemInteractionLog.setId(id);
return save(systemInteractionLog);
}