亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何使用Mybatis-plus實現多租戶架構

發布時間:2022-02-10 09:19:26 來源:億速云 閱讀:1339 作者:小新 欄目:開發技術

這篇文章給大家分享的是有關如何使用Mybatis-plus實現多租戶架構的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

多租戶(Multi-Tenant)是SaaS中的一個重要概念,它是一種軟件架構技術,在多個租戶的環境下,共享同一套系統實例,并且租戶之間的數據具有隔離性,也就是說一個租戶不能去訪問其他租戶的數據。基于不同的隔離級別,通常具有下面三種實現方案:

1、每個租戶使用獨立DataBase,隔離級別高,性能好,但成本大

2、租戶之間共享DataBase,使用獨立的Schema

3、租戶之間共享Schema,在表上添加租戶字段,共享數據程度最高,隔離級別最低。

Mybatis-plus在第3層隔離級別上,提供了基于分頁插件的多租戶的解決方案,我們對此來進行介紹。在正式開始前,首先做好準備工作創建兩張表,在基礎字段后都添加租戶字段tenant_id:

CREATE TABLE `user` (
  `id` bigint(20) NOT NULL,
  `name` varchar(20) DEFAULT NULL,
  `phone` varchar(11) DEFAULT NULL,
  `address` varchar(64) DEFAULT NULL,
  `tenant_id` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
)
CREATE TABLE `dept` (
  `id` bigint(20) NOT NULL,
  `dept_name` varchar(64) DEFAULT NULL,
  `comment` varchar(128) DEFAULT NULL,
  `tenant_id` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
)

在項目中導入需要的依賴:

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.3.2</version>
</dependency>
<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId>
    <version>3.1</version>
</dependency>

Mybatis-plus 配置類:

@EnableTransactionManagement(proxyTargetClass = true)
@Configuration
public class MybatisPlusConfig {
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();

        List<ISqlParser> sqlParserList=new ArrayList<>();
        TenantSqlParser tenantSqlParser=new TenantSqlParser();
        tenantSqlParser.setTenantHandler(new TenantHandler() {
            @Override
            public Expression getTenantId(boolean select) {               
                String tenantId = "3";
                return new StringValue(tenantId);
            }

            @Override
            public String getTenantIdColumn() {
                return "tenant_id";
            }

            @Override
            public boolean doTableFilter(String tableName) {
                return false;
            }
        });

        sqlParserList.add(tenantSqlParser);
        paginationInterceptor.setSqlParserList(sqlParserList);
        return paginationInterceptor;
    }
}

這里主要實現的功能:

  • 創建SQL解析器集合

  • 創建租戶SQL解析器

  • 設置租戶處理器,具體處理租戶邏輯

這里暫時把租戶的id固定寫成3,來進行測試。測試執行全表語句:

public List<User> getUserList() {
    return userMapper.selectList(new LambdaQueryWrapper<User>().isNotNull(User::getId));
}

使用插件解析執行的SQL語句,可以看到自動在查詢條件后加上了租戶過濾條件:

如何使用Mybatis-plus實現多租戶架構

那么在實際的項目中,怎么將租戶信息傳給租戶處理器呢,根據情況我們可以從緩存或者請求頭中獲取,以從Request請求頭獲取為例:

