Mybatis-plus 分表

2023-12-15 18:37:11

一、简介

MybatisPlus提供处理动态表名的接口TableNameHandler,可以通过这个接口获取当前执行的SQL和数据库表名

  • TableNameHandler 接口源码
public interface TableNameHandler {

    /**
     * 生成动态表名
     *
     * @param sql       当前执行 SQL
     * @param tableName 表名
     * @return String
     */
    String dynamicTableName(String sql, String tableName);
}

二、分表demo

场景:用户每次登录,都要记录下来,分表数量为10个

  • SQL脚本
CREATE TABLE user_login_record_0 (
       `id` BIGINT ( 20 ) NOT NULL AUTO_INCREMENT COMMENT '主键id',
       `user_id` BIGINT ( 20 ) NOT NULL COMMENT '商品id',
       `login_type` INT ( 11 ) NOT NULL COMMENT '登录方式:0:手机号码登录,1:微信登录,2:一键登录',
       `login_time` datetime ( 3 ) DEFAULT CURRENT_TIMESTAMP ( 3 ) COMMENT '登录时间',
       PRIMARY KEY ( `id` )
) ENGINE = INNODB COMMENT '用户登录记录表_0';

CREATE TABLE user_login_record_1 (
       `id` BIGINT ( 20 ) NOT NULL AUTO_INCREMENT COMMENT '主键id',
       `user_id` BIGINT ( 20 ) NOT NULL COMMENT '商品id',
       `login_type` INT ( 11 ) NOT NULL COMMENT '登录方式:0:手机号码登录,1:微信登录,2:一键登录',
       `login_time` datetime ( 3 ) DEFAULT CURRENT_TIMESTAMP ( 3 ) COMMENT '登录时间',
       PRIMARY KEY ( `id` )
) ENGINE = INNODB COMMENT '用户登录记录表_1';

CREATE TABLE user_login_record_2 (
       `id` BIGINT ( 20 ) NOT NULL AUTO_INCREMENT COMMENT '主键id',
       `user_id` BIGINT ( 20 ) NOT NULL COMMENT '商品id',
       `login_type` INT ( 11 ) NOT NULL COMMENT '登录方式:0:手机号码登录,1:微信登录,2:一键登录',
       `login_time` datetime ( 3 ) DEFAULT CURRENT_TIMESTAMP ( 3 ) COMMENT '登录时间',
       PRIMARY KEY ( `id` )
) ENGINE = INNODB COMMENT '用户登录记录表_2';

CREATE TABLE user_login_record_3 (
       `id` BIGINT ( 20 ) NOT NULL AUTO_INCREMENT COMMENT '主键id',
       `user_id` BIGINT ( 20 ) NOT NULL COMMENT '商品id',
       `login_type` INT ( 11 ) NOT NULL COMMENT '登录方式:0:手机号码登录,1:微信登录,2:一键登录',
       `login_time` datetime ( 3 ) DEFAULT CURRENT_TIMESTAMP ( 3 ) COMMENT '登录时间',
       PRIMARY KEY ( `id` )
) ENGINE = INNODB COMMENT '用户登录记录表_3';

CREATE TABLE user_login_record_4 (
       `id` BIGINT ( 20 ) NOT NULL AUTO_INCREMENT COMMENT '主键id',
       `user_id` BIGINT ( 20 ) NOT NULL COMMENT '商品id',
       `login_type` INT ( 11 ) NOT NULL COMMENT '登录方式:0:手机号码登录,1:微信登录,2:一键登录',
       `login_time` datetime ( 3 ) DEFAULT CURRENT_TIMESTAMP ( 3 ) COMMENT '登录时间',
       PRIMARY KEY ( `id` )
) ENGINE = INNODB COMMENT '用户登录记录表_4';

CREATE TABLE user_login_record_5 (
       `id` BIGINT ( 20 ) NOT NULL AUTO_INCREMENT COMMENT '主键id',
       `user_id` BIGINT ( 20 ) NOT NULL COMMENT '商品id',
       `login_type` INT ( 11 ) NOT NULL COMMENT '登录方式:0:手机号码登录,1:微信登录,2:一键登录',
       `login_time` datetime ( 3 ) DEFAULT CURRENT_TIMESTAMP ( 3 ) COMMENT '登录时间',
       PRIMARY KEY ( `id` )
) ENGINE = INNODB COMMENT '用户登录记录表_5';

CREATE TABLE user_login_record_6 (
       `id` BIGINT ( 20 ) NOT NULL AUTO_INCREMENT COMMENT '主键id',
       `user_id` BIGINT ( 20 ) NOT NULL COMMENT '商品id',
       `login_type` INT ( 11 ) NOT NULL COMMENT '登录方式:0:手机号码登录,1:微信登录,2:一键登录',
       `login_time` datetime ( 3 ) DEFAULT CURRENT_TIMESTAMP ( 3 ) COMMENT '登录时间',
       PRIMARY KEY ( `id` )
) ENGINE = INNODB COMMENT '用户登录记录表_6';

