MybatisPlus提供处理动态表名的接口TableNameHandler,可以通过这个接口获取当前执行的SQL和数据库表名
public interface TableNameHandler {
/**
* 生成动态表名
*
* @param sql 当前执行 SQL
* @param tableName 表名
* @return String
*/
String dynamicTableName(String sql, String tableName);
}
场景:用户每次登录,都要记录下来,分表数量为10个
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;
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的方式进行分表
*
* TableNameHandler:MybatisPlus提供处理动态表名的接口,可以通过这个接口获取当前执行的SQL和数据库表名
*
* 需要设置每个表的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;
}
}
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;
}
}
package com.coolw.mybatisplus.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.coolw.mybatisplus.entity.UserEntity;
public interface UserService extends IService<UserEntity> {
}
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 {
}
/**
* 用户登录记录表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);
}
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);
}
}