关于使用Mybatis-plus的TableNameHandler动态表名处理器实现分表业务的详细介绍

引言

        随着互联网应用的快速发展,数据量呈爆炸式增长。传统的单表设计在面对海量数据时显得力不从心,容易出现性能瓶颈、查询效率低下等问题。为了提高数据库的扩展性和响应速度,分表(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排除。避免配置冲突。

主要注解及功能

  1. @SpringBootApplication

    • 这是一个组合注解,包含了 @Configuration@EnableAutoConfiguration 和 @ComponentScan。它简化了 Spring Boot 应用程序的基本设置和自动配置。
  2. @EnableScheduling

    • 开启对计划任务的支持,允许使用 @Scheduled 注解来定义定时执行的任务。
  3. @ComponentScan

    • 指定了组件扫描的基础包为 com.chinaunicom.medical,同时通过 excludeFilters 排除了 MybatisPlusConfig 类的扫描。这意味着在组件扫描过程中不会加载或处理 MybatisPlusConfig 这个类。
  4. @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);}

4.3 分表效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/8880.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【开源免费】基于Vue和SpringBoot的在线文档管理系统(附论文)

本文项目编号 T 038 &#xff0c;文末自助获取源码 \color{red}{T038&#xff0c;文末自助获取源码} T038&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…

智慧园区系统分类及其在提升企业管理效率中的创新应用探讨

内容概要 智慧园区的概念已经逐渐深入人心&#xff0c;成为现代城市发展中不可或缺的一部分。随着信息技术的飞速发展和数字化转型的不断推进&#xff0c;一系列智慧园区管理系统应运而生。这些系统不仅帮助企业提高了管理效率&#xff0c;还在多个方面激发了创新。 首先&…

图片上传实现图片预览的功能

文章目录 图片上传实现图片预览的功能一、引言二、拖拽上传实现预览1、HTML结构与样式2、JavaScript实现拖拽逻辑 三、选择文件上传实现预览1、HTML结构2、JavaScript实现预览逻辑 四、使用示例五、总结 图片上传实现图片预览的功能 一、引言 在现代网页设计中&#xff0c;图片…

电力晶体管(GTR)全控性器件

电力晶体管&#xff08;Giant Transistor&#xff0c;GTR&#xff09;是一种全控性器件&#xff0c;以下是关于它的详细介绍&#xff1a;&#xff08;模电普通晶体管三极管进行对比学习&#xff09; 基本概念 GTR是一种耐高电压、大电流的双极结型晶体管&#xff08;BJT&am…

Linux - 进程间通信(2)

目录 2、进程池 1&#xff09;理解进程池 2&#xff09;进程池的实现 整体框架&#xff1a; a. 加载任务 b. 先描述&#xff0c;再组织 I. 先描述 II. 再组织 c. 创建信道和子进程 d. 通过channel控制子进程 e. 回收管道和子进程 问题1&#xff1a; 解答1&#xff…

【阅读笔记】New Edge Diected Interpolation,NEDI算法,待续

