最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于Mybatis-plus實(shí)現(xiàn)多租戶架構(gòu)的全過(guò)程

 更新時(shí)間:2022年02月09日 15:53:38   作者:碼農(nóng)參上  
多租戶是一種軟件架構(gòu)技術(shù),在多用戶的環(huán)境下,共有同一套系統(tǒng),并且要注意數(shù)據(jù)之間的隔離性,下面這篇文章主要給大家介紹了關(guān)于基于Mybatis-plus實(shí)現(xiàn)多租戶架構(gòu)的相關(guān)資料,需要的朋友可以參考下

多租戶(Multi-Tenant)是SaaS中的一個(gè)重要概念,它是一種軟件架構(gòu)技術(shù),在多個(gè)租戶的環(huán)境下,共享同一套系統(tǒng)實(shí)例,并且租戶之間的數(shù)據(jù)具有隔離性,也就是說(shuō)一個(gè)租戶不能去訪問(wèn)其他租戶的數(shù)據(jù)?;诓煌母綦x級(jí)別,通常具有下面三種實(shí)現(xiàn)方案:

1、每個(gè)租戶使用獨(dú)立DataBase,隔離級(jí)別高,性能好,但成本大

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

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

Mybatis-plus在第3層隔離級(jí)別上,提供了基于分頁(yè)插件的多租戶的解決方案,我們對(duì)此來(lái)進(jìn)行介紹。在正式開始前,首先做好準(zhǔn)備工作創(chuàng)建兩張表,在基礎(chǔ)字段后都添加租戶字段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`)
)

在項(xiàng)目中導(dǎo)入需要的依賴:

<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;
    }
}

這里主要實(shí)現(xiàn)的功能:

  • 創(chuàng)建SQL解析器集合

  • 創(chuàng)建租戶SQL解析器

  • 設(shè)置租戶處理器,具體處理租戶邏輯

這里暫時(shí)把租戶的id固定寫成3,來(lái)進(jìn)行測(cè)試。測(cè)試執(zhí)行全表語(yǔ)句:

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

使用插件解析執(zhí)行的SQL語(yǔ)句,可以看到自動(dòng)在查詢條件后加上了租戶過(guò)濾條件:

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

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

前端在發(fā)起http請(qǐng)求時(shí),在Header中加入tenantId字段,后端在處理器中獲取后,設(shè)置為當(dāng)前這次請(qǐng)求的租戶過(guò)濾條件。

如果是基于請(qǐng)求頭攜帶租戶信息的情況,那么在使用中可能會(huì)遇到一個(gè)坑,如果當(dāng)使用多線程的時(shí)候,新開啟的異步線程并不會(huì)自動(dòng)攜帶當(dāng)前線程的Request請(qǐng)求。

@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;
}

執(zhí)行上面的方法,可以看出是獲取不到當(dāng)前的Request請(qǐng)求的,因此無(wú)法獲得租戶id,會(huì)導(dǎo)致后續(xù)報(bào)錯(cuò)空指針異常:

修改的話也非常簡(jiǎn)單,開啟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;
}

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

那么,有的小伙伴可能要問(wèn)了,在業(yè)務(wù)中并不是所有的查詢都需要過(guò)濾租戶條件啊,針對(duì)這種情況,有兩種方式來(lái)進(jìn)行處理。

1、如果整張表的所有SQL操作都不需要針對(duì)租戶進(jìn)行操作,那么就對(duì)表進(jìn)行過(guò)濾,修改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表的所有查詢都不進(jìn)行過(guò)濾:

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

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

或在分頁(yè)攔截器中指定需要過(guò)濾的方法:

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

上面這兩種方式實(shí)現(xiàn)的功能相同,但是如果需要過(guò)濾的SQL語(yǔ)句很多,那么第二種方式配置起來(lái)會(huì)比較麻煩,因此建議通過(guò)注解的方式進(jìn)行過(guò)濾。

除此之外,還有一個(gè)比較容易踩的坑就是在復(fù)制Bean時(shí),不要復(fù)制租戶id字段,否則會(huì)導(dǎo)致SQL語(yǔ)句報(bào)錯(cuò):

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);
}

查看報(bào)錯(cuò)可以看出,本身Bean的租戶字段不為空的情況下,SQL又自動(dòng)添加一次租戶查詢條件,因此導(dǎo)致了報(bào)錯(cuò):

我們可以修改復(fù)制Bean語(yǔ)句,手動(dòng)忽略租戶id字段,這里使用的是hutool的BeanUtil工具類,可以添加忽略字段。

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

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

最后,再來(lái)看一下對(duì)聯(lián)表查詢的支持,首先看一下包含子查詢的SQL:

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

查看執(zhí)行結(jié)果,可以看見,在子查詢的內(nèi)部也自動(dòng)添加的租戶查詢條件:

再來(lái)看一下使用Join進(jìn)行聯(lián)表查詢:

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

同樣,會(huì)在左右兩張表上都添加租戶的過(guò)濾條件:

再看一下不使用Join的普通聯(lián)表查詢:

@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();

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

總結(jié)

到此這篇關(guān)于基于Mybatis-plus實(shí)現(xiàn)多租戶架構(gòu)的文章就介紹到這了,更多相關(guān)Mybatis-plus實(shí)現(xiàn)多租戶架構(gòu)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java求兩集合中元素交集的四種方法對(duì)比分析

    Java求兩集合中元素交集的四種方法對(duì)比分析

    這篇文章主要介紹了Java求兩集合中元素交集的四種方法對(duì)比總結(jié),四種求集合中元素交集的方法,按照在處理大量數(shù)據(jù)的效率來(lái)看,使用map集合的特性的方法效率最高,之后是使用Java流的方法,其次是使用for循環(huán)和迭代器的方法,需要的朋友可以參考下
    2023-05-05
  • Java之String、StringBuffer、StringBuilder的區(qū)別分析

    Java之String、StringBuffer、StringBuilder的區(qū)別分析

    今天搞安卓在看書的時(shí)候遇到了StringBuilder這個(gè)類型的東東,有點(diǎn)小迷,不知道它跟string、stringbuffer的關(guān)系式怎么樣的,趕快查閱相關(guān)資料,了解了個(gè)大概,拿出來(lái)分享一下
    2012-11-11
  • Spring Boot事務(wù)配置詳解

    Spring Boot事務(wù)配置詳解

    這篇文章主要介紹了Spring Boot事務(wù)配置詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • SpringBoot定時(shí)任務(wù)兩種(Spring Schedule 與 Quartz 整合 )實(shí)現(xiàn)方法

    SpringBoot定時(shí)任務(wù)兩種(Spring Schedule 與 Quartz 整合 )實(shí)現(xiàn)方法

    本篇文章主要介紹了SpringBoot定時(shí)任務(wù)兩種(Spring Schedule 與 Quartz 整合 )實(shí)現(xiàn)方法,詳細(xì)的介紹了Spring Schedule 與 Quartz 整合的兩種方法,有興趣的可以了解一下。
    2017-03-03
  • java實(shí)現(xiàn)兩臺(tái)服務(wù)器間文件復(fù)制的方法

    java實(shí)現(xiàn)兩臺(tái)服務(wù)器間文件復(fù)制的方法

    這篇文章主要介紹了java實(shí)現(xiàn)兩臺(tái)服務(wù)器間文件復(fù)制的方法,是對(duì)單臺(tái)服務(wù)器上文件復(fù)制功能的升級(jí)與改進(jìn),具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-01-01
  • Springboot SseEmitter流式輸出的實(shí)現(xiàn)代碼

    Springboot SseEmitter流式輸出的實(shí)現(xiàn)代碼

    本文介紹了Spring Boot中使用SseEmitter實(shí)現(xiàn)流式輸出的原理和示例代碼,通過(guò)SseEmitter,可以實(shí)現(xiàn)客戶端和服務(wù)器之間的實(shí)時(shí)通信,服務(wù)器可以分塊發(fā)送數(shù)據(jù),而客戶端可以實(shí)時(shí)接收和處理這些數(shù)據(jù),,感興趣的朋友一起看看吧
    2025-03-03
  • Java之網(wǎng)絡(luò)編程案例講解

    Java之網(wǎng)絡(luò)編程案例講解

    這篇文章主要介紹了Java之網(wǎng)絡(luò)編程案例講解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-07-07
  • ant打包jar文件腳本分享

    ant打包jar文件腳本分享

    本文介紹的ant腳本是用來(lái)打包jar文件,做完JAVA應(yīng)用一定會(huì)用到這個(gè),需要的朋友可以參考下
    2014-03-03
  • Java實(shí)現(xiàn)線程按序交替執(zhí)行的方法詳解

    Java實(shí)現(xiàn)線程按序交替執(zhí)行的方法詳解

    這篇文章主要為大家詳細(xì)介紹了Java如何實(shí)現(xiàn)線程按序交替執(zhí)行,文中的示例代碼講解詳細(xì),對(duì)我們了解線程有一定幫助,需要的可以參考一下
    2022-10-10
  • Java ThreadLocal有什么作用你知道嗎

    Java ThreadLocal有什么作用你知道嗎

    這篇文章主要為大家詳細(xì)介紹了java ThreadLocal的作用,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助
    2021-09-09

最新評(píng)論

察雅县| 安徽省| 通州区| 海林市| 兴国县| 宜兴市| 建阳市| 余庆县| 尚志市| 怀远县| 大荔县| 泸水县| 永登县| 密山市| 永靖县| 尚义县| 买车| 鄄城县| 怀安县| 鸡西市| 微山县| 渭南市| 宁津县| 高碑店市| 滦平县| 景东| 昭平县| 富锦市| 健康| 遵义县| 额济纳旗| 肃宁县| 友谊县| 海南省| 巩留县| 手游| 友谊县| 镇沅| 大宁县| 泌阳县| 东港市|