@Override
public Expression getTenantId(boolean select) {
    ServletRequestAttributes attributes=(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = attributes.getRequest();
    String tenantId = request.getHeader("tenantId");
    return new StringValue(tenantId);
}

前端在發起http請求時,在Header中加入tenantId字段,后端在處理器中獲取后,設置為當前這次請求的租戶過濾條件。

如果是基于請求頭攜帶租戶信息的情況,那么在使用中可能會遇到一個坑,如果當使用多線程的時候,新開啟的異步線程并不會自動攜帶當前線程的Request請求。

@Override
public List<User> getUserListByFuture() {
    Callable getUser=()-> userMapper.selectList(new LambdaQueryWrapper<User>().isNotNull(User::getId));
    FutureTask<List<User>> future=new FutureTask<>(getUser);
    new Thread(future).start();
    try {
        return future.get();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

執行上面的方法,可以看出是獲取不到當前的Request請求的,因此無法獲得租戶id,會導致后續報錯空指針異常:

如何使用Mybatis-plus實現多租戶架構

修改的話也非常簡單,開啟RequestAttributes的子線程共享,修改上面的代碼:

@Override
public List<User> getUserListByFuture() {
    ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    Callable getUser=()-> {
        RequestContextHolder.setRequestAttributes(sra, true);
        return userMapper.selectList(new LambdaQueryWrapper<User>().isNotNull(User::getId));
    };
    FutureTask<List<User>> future=new FutureTask<>(getUser);
    new Thread(future).start();
    try {
        return future.get();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

這樣修改后,在異步線程中也能正常的獲取租戶信息了。

那么,有的小伙伴可能要問了,在業務中并不是所有的查詢都需要過濾租戶條件啊,針對這種情況,有兩種方式來進行處理。

1、如果整張表的所有SQL操作都不需要針對租戶進行操作,那么就對表進行過濾,修改doTableFilter方法,添加表的名稱:

@Override
public boolean doTableFilter(String tableName) {
    List<String> IGNORE_TENANT_TABLES= Arrays.asList("dept");
    return IGNORE_TENANT_TABLES.stream().anyMatch(e->e.equalsIgnoreCase(tableName));
}

這樣,在dept表的所有查詢都不進行過濾:

如何使用Mybatis-plus實現多租戶架構

2、如果有一些特定的SQL語句不想被執行租戶過濾,可以通過@SqlParser注解的形式開啟,注意注解只能加在Mapper接口的方法上:

@SqlParser(filter = true)
@Select("select * from user where name =#{name}")
User selectUserByName(@Param(value="name") String name);

或在分頁攔截器中指定需要過濾的方法:

@Bean
public PaginationInterceptor paginationInterceptor() {
    PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
    paginationInterceptor.setSqlParserFilter(metaObject->{
        MappedStatement ms = SqlParserHelper.getMappedStatement(metaObject);
        // 對應Mapper、dao中的方法
        if("com.cn.tenant.dao.UserMapper.selectUserByPhone".equals(ms.getId())){
            return true;
        }
        return false;
    });
    ...
}

上面這兩種方式實現的功能相同,但是如果需要過濾的SQL語句很多,那么第二種方式配置起來會比較麻煩,因此建議通過注解的方式進行過濾。

除此之外,還有一個比較容易踩的坑就是在復制Bean時,不要復制租戶id字段,否則會導致SQL語句報錯:

public void createSnapshot(Long userId){
    User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getId, userId));
    UserSnapshot userSnapshot=new UserSnapshot();
    BeanUtil.copyProperties(user,userSnapshot);
    userSnapshotMapper.insert(userSnapshot);
}

查看報錯可以看出,本身Bean的租戶字段不為空的情況下,SQL又自動添加一次租戶查詢條件,因此導致了報錯:

如何使用Mybatis-plus實現多租戶架構

我們可以修改復制Bean語句,手動忽略租戶id字段,這里使用的是hutool的BeanUtil工具類,可以添加忽略字段。

BeanUtil.copyProperties(user,userSnapshot,"tenantId");

在忽略了租戶id的拷貝后,查詢可以正常執行。

最后,再來看一下對聯表查詢的支持,首先看一下包含子查詢的SQL:

@Select("select * from user where id in (select id from user_snapshot)")
List<User> selectSnapshot();

查看執行結果,可以看見,在子查詢的內部也自動添加的租戶查詢條件:

如何使用Mybatis-plus實現多租戶架構

再來看一下使用Join進行聯表查詢:

@Select("select u.* from user u left join user_snapshot us on u.id=us.id")
List<User> selectSnapshot();

同樣,會在左右兩張表上都添加租戶的過濾條件:

如何使用Mybatis-plus實現多租戶架構

再看一下不使用Join的普通聯表查詢:

@Select("select u.* from user u ,user_snapshot us,dept d where u.id=us.id and d.id is not null")
List<User> selectSnapshot();

如何使用Mybatis-plus實現多租戶架構

查看執行結果,可以看見在這種情況下,只在FROM關鍵字后面的第一張表上添加了租戶的過濾條件,因此如果使用這種查詢方式,需要額外注意,用戶需要手動在SQL語句中添加租戶過濾。

感謝各位的閱讀!關于“如何使用Mybatis-plus實現多租戶架構”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

南京市| 邯郸市| 聂荣县| 乌拉特前旗| 阿鲁科尔沁旗| 龙里县| 德钦县| 高雄县| 高平市| 乐亭县| 那曲县| 云和县| 辽阳市| 隆德县| 离岛区| 崇州市| 萝北县| 平塘县| 精河县| 奉贤区| 扶沟县| 湘乡市| 图片| 临江市| 新田县| 荔波县| 钟山县| 汉中市| 高台县| 石景山区| 奈曼旗| 东阳市| 延边| 汕尾市| 渭南市| 安化县| 彩票| 曲水县| 吉安市| 吉木乃县| 汝阳县|