最近遇到一个项目财务系统,这个财务系统主要是分为两大部分,一部分是集团管理端,一部分是各个学校的管理端,那么整个系统的数据是共享的,集团端登录的时候需要看到所有的数据,各个学校的管理端看到的数据只能是自己的。这个系统就是典型的saas系统。在这个系统里面,数据是共享的,而且都是一个集团,因此我们程序开发肯定是套,数据库肯定是公用一个数据库。那这时候由于各个系统都在同一个数据库中操作,如果程序编写不严谨,例如造成如下问题:
1、程序编写不严谨,A学校操作的内容导致所有学校的数据发生变更。 2、程序编写不严谨,B学校也看到了A学校的数据。
一般公司编写代码的研发人员技能参差不齐,同时由于信息不对称,很容易出现上面的情况。所以我们怎么避免呢?今天我们就来使用mybatis-plus的插件来解决这个问题。
其实解决这个问题的核心就是:
1、每张表里面添加一个tenant_id
2、利用mybatis-plus的插件进行sql转换,在运行sql的时候,自动添加一个where的条件,也就是把tenant_id加上。
以上,那么研发人员在编写代码的时候,就只需要关心具体的实现代码,不用关心多租户造成的数据影响。这样子是不是很方便。下面我们实战代码演示一下。
1、编写一个测试表
/* Navicat Premium Data Transfer Source Server : 演示数据库 Source Server Type : MySQL Source Server Version : 50738 Source Host : 192.168.31.30:3306 Source Schema : test Target Server Type : MySQL Target Server Version : 50738 File Encoding : 65001 Date: 15/08/2022 15:10:10 */ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for user -- ---------------------------- DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` bigint(20) NOT NULL, `tenant_id` int(11) NULL DEFAULT NULL COMMENT '租户id', `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名', `age` int(3) NULL DEFAULT NULL COMMENT '年龄', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of user -- ---------------------------- INSERT INTO `user` VALUES (1, 1, '张三', 18); INSERT INTO `user` VALUES (2, 2, '李四', 16); INSERT INTO `user` VALUES (3, 1, '王五', 16); INSERT INTO `user` VALUES (4, 2, '赵六', 15); INSERT INTO `user` VALUES (5, 1, '小龙', 16); SET FOREIGN_KEY_CHECKS = 1;
2、创建一个maven项目,导入poml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mybatisplus.demo</groupId> <artifactId>MyBatisDemo</artifactId> <version>1.0</version> <packaging>jar</packaging> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.3.RELEASE</version> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <mybatis-plus-boot-starter.version>3.0-RC3</mybatis-plus-boot-starter.version> <HikariCP.version>3.2.0</HikariCP.version> </properties> <dependencies> <!-- 这是mysql的依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- 这是lombok的依赖 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- 这是mybatis-plus依赖 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.1.1</version> </dependency> <!-- 这是mybatis-plus的代码自动生成器 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.1.1</version> </dependency> <!-- 这是模板引擎依赖 --> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.28</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
3、编写主类
package com.mybatisplus.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan(basePackages = { "com.mybatisplus.demo.mapper" }) // 扫描DAO
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}4、编写crontroller
package com.mybatisplus.demo.controller;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.mybatisplus.demo.entity.User;
import com.mybatisplus.demo.service.IUserService;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private IUserService userService;
/**
* 根据ID获取用户信息
*
* @Param userId 用户ID
* @Return User 用户实体
*/
@RequestMapping("/getInfo")
public User getInfo(String userId) {
User user = userService.getById(userId);
return user;
}
/**
* 查询全部信息
*
* @Return List 用户实体集合
*/
@RequestMapping("/getList")
public List getList() {
List userList = userService.list();
return userList;
}
/**
* 分页查询全部数据
*
* @Return IPage 分页数据
*/
@RequestMapping("/getInfoListPage")
public IPage getInfoListPage() {// 需要在Config配置类中配置分页插件
IPage page = new Page<>();
page.setCurrent(5); // 当前页
page.setSize(1); // 每页条数
page = userService.page(page);
return page;
}
/**
* 根据指定字段查询用户信息集合
*
* @Return Collection 用户实体集合
*/
@RequestMapping("/getListMap")
public Collection getListMap() {
Map map = new HashMap<>();// kay是字段名 value是字段值
map.put("name", "tzs");
Collection userList = userService.listByMap(map);
return userList;
}
/**
* 新增用户信息
*
*/
@RequestMapping("/saveInfo")
public void saveInfo() {
User user = new User();
user.setId(5);
user.setName("小龙");
user.setAge(16);
userService.save(user);
}
/**
* 批量新增用户信息
*/
@RequestMapping("/saveInfoList")
public void saveInfoList() {// 创建对象
User sans = new User();
sans.setName("Sans");
User papyrus = new User();
papyrus.setName("papyrus");
List list = new ArrayList<>();
list.add(sans);
list.add(papyrus);
userService.saveBatch(list);
}
/**
* 更新用户信息
*/
@RequestMapping("/updateInfo")
public void updateInfo() {// 根据实体中的ID去更新,其他字段如果值为null则不会更新该字段,参考yml配置文件
User user = new User();
user.setId(1);
userService.updateById(user);
}
/**
* 新增或者更新用户信息
*/
@RequestMapping("/saveOrUpdateInfo")
public void saveOrUpdate() {// 传入的实体类user中ID为null就会新增(ID自增)//实体类ID值存在,如果数据库存在ID就会更新,如果不存在就会新增
User user = new User();
user.setId(1);
userService.saveOrUpdate(user);
}
/**
* 根据ID删除用户信息
*/
@RequestMapping("/deleteInfo")
public void deleteInfo(String userId) {
userService.removeById(userId);
}
/**
* 根据ID批量删除用户信息
*/
@RequestMapping("/deleteInfoList")
public void deleteInfoList() {
List userIdlist = new ArrayList<>();
userIdlist.add("12");
userIdlist.add("13");
userService.removeByIds(userIdlist);
}
/**
* 根据指定字段删除用户信息
*/
@RequestMapping("/deleteInfoMap")
public void deleteInfoMap() {// kay是字段名 value是字段值
Map map = new HashMap<>();
map.put("id", 1);
userService.removeByMap(map);
}
}5、编写service
package com.mybatisplus.demo.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.mybatisplus.demo.entity.User;
public interface IUserService extends IService<User> {
}6、编写serviceImpl
package com.mybatisplus.demo.service.impl;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mybatisplus.demo.entity.User;
import com.mybatisplus.demo.mapper.UserMapper;
import com.mybatisplus.demo.service.IUserService;
/**
* <p>
* 服务实现类
* </p>
*
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}7、编写mapper
package com.mybatisplus.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mybatisplus.demo.entity.User;
public interface UserMapper extends BaseMapper<User> {
}8、还差个entity
package com.mybatisplus.demo.entity;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* <p>
*
* </p>
*
* @author tzs
* @since 2021-03-13
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String name;
private Integer age;
}9、在src下面新建一个mapper文件夹,再创建一个UserMapper.xml
<?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.mybatisplus.mapper.UserMapper"> </mapper>
10、最后的重头戏
package com.mybatisplus.demo.config;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.baomidou.mybatisplus.core.parser.ISqlParser;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.tenant.TenantHandler;
import com.baomidou.mybatisplus.extension.plugins.tenant.TenantSqlParser;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue;
@Configuration
public class MyBatisPlusConfig {
/**
* 分页插件
*
* @return
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 创建SQL解析器集合
List<ISqlParser> sqlParserList = new ArrayList<>();
// 创建租户SQL解析器
TenantSqlParser tenantSqlParser = new TenantSqlParser();
// 设置租户处理器
tenantSqlParser.setTenantHandler(new TenantHandler() {
public Expression getTenantId() {
// 设置当前租户ID,实际情况你可以从cookie、或者缓存中拿都行
return new StringValue("1");
}
@Override
public String getTenantIdColumn() {
// 对应数据库租户ID的列名
return "tenant_id";
}
@Override
public boolean doTableFilter(String tableName) {
// 是否需要需要过滤某一张表
return false;
}
});
sqlParserList.add(tenantSqlParser);
paginationInterceptor.setSqlParserList(sqlParserList);
return paginationInterceptor;
}
}zhegconfig是最重要的,也就是这个config再sql执行的时候会自动拼装sql,然后tenant_id直接根据实际情况改就可以了。然后我们运行项目,请求接口。
我们在这里tenant_id写死的1,所以可以在接口请求的返回结果里面看到这个接口只会返回tenant_id为1的数据,不会返回tenant_id=2的数据。
最后这里由于本文写的时间比较久,因此当时没有附上源码,如果想用源码的话,可以看看改进版的文章:《使用mybatis-plus完成多租户的saas系统(改进版)》









还没有评论,来说两句吧...