CREATE TABLE user_login_record_7 (
       `id` BIGINT ( 20 ) NOT NULL AUTO_INCREMENT COMMENT '主键id',
       `user_id` BIGINT ( 20 ) NOT NULL COMMENT '商品id',
       `login_type` INT ( 11 ) NOT NULL COMMENT '登录方式:0:手机号码登录,1:微信登录,2:一键登录',
       `login_time` datetime ( 3 ) DEFAULT CURRENT_TIMESTAMP ( 3 ) COMMENT '登录时间',
       PRIMARY KEY ( `id` )
) ENGINE = INNODB COMMENT '用户登录记录表_7';

CREATE TABLE user_login_record_8 (
       `id` BIGINT ( 20 ) NOT NULL AUTO_INCREMENT COMMENT '主键id',
       `user_id` BIGINT ( 20 ) NOT NULL COMMENT '商品id',
       `login_type` INT ( 11 ) NOT NULL COMMENT '登录方式:0:手机号码登录,1:微信登录,2:一键登录',
       `login_time` datetime ( 3 ) DEFAULT CURRENT_TIMESTAMP ( 3 ) COMMENT '登录时间',
       PRIMARY KEY ( `id` )
) ENGINE = INNODB COMMENT '用户登录记录表_8';

CREATE TABLE user_login_record_9 (
       `id` BIGINT ( 20 ) NOT NULL AUTO_INCREMENT COMMENT '主键id',
       `user_id` BIGINT ( 20 ) NOT NULL COMMENT '商品id',
       `login_type` INT ( 11 ) NOT NULL COMMENT '登录方式:0:手机号码登录,1:微信登录,2:一键登录',
       `login_time` datetime ( 3 ) DEFAULT CURRENT_TIMESTAMP ( 3 ) COMMENT '登录时间',
       PRIMARY KEY ( `id` )
) ENGINE = INNODB COMMENT '用户登录记录表_9';

# 设置id自增初始化值
ALTER TABLE user_login_record_0 AUTO_INCREMENT = 10;
ALTER TABLE user_login_record_1 AUTO_INCREMENT = 1;
ALTER TABLE user_login_record_2 AUTO_INCREMENT = 2;
ALTER TABLE user_login_record_3 AUTO_INCREMENT = 3;
ALTER TABLE user_login_record_4 AUTO_INCREMENT = 4;
ALTER TABLE user_login_record_5 AUTO_INCREMENT = 5;
ALTER TABLE user_login_record_6 AUTO_INCREMENT = 6;
ALTER TABLE user_login_record_7 AUTO_INCREMENT = 7;
ALTER TABLE user_login_record_8 AUTO_INCREMENT = 8;
ALTER TABLE user_login_record_9 AUTO_INCREMENT = 9;

-- 全局级别:设置id自增步长
SET GLOBAL auto_increment_increment=10;
  • yml配置
spring:
  application:
    name: springboot-mybatisplus
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
    username: root
    password: 123456

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 全局配置,开启缓存(一级缓存默认开启,二级缓存需手动开启)
    cache-enabled: true
  mapper-locations: classpath:/mapper/**/*.xml
  type-aliases-package: com.coolw.mybatisplus.entity

基于数据库ID自增,自定义动态表名处理器,实现接口TableNameHandler

package com.coolw.mybatisplus.config;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.handler.TableNameHandler;

import java.util.HashMap;
import java.util.Map;

/**
 * 自定义动态表名处理器,基于id的方式进行分表
 * <p>
 * TableNameHandler:MybatisPlus提供处理动态表名的接口,可以通过这个接口获取当前执行的SQL和数据库表名
 * <p>
 * 需要设置每个表的id自增初始值 及 id自增步长(mysql仅支持全局修改)
 * 分库分表一般需要用分布式ID生成(例如雪花算法),使用中间件shareding jdbc(推荐) 或者 mycat
 *
 * @author coolw
 * @version 1.0
 * @date 2023/9/22 8:59
 */
public class IdTableNameHandler implements TableNameHandler {

    /**
     * 哪些表可以使用这个动态表名规则
     * 表名 -> 分几张表
     */
    private static final Map<String, Integer> CONFIG_TABLE_INFO_MAP = new HashMap<>();

    static {
        CONFIG_TABLE_INFO_MAP.put("user_login_record", 10);
    }

    /**
     * 保存分表的id,使用ThreadLocal存储,避免多线程数据冲突
     */
    private static final ThreadLocal<Long> ID_DATA = new ThreadLocal<>();

    /**
     * 初始化当前分表的id
     */
    public static void initCurrentId(Long id) {
        ID_DATA.set(id);
    }

    /**
     * 获取当前分表的id
     */
    public static Long getCurrentId() {
        return ID_DATA.get();
    }

    /**
     * 分表使用结束后,手动进行remove清除分表的id,防止内存泄露
     */
    public static void removeCurrentId() {
        ID_DATA.remove();
    }

