优秀的编程知识分享平台

网站首页 > 技术文章 正文

Java,SpringBoot,MyBatisPlus,多租户插件的应用,代码分享

nanyue 2024-08-21 19:46:50 技术文章 4 ℃

背景

SaaS(Software-as-a-Service),软件即服务,通过网络提供软件服务。

多租户(multi-tenancy technology),多重租赁技术,一种软件架构技术,是在探讨与实现如何于多用户的环境下共用相同的系统或程序组件,并且仍可确保各用户间数据的隔离性。

多租户实现方式:1、独立数据库、2、共享数据库但隔离数据架构、3、共享数据库且共享数据架构。

MybatisPlus多租户插件:com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor,实现了共享数据库且共享数据架构,提供了租户处理器( TenantId 行级 ),租户之间共享数据库,共享数据架构,通多表字段(租户ID)进行数据逻辑隔离。

代码

pom.xml

<dependencies>
    <!-- spring boot start-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!--mybatis-plus-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.1</version>
    </dependency>
    <!--mybatis-plus代码生成器-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-generator</artifactId>
        <version>3.4.1</version>
    </dependency>
    <!-- pagehelper分页 -->
    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper-spring-boot-starter</artifactId>
        <version>1.2.13</version>
    </dependency>
    <dependency>
        <groupId>com.github.jsqlparser</groupId>
        <artifactId>jsqlparser</artifactId>
        <version>3.1</version>
    </dependency>
    <!-- Driver -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <!-- Druid -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.22</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <!-- lombok插件-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.20</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

application.yml

server:
  port: 8080
  tomcat:
    uri-encoding: utf-8
  servlet:
    context-path: /demo

#数据源配置
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/demo?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&tinyInt1isBit=false&allowMultiQueries=true&serverTimezone=GMT%2B8
    username: root
    password: ********
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      initial-size: 20
      min-idle: 20
      max-active: 50
      max-wait: 60000
      time-between-eviction-runsMillis: 60000
      min-evictable-idle-timeMillis: 300000
      validation-query: select 'x' FROM DUAL
      test-on-borrow: false
      test-on-return: false
      max-open-prepared-statements: 20
      max-pool-prepared-statement-per-connection-size: 20
      filters:
        stat:
          # 数据库类型
          db-type: mysql
          # 慢SQL
          log-slow-sql: true
          # 慢SQL执行时间
          slow-sql-millis: 1000
          merge-sql: true
      initialization-mode: always
      connection-properties: druid.stat.mergeSql=true;druid.stat.sloSqlMillis=5000

logging:
  level:
    #com.what21.demo: error
    com.what21.demo.mapper: debug

配置类:

package com.what21.demo.config;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import com.what21.demo.config.mybatis.MyTenantLineHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        // 多租户: TenantLineInnerInterceptor
        TenantLineInnerInterceptor tenantLineInnerInterceptor = new TenantLineInnerInterceptor();
        TenantLineHandler tenantLineHandler = new MyTenantLineHandler();
        tenantLineInnerInterceptor.setTenantLineHandler(tenantLineHandler);
        mybatisPlusInterceptor.addInnerInterceptor(tenantLineInnerInterceptor);
        return mybatisPlusInterceptor;
    }

}

MyTenantLineHandler

package com.what21.demo.config.mybatis;

import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.what21.demo.service.TenantServiceUtils;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue;

@Slf4j
public class MyTenantLineHandler implements TenantLineHandler {

    /**
     * 获取租户ID实际应该从用户信息中获取
     *
     * @return
     */
    @Override
    public Expression getTenantId() {
        // 模拟ID
        String userTenantId = TenantServiceUtils.getTenantId();
        return new StringValue(userTenantId);
    }

    /**
     * 获取租户表字段 默认为tenant_id
     *
     * @return
     */
    @Override
    public String getTenantIdColumn() {
        return "tenant_id";
    }

    /**
     * 表过滤,返回true,表示当前表不进行租户过滤
     *
     * @param tableName 表名
     * @return
     */
    @Override
    public boolean ignoreTable(String tableName) {
        log.info("==========================ignoreTable");
        // 排除user表
        return "user".equalsIgnoreCase(tableName);
    }

}

TenantServiceUtils

package com.what21.demo.service;

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

public class TenantServiceUtils {

    /**
     * @return
     */
    public static String getTenantIdByHttpServletRequest() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        if (request == null) {
            return null;
        }
        try {
            return "1";
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * @return
     */
    public static String getTenantId() {
        return "2";
    }

}

实体类

package com.what21.demo.model;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;

@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_order2")
public class Order implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    // 订单编号
    private String code;

    // 所属用户
    private Integer userId;

    // 总价钱
    private BigDecimal totalPrice;

    // 备注
    private String remark;

    // 创建时间
    private Date createTime;

    // @Version
    private Long version;

    // 租户ID
    private String tenantId;

}

Mapper类

package com.what21.demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.what21.demo.model.Order;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface OrderMapper extends BaseMapper<Order> {
}
CREATE TABLE `t_order2` (
  `id` int(12) NOT NULL AUTO_INCREMENT,
  `code` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
  `user_id` int(12) DEFAULT NULL,
  `total_price` decimal(10,2) DEFAULT NULL,
  `remark` text COLLATE utf8_unicode_ci,
  `create_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  `version` bigint(18) DEFAULT NULL,
  `tenant_id` varchar(12) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3101 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

多租户测试类

package com.what21.demo.mapper;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.what21.demo.model.Order;
import org.junit.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.math.BigDecimal;
import java.util.Date;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class OrderMapperTest {

    @Autowired
    OrderMapper orderMapper;

    @Test
    public void testInsert() {
        for (int i = 0; i < 100; i++) {
            Order newOrder = new Order();
            newOrder.setCode("order" + i);
            newOrder.setUserId(1);
            newOrder.setTotalPrice(new BigDecimal(i));
            newOrder.setRemark("备注");
            newOrder.setCreateTime(new Date());
            newOrder.setVersion(0L);
            // 如果不指定值,默认TenantId插入为:2<租户标识>
            // newOrder.setTenantId(String.valueOf((i%5)));
            int operate = orderMapper.insert(newOrder);
            System.out.println(operate);
            // INSERT INTO t_order2 (code, user_id, total_price, remark, create_time, version, tenant_id) VALUES (?, ?, ?, ?, ?, ?, '2')
        }

    }

    @Test
    public void testUpdate() {
        // SELECT id, code, user_id, total_price, remark, create_time, version, tenant_id FROM t_order2 WHERE id = ? AND tenant_id = '2'
        Order order = orderMapper.selectById(3001);
        Order newOrder = new Order();
        newOrder.setId(3001);
        newOrder.setRemark("......");
        // UPDATE t_order2 SET remark = ? WHERE tenant_id = '2' AND id = ?
        orderMapper.updateById(newOrder);
    }

    @Test
    public void testSelect() {
        QueryWrapper warpper = new QueryWrapper();
        warpper.eq("user_id",1);
        List<Order> orderList = orderMapper.selectList(warpper);
        orderList.forEach(System.out::println);
    }

}

Tags:

最近发表
标签列表