mybatis水平分表實(shí)現(xiàn)動態(tài)表名的項(xiàng)目實(shí)例
一、水平分表
- 當(dāng)業(yè)務(wù)需求的數(shù)據(jù)量過大時(shí),一個(gè)表格存儲數(shù)據(jù)會非常之多,故時(shí)長采用水平分表的方式來減少每張表的數(shù)據(jù)量即是提升查詢數(shù)據(jù)庫時(shí)的效率。
- 水平分表時(shí),各表的結(jié)構(gòu)完全一樣,表名不同。
- 例如:這里我們建了10張user表,每張表的結(jié)構(gòu)完全一致,表名由0~9。
- 表中包含有id和name屬性且都采用varchar的存儲類型。
- 為什么id沒有采用int自增的形式?
- 大型項(xiàng)目極有可能采用分布式數(shù)據(jù)庫,若采用自增的方式,會導(dǎo)致id重復(fù)。且id也不一定只是由純數(shù)字組成,id不由數(shù)據(jù)庫自主生成后,可在后臺代碼中是使用工具類進(jìn)行id生成。


二、項(xiàng)目實(shí)現(xiàn)
目錄結(jié)構(gòu)
不需要的文件我已經(jīng)手動馬賽克,避免干擾。
簡單以一個(gè)用戶信息的新增和查詢為例子。

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 #設(shè)置別名,類名的小寫
工具類
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;
}
}
通過封裝的返回類來實(shí)現(xiàn)數(shù)據(jù)的返回,同時(shí)可以自定義返回code(各種錯(cuò)誤碼),便于項(xiàng)目規(guī)范即管理。
ResultCode:返回碼
package com.example.demo.utils.inter;
public interface ResultCode {
Integer SUCCESS = 2000;
Integer ERROR = 3000;
}
TableNameUtil:根據(jù)自己定義的規(guī)則獲取表名
這里可以通過字符串拼接,實(shí)現(xiàn)user_0~user_9的返回。
數(shù)據(jù)存儲規(guī)則: id最后一個(gè)數(shù)字為0即對應(yīng)user_0,同理9對應(yīng)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:根據(jù)自己定義的規(guī)則生成唯一ID
這里的生成規(guī)則是通過UUID+隨機(jī)0~ 9的數(shù)字來實(shí)現(xiàn)唯一ID的生成。大型項(xiàng)目一般會有一個(gè)專門由于生成唯一序列號的服務(wù)器~。
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);
}
}
實(shí)體類
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匹配一張表即可,因?yàn)樗斜淼慕Y(jié)構(gòu)完全一致。
public class User implements Serializable {
@TableId
private String id;
private String name;
}
dto類(Data Transfer Object)
注: 用例比較簡單,沒有幾個(gè)參數(shù),實(shí)際時(shí)新增和查詢可能都會封裝許多參數(shù)。
UserIn: 新增User時(shí)用于傳遞數(shù)據(jù),id由后臺生成,前端傳輸數(shù)據(jù)時(shí)就不必存在id字段。
package com.example.demo.po.dto;
import lombok.Data;
@Data
public class UserIn {
private String name;
}
QueryUserId:查詢時(shí)使用
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> {
//這里只是簡單定義了兩個(gè)接口,本打算寫個(gè)批量插入的,
//但是這必須要求批量插入時(shí)的所有表的id最后一位數(shù)字是相同的(即是同一張表),
//這里不符合,因?yàn)檫@里是在插入時(shí)隨機(jī)生成的,就和循環(huán)單獨(dú)插入一樣了~
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中進(jìn)行調(diào)用時(shí)的名稱。
動態(tài)表名的實(shí)現(xiàn)也是通過傳參實(shí)現(xiàn)的,這里直接傳遞參數(shù)"tableName"讓xml進(jìn)行獲取。
注: 這里使用${tableName}而不使用#{tableNaem}是因?yàn)樵谑褂?進(jìn)行預(yù)加載時(shí),由于此時(shí)未能獲取tableName,即無法確定具體的表格,預(yù)加載會報(bào)錯(cuò)。
不錯(cuò)的博客,詳細(xì)介紹
<?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實(shí)現(xiàn)類
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);//根據(jù)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);
}
}
過程及結(jié)果截圖展示
通過Apifox調(diào)用接口

