mybatis-plus多租戶的使用


1. 什么是多租戶,怎么理解多租戶

多租戶:多租戶技術或稱多重租賃技術,簡稱SaaS,是一種軟件架構技術。它支持一個實例服務多個用戶,每一個用戶被稱之為租戶,且保證租戶間數據隔離,並且保證每個用戶的數據對其他租戶不可見。它能給予租戶可以對系統進行部分定制的能力,如:用戶界面顏色或業務規則,但是他們不能定制修改軟件的代碼。

單租戶:從多租戶的定義可以看出,多租戶下無法實現用戶的定制化操作。單租戶就可以。單租戶是為每個客戶單獨創建各自的軟件應用和支撐環境。單租戶SaaS被廣泛引用在客戶需要支持定制化的應用場合,而這種定制或者是因為地域或他們需要更高的安全控制。在單租戶的模式,每個客戶都可以有一份分別放在獨立的服務器上的數據庫和操作系統,或者使用強的安全措施進行隔離的虛擬網絡環境中。



2. 多租戶的實現方案

方案一 獨立數據庫

一個租戶一個數據庫,這種方案的用戶數據隔離級別最高,安全性最好,但成本較高。

  • 優點:為不同的租戶提供獨立的數據庫,有助於簡化數據模型的擴展設計,滿足不同租戶的獨特需求;如果出現故障,恢復數據比較簡單。
  • 缺點: 增多了數據庫的安裝數量,隨之帶來維護成本和購置成本的增加。
    這種方案與傳統的一個客戶、一套數據、一套部署類似,差別只在於軟件統一部署在運營商那里。如果面對的是銀行、醫院等需要非常高數據隔離級別的租戶,可以選擇這種模式,提高租用的定價。如果定價較低,產品走低價路線,這種方案一般對運營商來說是無法承受的。

方案二 共享數據庫 獨立數據架構

多個或所有租戶共享Database,但是每個租戶一個Schema(也可叫做一個user)。底層庫比如是:DB2、ORACLE等,一個數據庫下可以有多個SCHEMA。

  • 優點: 為安全性要求較高的租戶提供了一定程度的邏輯數據隔離,並不是完全隔離;每個數據庫可支持更多的租戶數量。
  • 缺點: 如果出現故障,數據恢復比較困難,因為恢復數據庫將牽涉到其他租戶的數據; 如果需要跨租戶統計數據,存在一定困難。

方案三 共享數據庫,共享數據架構,共享數據表

租戶共享同一個Database、同一個Schema,但在表中增加多租戶ID的數據字段。這是共享程度最高、隔離級別最低的模式。即每插入一條數據時都需要有一個客戶的標識。這樣才能在同一張表中區分出不同客戶的數據。
比如:我們現在要做一個社區多租戶SaaS項目,每個社區的數據獨立,那么我們設計表的時候大多數表可能都要有社區id字段用來區分不同的社區。

  • 優點:三種方案比較,第三種方案的維護和購置成本最低,允許每個數據庫支持的租戶數量最多。
  • 缺點: 隔離級別最低,安全性最低,需要在設計開發時加大對安全的開發量; 數據備份和恢復最困難,需要逐表逐條備份和還原。



3. 基於Mybatis-plus多租戶實戰落地

前言:不用多租戶的架構,采用原始的方式也是可行的,只是原始的方式是在每一個接口都添加租戶標識參數,這種方法雖然能夠實現,但是造成了一定量的代碼冗余。
參考:
https://blog.csdn.net/weixin_38111957/article/details/101161660
https://blog.csdn.net/qq_34936541/article/details/100048199?utm_term=springboot多租戶實現&utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2allsobaiduweb~default-4-100048199&spm=3001.4430
MP提供了一種多租戶的解決方案,實現方式是基於分頁插件進行實現的。

// 多租戶配置類

@Configuration
public class MybatisPlusTenantConfig {
    
    // 定義當前的多租戶標識字段
    private static final String SYSTEM_TENANT_ID = "shop_id";
    
    // 定義當前有哪些表要忽略多租戶的操作  
    private static final List<String> IGNORE_TENANT_TABLES = new ArrayList<String>();
 
    // 創建PaginationInterceptor攔截器     其實實現方式是基於分頁插件進行實現的
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        // 創建租戶SQL解析器
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();

        // 創建SQL解析器,會對sql進行攔截處理。
        TenantSqlParser tenantSqlParser = new TenantSqlParser();
        tenantSqlParser.setTenantHandler(new TenantHandler() {
                
                // 用於設置租戶id的值  
                @Override
                public Expression getTenantId(boolean where) {
                    // 暫時寫死租戶id的值,用於測試
                    String shopIdValue = "test";
                    return new StringValue(shopIdValue);
                }
                
                // 設置租戶id所對應的  表字段
                @Override
                public String getTenantIdColumn() {
                    return "shop_id";
                }
                
                // 設置表級過濾器   用於設置哪些表不需要這個多租戶操作,即操作sql的時候,不帶shopId
                @Override
                public boolean doTableFilter(String tableName) {
                    // 忽略掉一些表:如租戶表(provider)本身不需要執行這樣的處理。
                    return IGNORE_TENANT_TABLES.stream().anyMatch((e) -> e.equalsIgnoreCase(tableName));
                }
            });
    
        // 創建SQL解析器集合
        List<ISqlParser> sqlParserList = new ArrayList<>();
        sqlParserList.add(tenantSqlParser);
        
        // 設置SQL解析器集合
        paginationInterceptor.setSqlParserList(sqlParserList);
        return paginationInterceptor;
    }
}
@SpringBootTest
@RunWith(SpringRunner.class)
public class StoreTest {

	@Reference(version = "1.0.0", check = false)
	private IStoreService storeService;

	
	@Test
	@Rollback(false)
	public void saveTest() {
		Store store = new Store();
		store.setBrandId("test");
		store.setStoreName("測試");
		store.setProvince("北京");
		store.setCity("昌平區");
		store.setArea("金燕龍大廈");
		store.setAddress("北京 昌平區 金燕龍大廈");
		// 這里沒有設置shopId的值
		storeService.save(store);
	}
	
	@Test
	public void queryTest() {
		Store store = storeService.getById("1400885090552180737");
		System.out.println(store);
	}
}

配置好之后,不管是查詢、新增、修改刪除方法,MP都會自動加上租戶ID的標識。



某些特定語句需要不過濾租戶條件,比喻運營管理時候看所有用戶什么的,可以在Mapper上租戶注解的形式實現。

@Repository
public interface SysUserMapper  extends BaseMapper<SysUser> {
 
    /**
     * 根據條件分頁查詢用戶列表
     *
     * @param sysUser 用戶信息
     * @return 用戶信息集合信息
     */
    @SqlParser(filter = true)
    public List<SysUser> selectUserList(@Param(Constants.WRAPPER) Wrapper<User> userWrapper, @Param("user")SysUser sysUser);


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM