引言
随着互联网应用的快速发展,数据量呈爆炸式增长。传统的单表设计在面对海量数据时显得力不从心,容易出现性能瓶颈、查询效率低下等问题。为了提高数据库的扩展性和响应速度,分表(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 表信息
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 表名按月处理器
月份动态表名处理器
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数据,避免多线程数据冲突。所以使用ThreadLocalprivate 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();}//动态表名接口实现方法@Overridepublic 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
):支持数据库查询结果的分页操作,便于处理大量数据。
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@Primarypublic 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 相关的配置,如动态表名处理器、乐观锁插件和分页插件等。
@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 定时按月分表
定时创建下月表,已存在不会创建
启动时创建当月与下月表
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);@Resourceprivate SystemInteractionLogService systemInteractionLogService;@PostConstructpublic 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);}
}
/*** @author Administrator* @description 针对表【ihm_system_interaction_log(跨系统交互日志记录表)】的数据库操作Service实现* @createDate 2024-07-12 20:20:18*/
@Service
@Slf4j
public class SystemInteractionLogService extends ServiceImpl<SystemInteractionLogMapper, SystemInteractionLog>{@Resourceprivate SystemInteractionLogMapper systemInteractionLogMapper;/*** 创建跨系统交互日志表* @param* @return*/public void createIhmSystemInteractionLogTable(String tableName) {systemInteractionLogMapper.createSystemInteractionLogTable(tableName);}}
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("-", ""));
@Overridepublic 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);
@Overridepublic 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);// 自增idLong id = redisTemplate.opsForValue().increment("ihm_system_interaction_log");systemInteractionLog.setId(id);return save(systemInteractionLog);}