創(chuàng)建用戶User
使用自動生成數(shù)據(jù),結(jié)果返回成功

…多輸入些數(shù)據(jù)…查看數(shù)據(jù)庫中數(shù)據(jù)


… id查詢用戶

測試成功
三、擴(kuò)展
還可以在xml中通過調(diào)用Java方法來實(shí)現(xiàn)生成表名的操作,這里就不需要mapper傳遞tableName參數(shù)了,但是需要用于獲取tableName的參數(shù)。在學(xué)習(xí)中遇到這種方式,做個(gè)記錄。
這種方式比較特殊,一般很少使用。
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標(biāo)簽進(jìn)行綁定
<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);
}
過程及結(jié)果截圖展示
調(diào)用insertUserI創(chuàng)建新用戶

日志打印

數(shù)據(jù)庫中數(shù)據(jù)

調(diào)用selectUserByIdI進(jìn)行查詢

日志查看

關(guān)于xml調(diào)用Java方法時(shí)是否能獲取對應(yīng)參數(shù)
這里在getUserTableName方法下輸出了一下id


到此這篇關(guān)于mybatis水平分表實(shí)現(xiàn)動態(tài)表名的項(xiàng)目實(shí)例的文章就介紹到這了,更多相關(guān)mybatis 動態(tài)表名內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java如何實(shí)現(xiàn)遞歸刪除樹形數(shù)據(jù)的任一個(gè)節(jié)點(diǎn)
文章講述了在Java中實(shí)現(xiàn)遞歸刪除樹形數(shù)據(jù)的任一個(gè)節(jié)點(diǎn)時(shí)需要注意的三個(gè)點(diǎn),包括刪除的節(jié)點(diǎn)包含子節(jié)點(diǎn)、刪除子節(jié)點(diǎn)和其他子節(jié)點(diǎn)刪除的節(jié)點(diǎn)不包含子節(jié)點(diǎn)、以及該父節(jié)點(diǎn)變成葉子節(jié)點(diǎn),此外,文章還提到這兩件事包含在同一件事務(wù)中2024-12-12
Java實(shí)現(xiàn)PDF轉(zhuǎn)為Word文檔的示例代碼
眾所周知,PDF文檔除了具有較強(qiáng)穩(wěn)定性和兼容性外,?還具有較強(qiáng)的安全性,在工作中可以有效避免別人無意中對文檔內(nèi)容進(jìn)行修改。本文將分為以下兩部分介紹如何在保持布局的情況下將PDF轉(zhuǎn)為Word文檔,希望對大家有所幫助2023-01-01
Java網(wǎng)絡(luò)編程實(shí)例——簡單模擬在線聊天
學(xué)了java網(wǎng)絡(luò),也是該做個(gè)小案例來鞏固一下了。本次案例將使用UDP和多線程模擬即時(shí)聊天,簡單練練手。2021-05-05
SpringBoot如何配置文件properties和yml
這篇文章主要介紹了SpringBoot如何配置文件properties和yml問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
SpringBoot使用Sa-Token實(shí)現(xiàn)登錄認(rèn)證
本文主要介紹了SpringBoot使用Sa-Token實(shí)現(xiàn)登錄認(rèn)證,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04
Java中使用JWT生成Token進(jìn)行接口鑒權(quán)實(shí)現(xiàn)方法
這篇文章主要介紹了Java中使用JWT生成Token進(jìn)行接口鑒權(quán)實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
使用Java讀取Excel文件數(shù)據(jù)的方法詳解
通過編程方式讀取Excel數(shù)據(jù)能實(shí)現(xiàn)數(shù)據(jù)導(dǎo)入、批量處理、數(shù)據(jù)比對和更新等任務(wù)的自動化,本文為大家介紹了三種Java讀取Excel文件數(shù)據(jù)的方法,需要的可以參考下2024-01-01
springBoot整合CXF并實(shí)現(xiàn)用戶名密碼校驗(yàn)的方法
這篇文章主要介紹了springBoot整合CXF并實(shí)現(xiàn)用戶名密碼校驗(yàn)的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-08-08

