最近遇到一个项目财务系统,这个财务系统主要是分为两大部分,一部分是集团管理端,一部分是各个学校的管理端,那么整个系统的数据是共享的,集团端登录的时候需要看到所有的数据,各个学校的管理端看到的数据只能是自己的。这个系统就是典型的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系统(改进版)》
还没有评论,来说两句吧...