springboot 按月分表的实现方式
作者:终成一个大象 发布时间:2023-11-25 00:03:47
一、项目背景
在实际工作中,会遇到业务比较集中的情况,随着时间推延,这部分业务关联的mysql表就会越来越大,十分臃肿。尽管在项目架构上做了读写分离,也会导致查询的时候出现比较慢的情况,导致线上慢查询的出现。
这种情况下导致的慢查询,单纯从sql优化的角度是无法解决的,此时我们就会用到分库分表。由于我们目前的问题是部分mysql表比较大,采用分表的方式即可解决,本文主要讨论分表的情况。
1、分表的方式
垂直分表
简单理解:把同一个表中的数据按列拆分
到不同的表中。
所谓的垂直分表指的是将表结构按照功能模块、关系密切程度划分出来,部署到不同的库或者不同的表中。
水平分表
简单理解:把同一个表中的数据按行拆分
到不同的表中。
所谓的水平分表,即将数据按照某种规则存储到不同的表中。例如日志表,可以使用按月或者按天分表,即每个月的日志数据单独存储在一张表中。这些表同时属于一张主表,拥有相同的表结构,但查询时可以大大减轻主表查询的负担。
二、代码实现
主要使用mybatis-plus提供的功能来实现功能。
1、pom文件依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.1</version>
</dependency>
2、配置文件
# mybatis-plus 配置
mybatis-plus.configuration.call-setters-on-nulls=true
# xml 文件路径
mybatis-plus.mapper-locations=classpath*:mapping/*.xml
# entity 文件路径
mybatis-plus.type-aliases-package=com.geniuworks.bot.entity
# 打印sql语句执行日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 需要按月分表的表名
mp.tableNames=message
3、MybatisPlusConfig实现
MybatisPlusConfig配置类实现:
package com.geniuworks.bot.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.parsers.DynamicTableNameParser;
import com.baomidou.mybatisplus.extension.parsers.ITableNameHandler;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.geniuworks.bot.entity.Tables;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
/**
* @Author dingws
* @PackageName
* @Package
* @Date 2022/1/5 1:53 下午
* @Version 1.0
*/
@Configuration
@Slf4j
public class MybatisPlusConfig {
@Autowired
private Tables tableNames;
/**
*
* @return
*/
@Bean
public PaginationInterceptor paginationInterceptor(){
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
DynamicTableNameParser dynamicTableNameParser = new DynamicTableNameParser();
dynamicTableNameParser.setTableNameHandlerMap(new HashMap<String, ITableNameHandler>(2){{
//涉及表集合
List<String> tables = tableNames.getTableNames();
//动态表规则 初始表名+_+code
tables.forEach(tableTitle -> put(tableTitle,(metaObject, sql, tableName) -> tableName + String.valueOf(getParamValue("month",metaObject))));
}});
paginationInterceptor.setSqlParserList(Collections.singletonList(dynamicTableNameParser));
return paginationInterceptor;
}
/**
*
* @param title
* @param metaObject
* @return
*/
private Object getParamValue(String title, MetaObject metaObject){
//获取参数
Object originalObject = metaObject.getOriginalObject();
JSONObject originalObjectJSON = JSON.parseObject(JSON.toJSONString(originalObject));
JSONObject boundSql = originalObjectJSON.getJSONObject("boundSql");
JSONObject parameterObject = boundSql.getJSONObject("parameterObject");
SimpleDateFormat formatter = new SimpleDateFormat("yyyy_MM");
if(parameterObject.get(title) == null){
return "";
}
Date date = parameterObject.getObject(title, Date.class);
log.info("param value = " + formatter.format(date));
return "_" + formatter.format(date);
}
}
Tables类实现:
package com.geniuworks.bot.entity;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.List;
/**
* @Author dingws
* @PackageName
* @Package
* @Date 2022/1/5 2:18 下午
* @Version 1.0
*/
@Configuration
@ConfigurationProperties("mp")
@Data
public class Tables {
private List<String> tableNames;
}
4、优雅的使用
在使用的时候,只需要在mysql表对应的entity里添加一个字段month即可。
如果month不为空就会按照month的日期所在的月份对数据库表明进行动态拼接。如果month为空则不进行拼接,直接访问总表。
entity类实现:
package com.geniuworks.bot.entity;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Message {
private String id;
private String sessionId;
private Date createdTime;
private String content;
// 根据该字段所在的月分,区分访问的表名
private Date month;
}
mapper类实现:
package com.geniuworks.bot.mapper;
import com.geniuworks.bot.entity.Message;
import com.geniuworks.bot.vo.MessageVo;
import com.geniuworks.bot.vo.StatisticsVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Mapper
public interface MessageMapper {
/**
* insert record to table
* @param record the record
* @return insert count
*/
int insert(Message record);
/**
* insert record to table selective
* @param record the record
* @return insert count
*/
int insertSelective(Message record);
/**
* update record selective
* @param record the updated record
* @return update count
*/
int updateByPrimaryKeySelective(Message record);
/**
* update record
* @param record the updated record
* @return update count
*/
int updateByPrimaryKey(Message record);
5、mysql表名拆分
需要手动把当年需要的数据库手动创建出来,命名规则对应MybatisPlusConfig类中的拼接规则。
三、遇到的问题
由于我一直用的是mybatis组件,需要升级为mybatis-plus,在升级的过程中出现如下的问题。
1、Invalid bound statement (not found)
问题原因: pom文件依赖的是mybatis-plus,配置文件中使用的是mybatis的配置,导致mybatis加载失败。
解决方法:把配置文件的mybatis配置改为mybatis-plus配置
2、resultType=“java.util.Map”,返回字段名被包装
问题原因: 在未升级成mybatis-plus之前,可以直接放回数据库中的字段命名。 升级之后,mybatis-plus将放回字段自动映射为entity中的字段命名。
解决方案: 梳理受到影响的代码逻辑,更新使用的字段命名。
来源:https://blog.csdn.net/Martin_chen2/article/details/122491174


猜你喜欢
- 1. 需要事先将jar包 放在kettle 的 libext 目录,kettle 在启动时会自动加载libext 目录下的所有 jar 包。
- 前言AQS 绝对是JUC的重要基石,也是面试中经常被问到的,所以我们要搞清楚这个AQS到底是什么?骑工作原理是什么?AQS是什么?AQS,A
- 前言最近在用 MVP + RxJava + Retrofit 写项目,觉得相对于其他的开发框架,这的确是给我们带来了很多方便,但是在网上搜寻
- 1、简介应客户要求为了是特殊定制的系统更具安全,系统ROM需要使用自己定义的签名,还有一些特殊的场景也会更改系统的签名比如在过cts认证测试
- C# 5.0 给我们带来了三个非常有用的编译器特性CallerMemberNameCallerFilePathCallerLineNumbe
- Chart控件可以用来绘制波形图、柱状图、饼图、折线图等,用来进行数据表现是很不错的简单说一下这个控件的使用方法效果图我们首先要加载Char
- import java.io.*;import java.text.SimpleDateFormat;import java.util.*;
- 1.创建阻塞的服务器当 ServerSocketChannel 与 SockelChannel 采用默认的阻塞模式时,为了同时处理多个客户的
- android中常常要用到ListView,有时也要用到ExpandableListView,如在手机设置中,对于分类有很好的效果,会用Li
- 首先我们看一下hibernate的主配置文件<!DOCTYPE hibernate-configuration PUBLIC &nbs
- 问题使用Runtime调用python脚本一直没有结果,经排查是因为 cv2 的 import 问题java代码:python代码:在导入c
- 第一种方法:一、测试如下,直接设置小圆点不是图标二、准备工作1.在drawable创建dot.xml,设置小圆点,比较方便<?xml
- 若代理类在程序运行前就已经存在,那么这种代理方式被成为 静态代理 ,这种情况下的代理类通常都是我们在Java代码中定义的。 通常情况下, 静
- 在前面的内容已经学会了如何定义变量和初始化变量。定义变量的目的就是为了操作数据。Java 语言中给我们提供了专门用来操作这些数据的代码符号,
- 一、相关介绍Gradle是一个好用的构建工具 ,使用它的原因是:配置相关依赖代码量少,不会像maven一样xml过多 打包编译测试
- 题目要求java实现字符串中的字母排序并输出排序后的结果分析1、创建一个字符串,赋值并将字符逐个存进数组中。String str = &qu
- Step 1.依赖bannerGradledependencies{ compile 'com.youth.banner
- datagridview手动添加行数据我在做软件模型界面时,通过功能按钮触发显示的datagridview中,为了方便,需要一些数据,仅写死
- 引言:最近在工作中遇到与某些API对接的post的数据需要将对象的字段首字母小写。解决办法有两种:第一种:使用对象的字段属性设置JsonPr
- 前置知识Kotlin协程不是什么空中阁楼,Kotlin源代码会被编译成class字节码文件,最终会运行到虚拟机中。所以从本质上讲,Kotli