关于使用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 表信息
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排除。避免配置冲突。

主要注解及功能

  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 相关的配置,如动态表名处理器、乐观锁插件和分页插件等。
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);
    }

4.3 分表效果

相关推荐
码农研究僧14 分钟前
Spring Boot 中的事件发布与监听:深入理解 ApplicationEventPublisher(附Demo)
java·spring boot·后端·事件发布与监听
NiNg_1_23416 分钟前
Spring Boot 实现文件上传和下载
java·spring boot·后端·文件上传
gentle_ice1 小时前
leetcode——二叉树的中序遍历(java)
java·数据结构·算法·leetcode
苹果酱05671 小时前
mysql.sock.lock 导致mysql重启失败
java·spring boot·毕业设计·layui·课程设计
吃一口大米饭1 小时前
合并两个有序链表(leetcode刷题)
java·数据结构·算法·leetcode·链表
简 洁 冬冬2 小时前
Java中的Servlet
java·开发语言·servlet
fly spider2 小时前
多线程-线程池的使用
java·面试·线程池·多线程·juc
组合缺一2 小时前
Solon Cloud Gateway 开发:导引
java·gateway·reactor·solon·响应式
陈平安Java and C3 小时前
二叉树介绍
java
S-X-S3 小时前
sunrays-framework配置重构
java·重构