    /**
     * 获取动态表名
     */
    @Override
    public String dynamicTableName(String sql, String tableName) {
        // 判断当前表名是否在动态表名配置中
        if (StrUtil.isBlank(tableName) || !CONFIG_TABLE_INFO_MAP.containsKey(tableName)) {
            return tableName;
        }

        // 分表的个数
        int tableSize = CONFIG_TABLE_INFO_MAP.get(tableName);

        // 当前分表的id
        Long currentId = getCurrentId();

        // 当前id的数据存储在第几张表
        int tableIndex = (int) (currentId % tableSize);

        // 清理当前id
        removeCurrentId();

        return tableName + "_" + tableIndex;
    }
}

需要将自定义表名动态处理器,配置到mybatis-plus拦截器中

package com.coolw.mybatisplus.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * mybatis-plus 配置
 *
 * @author coolw
 * @version 1.0
 * @date 2023/2/1 15:53
 */
@Configuration
public class MybatisPlusConfig {

    /**
     * Mybatis plus 拦截器
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        // 动态表名
        interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor());
        return interceptor;
    }

    /**
     * 注册动态表名拦截器
     */
    private DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor() {
        DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();

        dynamicTableNameInnerInterceptor.setTableNameHandler(new IdTableNameHandler());

        return dynamicTableNameInnerInterceptor;
    }
}
  • UserLoginRecord
package com.coolw.mybatisplus.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.coolw.common.api.BaseDomain;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.Date;

/**
 * 用户登录记录表
 *
 * @author coolw
 * @version 1.0
 * @date 2023/9/22 8:53
 */
@TableName("user_login_record")
@Getter
@Setter
@NoArgsConstructor
public class UserLoginRecord extends BaseDomain {
    private static final long serialVersionUID = -5994848718492490449L;

    @TableId(type = IdType.AUTO)
    private Long id;

    private Long userId;

    private Integer loginType;

    private Date loginTime;

    public UserLoginRecord(Long userId, Integer loginType) {
        this.userId = userId;
        this.loginType = loginType;
    }
}
  • UserService
package com.coolw.mybatisplus.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.coolw.mybatisplus.entity.UserEntity;

public interface UserService extends IService<UserEntity> {
}
  • UserServiceImpl
package com.coolw.mybatisplus.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.coolw.mybatisplus.dao.UserMapper;
import com.coolw.mybatisplus.entity.UserEntity;
import com.coolw.mybatisplus.service.UserService;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, UserEntity> implements UserService {
}
  • UserLoginRecordMapper
/**
 * 用户登录记录表Mapper
 *
 * @author coolw
 * @version 1.0
 * @date 2023/9/22 8:55
 */
public interface UserLoginRecordMapper extends BaseMapper<UserLoginRecord> {

    /**
     * 根据用户id获取登录记录
     *
     * @param userId 用户id
     * @return 登录记录
     */
    @Select("select * from user_login_record where user_id = #{userId}")
    List<UserLoginRecord> selectListByUserId(@Param("userId") Long userId);
}
  • DynamicTableNameTest 测试类
    新增
package com.coolw.mybatisplus.dynamictable;

import cn.hutool.core.util.IdUtil;
import com.coolw.mybatisplus.config.IdTableNameHandler;
import com.coolw.mybatisplus.dao.UserLoginRecordMapper;
import com.coolw.mybatisplus.entity.UserLoginRecord;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;
import java.util.Random;

/**
 * 分表:动态表名测试类
 *
 * @author coolw
 * @version 1.0
 * @date 2023/9/22 9:18
 */
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class DynamicTableNameTest {

    @Autowired
    private UserLoginRecordMapper userLoginRecordMapper;

    @Test
    public void testInsert() {
        long userId = IdUtil.getSnowflakeNextId();

        // 设置分表的id
        IdTableNameHandler.initCurrentId(1L);
        UserLoginRecord userLoginRecord1 = new UserLoginRecord(userId, 1);
        userLoginRecordMapper.insert(userLoginRecord1);

        // 设置分表的id
        IdTableNameHandler.initCurrentId(2L);
        UserLoginRecord userLoginRecord2 = new UserLoginRecord(userId, 2);
        userLoginRecordMapper.insert(userLoginRecord2);
    }

    @Test
    public void testInsert1() {
        Random random = new Random();

        long userId = IdUtil.getSnowflakeNextId();

        for (int i = 1; i <= 20; i++) {
            // 设置分表的id
            IdTableNameHandler.initCurrentId((long) i);
            UserLoginRecord userLoginRecord = new UserLoginRecord(userId, random.nextInt(3));
            userLoginRecordMapper.insert(userLoginRecord);
        }
    }

    @Test
    public void testSelectList() {
        // 设置分表的id
        IdTableNameHandler.initCurrentId(12L);
        List<UserLoginRecord> records = userLoginRecordMapper.selectList(null);
        log.info("records:{}", records);
    }

    @Test
    public void testSelectListByUserId() {
        long userId = 1705033771829153792L;

        // 设置分表的id
        IdTableNameHandler.initCurrentId(12L);
        List<UserLoginRecord> records = userLoginRecordMapper.selectListByUserId(userId);
        log.info("records:{}", records);
    }
}

文章来源:https://blog.csdn.net/weixin_41858020/article/details/135022456
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。