mybatis水平分表实现动态表名的项目实例
作者:先养只猫 发布时间:2023-04-02 22:11:42
一、水平分表
当业务需求的数据量过大时,一个表格存储数据会非常之多,故时长采用水平分表的方式来减少每张表的数据量即是提升查询数据库时的效率。
水平分表时,各表的结构完全一样,表名不同。
例如:这里我们建了10张user表,每张表的结构完全一致,表名由0~9。
表中包含有id和name属性且都采用varchar的存储类型。
为什么id没有采用int自增的形式?
大型项目极有可能采用分布式数据库,若采用自增的方式,会导致id重复。且id也不一定只是由纯数字组成,id不由数据库自主生成后,可在后台代码中是使用工具类进行id生成。
二、项目实现
目录结构
不需要的文件我已经手动马赛克,避免干扰。
简单以一个用户信息的新增和查询为例子。
pom.xml
本次使用mybatis+mybatis-plus为例。
<!--spring mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
application.yaml
server:
port: 9090
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
username: root
password: root
mybatis-plus:
mapper-locations: classpath:mybatis/*.xml #mapper位置
type-aliases-package: com/example/demo/po #设置别名,类名的小写
工具类
R返回类
package com.example.demo.utils;
import com.example.demo.utils.inter.ResultCode;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
@Data
public class R<T> {
private Boolean success;
private Integer code;
private String message;
private T data;
public R() {
}
public static <T> R<T> ok(){
R<T> r = new R<>();
r.setSuccess(true);
r.setCode(ResultCode.SUCCESS);
r.setMessage("成功");
return r;
}
public static <T> R<T> error() {
R<T> r = new R<>();
r.setSuccess(false);
r.setCode(ResultCode.ERROR);
r.setMessage("失败");
return r;
}
public R<T> success(Boolean success) {
this.setSuccess(success);
return this;
}
public R<T> message(String message) {
this.setMessage(message);
return this;
}
public R<T> code(Integer code) {
this.setCode(code);
return this;
}
public void setData(T data) {
this.data = data;
}
public R<T> data(T data) {
this.setData(data);
return this;
}
public R<T> setReLoginData() {
Map<String, Integer> resultMap = new HashMap<>();
resultMap.put("code", -100);
this.data((T) resultMap);
return this;
}
public R<T> setErrorCode(Integer errorCode) {
Map<String, Integer> resultMap = new HashMap<>();
resultMap.put("errorCode", errorCode);
this.data((T) resultMap);
return this;
}
}
通过封装的返回类来实现数据的返回,同时可以自定义返回code(各种错误码),便于项目规范即管理。
ResultCode:返回码
package com.example.demo.utils.inter;
public interface ResultCode {
Integer SUCCESS = 2000;
Integer ERROR = 3000;
}
TableNameUtil:根据自己定义的规则获取表名
这里可以通过字符串拼接,实现user_0~user_9的返回。
数据存储规则: id最后一个数字为0即对应user_0,同理9对应user_9。
package com.example.demo.utils;
import org.springframework.stereotype.Component;
@Component
public class TableNameUtil {
public String getUserTableName(String id){
return "user_"+id.substring(id.length()-1);
}
}
IdUtil:根据自己定义的规则生成唯一ID
这里的生成规则是通过UUID+随机0~ 9的数字来实现唯一ID的生成。大型项目一般会有一个专门由于生成唯一序列号的服务器~。
package com.example.demo.utils;
import org.springframework.stereotype.Component;
import java.util.UUID;
@Component
public class IdUtil {
public synchronized String generatorUserId(){
return UUID.randomUUID()+String.valueOf(Math.random()*10);
}
}
实体类
po类(Persistent Object )
package com.example.demo.po;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
@Data
@TableName("user_0")//mybatis-plus匹配一张表即可,因为所有表的结构完全一致。
public class User implements Serializable {
@TableId
private String id;
private String name;
}
dto类(Data Transfer Object)
注: 用例比较简单,没有几个参数,实际时新增和查询可能都会封装许多参数。
UserIn: 新增User时用于传递数据,id由后台生成,前端传输数据时就不必存在id字段。
package com.example.demo.po.dto;
import lombok.Data;
@Data
public class UserIn {
private String name;
}
QueryUserId:查询时使用
package com.example.demo.po.dto;
import lombok.Data;
@Data
public class QueryUserId {
private String id;
}
Mapper层
UserMapper
package com.example.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.po.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface UserMapper extends BaseMapper<User> {
//这里只是简单定义了两个接口,本打算写个批量插入的,
//但是这必须要求批量插入时的所有表的id最后一位数字是相同的(即是同一张表),
//这里不符合,因为这里是在插入时随机生成的,就和循环单独插入一样了~
void insertUser(@Param("tableName") String tableName, @Param("user") User user);
User selectUserById(@Param("tableName") String tableName, @Param("id") String id);
}
UserMapper.xml
与UserMapper相绑定,通过@Param指定传参后xml中进行调用时的名称。
动态表名的实现也是通过传参实现的,这里直接传递参数"tableName"让xml进行获取。
注: 这里使用${tableName}而不使用#{tableNaem}是因为在使用#进行预加载时,由于此时未能获取tableName,即无法确定具体的表格,预加载会报错。
不错的博客,详细介绍
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<insert id="insertUser" parameterType="com.example.demo.po.User">
insert into ${tableName}(id, name) values (#{user.id}, #{user.name});
</insert>
<select id="selectUserById" parameterType="java.lang.String" resultType="com.example.demo.po.User">
select * from ${tableName} where id = '${id}'
</select>
</mapper>
Service层
package com.example.demo.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.demo.po.User;
public interface UserService extends IService<User> {
void insertUser(User user);
User selectUserById(String id);
}
impl实现类
package com.example.demo.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.mapper.UserMapper;
import com.example.demo.po.User;
import com.example.demo.service.UserService;
import com.example.demo.utils.IdUtil;
import com.example.demo.utils.TableNameUtil;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Resource
private IdUtil idUtil;
@Resource
private TableNameUtil tableNameUtil;
@Override
public void insertUser(User user) {
String id = idUtil.generatorUserId();//生成Id
user.setId(id);
String tableName = tableNameUtil.getUserTableName(id);//根据id获取表名
baseMapper.insertUser(tableName,user);
}
@Override
public User selectUserById(String id) {
String tableName = tableNameUtil.getUserTableName(id);
return baseMapper.selectUserById(tableName, id);
}
}
Controller层
package com.example.demo.controller;
import com.example.demo.po.User;
import com.example.demo.po.dto.UserIn;
import com.example.demo.service.UserService;
import com.example.demo.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@PostMapping("/insertUser")
public R insertUser(@RequestBody UserIn userIn){
User user = new User();
user.setName(userIn.getName());
userService.insertUser(user);
return R.ok();
}
@GetMapping("/selectUserById")
public R selectUserById(@RequestBody String id){
User user = userService.selectUserById(id);
return R.ok().data(user);
}
}
过程及结果截图展示
通过Apifox调用接口
创建用户User
使用自动生成数据,结果返回成功
…多输入些数据…查看数据库中数据
… id查询用户
测试成功
三、扩展
还可以在xml中通过调用Java方法来实现生成表名的操作,这里就不需要mapper传递tableName参数了,但是需要用于获取tableName的参数。在学习中遇到这种方式,做个记录。
这种方式比较特殊,一般很少使用。
UserMapper新增
void insertUserI(@Param("user") User user);
User selectUserByIdI(@Param("id") String id);
UserMapper.xml新增
<insert id="insertUserI" parameterType="com.example.demo.po.User">
insert into "${@com.example.demo.utils.TableNameUtil@getUserTableName(id)}"(id, name) values (#{user.id}, #{user.name});
</insert>
<select id="selectUserByIdI" parameterType="java.lang.String" resultType="com.example.demo.po.User">
select * from "${@com.example.demo.utils.TableNameUtil@getUserTableName(id)}" where id = '${id}'
</select>
或通过bind标签进行绑定
<insert id="insertUserI" parameterType="com.example.demo.po.User">
<bind name="tableName" value="@com.example.demo.utils.TableNameUtil@getUserTableName(id)"/>
insert into ${tableName}(id, name) values (#{user.id}, #{user.name});
</insert>
<select id="selectUserByIdI" parameterType="java.lang.String" resultType="com.example.demo.po.User">
<bind name="tableName" value="@com.example.demo.utils.TableNameUtil@getUserTableName(id)"/>
select * from ${tableName} where id = '${id}'
</select>
Service层新增
void insertUserI(@Param("user") User user);
User selectUserByIdI(@Param("id") String id);
Controller层新增
@PostMapping("/insertUserI")
public R insertUserI(@RequestBody UserIn userIn){
User user = new User();
user.setName(userIn.getName());
userService.insertUser(user);
log.info("insertUserI OK");
return R.ok();
}
@GetMapping("/selectUserByIdI")
public R selectUserByIdI(@RequestBody QueryUserId queryUserId){
User user = userService.selectUserById(queryUserId.getId());
log.info("selectUserByIdI :"+user.toString());
return R.ok().data(user);
}
过程及结果截图展示
调用insertUserI创建新用户
日志打印
数据库中数据
调用selectUserByIdI进行查询
日志查看
关于xml调用Java方法时是否能获取对应参数
这里在getUserTableName方法下输出了一下id
来源:https://blog.csdn.net/qq_52050769/article/details/125841208


猜你喜欢
- 环境搭建spring boot的简介以往我们开发时用到spring总是避免不了繁琐的配置,例如我们要配置一个数据库连接,可能需要以下几步:1
- 重新指定分配默认值的参数时,可以显式地为指定参数名称赋值,隐式指定的时候,是根据方法参数的顺序,靠c#编译器的推断。 代码示例: void
- 简介这篇文章我一直在纠结到底要不要写,不想写一来因为定时器用法比较简单,二来是面试中也不常问。后来还是决定写了主要是想把自己分析问题思路分享
- Struts2简介Struts2是一个基于MVC设计模式的Web应用框架,它本质上相当于一个servlet,在MVC设计模式中,Struts
- 本文实例为大家分享了Android登录界面的注册实现代码,供大家参考,具体内容如 * 册一个登录界面在控制台将输入的信息文本选框展示出来xml
- QueryWrapper条件构造之apply、last、select场景: 查询数据库限制条数时mysql上的limit使用 Qu
- 先扯再说最近一直在研究某个国产开源的MySQL数据库中间件,拉下其最新版的代码到eclipse后,启动起来,然后做各种测试和代码追踪;用完想
- 两种解决方案前端查询字典数据然后前端转码后端查询字典值,然后再转码返回给前段。本文及时针对方案2 进行的改进目标:在需要返回给前段的字段上添
- kafka消费不到数据的排查集群上新安装并启动了3个kafka Broker,代码打包上传至集群,运行后发现一直消费不到数据,本地
- 一、前言最近自己在学习Spring boot的过程中开发了一个组件 multithreadpool-spring-boot-starter,
- App.config是C#开发WinForm程序的配置文件,开发Web程序的配置文件叫Web.config。本文介绍App.config的简
- 废话不多说,上代码//编译环境:codeblocks+gcc#include <stdio.h>#include <std
- 本文实例为大家分享了Android自定义控制条效果的具体代码,供大家参考,具体内容如下ControlBar自定义一个可以调节大小的控件,可以
- 先上效果图,如果大家感觉不错,请参考实现代码。 重要的是如何实现自定义的
- jvm虚拟机栈的作用jvm虚拟机栈栈帧的组成jvm虚拟机栈,也叫java栈,它由一个个的栈帧组成,而栈帖由以下几个部分组成局部变量表-存储方
- 前言1、下面是一个效果展示;2、先抱怨一下,在博客上面的抄袭真的非常严重,为了实现一个图片滑动验证,我搜索了挺久的资料,不过内容翻来覆去就是
- 读取自定义properties注入到bean在使用springboot项目时,可使用@value的方式直接读取application.pro
- vue3新增effectScope相关的API其官方的描述是创建一个 effect 作用域,可以捕获其中所创建的响应式副作用 (即计算属性和
- protobuf对象不能直接使用jsonlib去转,因为protobuf生成的对象的get方法返回的类型有byte[],而只有String类
- 在Java中,我们可以对List集合进行如下几种方式的遍历:List<Integer> list = new ArrayList