一、概述 由Li等提出的新的边缘指导插值(New Edge—Di-ected Interpolation&#xff0c;NEDI)算法是一种具有良好边缘保持效果的新算法&#xff0c;它利用低分辨率图像与高分辨率图像的局部协方差问的几何对偶性来对高分辨率图像进行自适应插值。 2001年Xin Li和M.T. Orchard…

Windows安装Miniconda和PySide6以及配置PyCharm

目录 1. 选择Miniconda 2. 下载Miniconda 3. 安装Miniconda 4. 在base环境下创建pyside6环境 5. 安装pyside6环境 6. 配置PyCharm环境 7. 运行第一个程序效果 1. 选择Miniconda 选择Miniconda而没有选择Anaconda&#xff0c;是因为它是一个更小的Anaconda发行版&#x…

Linux之内存管理前世今生(一)

一个程序&#xff08;如王者荣耀&#xff09;平常是存储在硬盘上的&#xff0c;运行时才把这个程序载入内存&#xff0c;CPU才能执行。 问题&#xff1a; 这个程序载入内存的哪个位置呢&#xff1f;载入内核所在的空间吗&#xff1f;系统直接挂了。 一、虚拟内存 1.1 内存分…

Java基于SSM框架的互助学习平台小程序【附源码、文档】

博主介绍&#xff1a;✌IT徐师兄、7年大厂程序员经历。全网粉丝15W、csdn博客专家、掘金/华为云//InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3…

【Rust自学】16.3. 共享状态的并发

喜欢的话别忘了点赞、收藏加关注哦&#xff08;加关注即可阅读全文&#xff09;&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 16.3.1. 使用共享来实现并发 还记得Go语言有一句名言是这么说的&#xff1a;Do not commun…

Python 数据分析 - Matplotlib 绘图

Python 数据分析 - Matplotlib 绘图 简介绘图折线图单线多线子图 散点图直方图条形图纵置横置多条 饼图 简介 Matplotlib 是 Python 提供的一个绘图库&#xff0c;通过该库我们可以很容易的绘制出折线图、直方图、散点图、饼图等丰富的统计图&#xff0c;安装使用 pip install…

Java进阶(二):Java设计模式

目录 设计模式 一.建模语言 二.类之间的关系 1.依赖关系 2.关联关系 3.聚合关系 4.组合关系 5.继承关系 6.实现关系 三.面向对象设计原则 单一职责原则 开闭原则 里氏替换原则 依赖倒置 接口隔离原则 迪米特原则 组合/聚合(关联关系)复用原则 四.23种设计模式…

双层Git管理项目,github托管显示正常

双层Git管理项目&#xff0c;github托管显示正常 背景 在写React项目时&#xff0c;使用Next.js,该项目默认由git托管。但是我有在项目代码外层记笔记的习惯&#xff0c;我就在外层使用了git托管。 目录如下 code 层内也有.git 文件&#xff0c;对其托管。 我没太在意&…

群晖docker获取私有化镜像http: server gave HTTP response to HTTPS client].

群晖docker获取私有化镜像提示http: server gave HTTP response to HTTPS clien 问题描述 层级时间用户事件Information2023/07/08 12:47:45cxlogeAdd image from xx.xx.31.240:1923/go-gitea/gitea:1.19.3Error2023/07/08 12:47:48cxlogeFailed to pull image [Get "http…

机器学习:支持向量机

支持向量机&#xff08;Support Vector Machine&#xff09;是一种二类分类模型&#xff0c;其基本模型定义为特征空间上的间隔最大的广义线性分类器&#xff0c;其学习策略便是间隔最大化&#xff0c;最终可转化为一个凸二次规划问题的求解。 假设两类数据可以被 H x : w T x…

相互作用感知的蛋白-小分子对接模型 - Interformer 评测

Interformer 是一个应用于分子对接和亲和力预测的深度学习模型&#xff0c;基于 Graph-Transdormer 架构的模型&#xff0c;利用相互作用&#xff08;氢键、疏水&#xff09;感知的混合密度网络&#xff08;interaction-aware mixture den sity network&#xff0c; MDN&#x…

如果我想设计一款复古风格的壁纸,应该选什么颜色?

设计复古风格的壁纸时&#xff0c;选择合适的颜色是营造怀旧和经典氛围的关键。复古风格通常使用一些温暖、柔和且带有岁月痕迹的色调。以下是一些适合复古风格壁纸的颜色选择和搭配建议&#xff1a; 一、复古风格的主色调 棕色系&#xff1a; 特点&#xff1a;棕色是复古风格的…

AI 浪潮席卷中国年,开启科技新春新纪元

在这博主提前祝大家蛇年快乐呀&#xff01;&#xff01;&#xff01; 随着人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;其影响力已经渗透到社会生活的方方面面。在中国传统节日 —— 春节期间&#xff0c;AI 技术也展现出了巨大的潜力&#xff0c;为中国年带…

WPS数据分析000007

目录 一、分列 智能分列 出生日期 数值转换 公式不运算 二、数据对比 离职员工 新入职员工 都在职的员工 三、合并计算 四、拆分表格 合并表格 一、分列 智能分列 出生日期 数据求和 文本型数字左对齐&#xff1b;数值型数字右对齐 数值转换 方式一&#xff1a; 方…

fps一些内容添加

1 增强输入要点记录 输入 &#xff1a;输入值的类型 布尔 1d&#xff0c;2d&#xff0c;3d 映射&#xff1a;就是确定按键输入键位&#xff0c;输入类型&#xff0c;和一些触发器&#xff08;按键方式&#xff09;修改器&#xff08;对输出值进行修改&#xff09; 基本的&am…