您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關(guān)如何使用Mybatis-plus實現(xiàn)多租戶架構(gòu)的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
多租戶(Multi-Tenant)是SaaS中的一個重要概念,它是一種軟件架構(gòu)技術(shù),在多個租戶的環(huán)境下,共享同一套系統(tǒng)實例,并且租戶之間的數(shù)據(jù)具有隔離性,也就是說一個租戶不能去訪問其他租戶的數(shù)據(jù)?;诓煌母綦x級別,通常具有下面三種實現(xiàn)方案:
1、每個租戶使用獨立DataBase,隔離級別高,性能好,但成本大
2、租戶之間共享DataBase,使用獨立的Schema
3、租戶之間共享Schema,在表上添加租戶字段,共享數(shù)據(jù)程度最高,隔離級別最低。
Mybatis-plus在第3層隔離級別上,提供了基于分頁插件的多租戶的解決方案,我們對此來進行介紹。在正式開始前,首先做好準備工作創(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`) )
在項目中導(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; } }
這里主要實現(xiàn)的功能:
創(chuàng)建SQL解析器集合
創(chuàng)建租戶SQL解析器
設(shè)置租戶處理器,具體處理租戶邏輯
這里暫時把租戶的id固定寫成3,來進行測試。測試執(zhí)行全表語句:
public List<User> getUserList() { return userMapper.selectList(new LambdaQueryWrapper<User>().isNotNull(User::getId)); }
使用插件解析執(zhí)行的SQL語句,可以看到自動在查詢條件后加上了租戶過濾條件:
那么在實際的項目中,怎么將租戶信息傳給租戶處理器呢,根據(jù)情況我們可以從緩存或者請求頭中獲取,以從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); }
前端在發(fā)起http請求時,在Header中加入tenantId字段,后端在處理器中獲取后,設(shè)置為當(dāng)前這次請求的租戶過濾條件。
如果是基于請求頭攜帶租戶信息的情況,那么在使用中可能會遇到一個坑,如果當(dāng)使用多線程的時候,新開啟的異步線程并不會自動攜帶當(dāng)前線程的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; }
執(zhí)行上面的方法,可以看出是獲取不到當(dāng)前的Request請求的,因此無法獲得租戶id,會導(dǎo)致后續(xù)報錯空指針異常:
修改的話也非常簡單,開啟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; }
這樣修改后,在異步線程中也能正常的獲取租戶信息了。
那么,有的小伙伴可能要問了,在業(yè)務(wù)中并不是所有的查詢都需要過濾租戶條件啊,針對這種情況,有兩種方式來進行處理。
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表的所有查詢都不進行過濾:
2、如果有一些特定的SQL語句不想被執(zhí)行租戶過濾,可以通過@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); // 對應(yīng)Mapper、dao中的方法 if("com.cn.tenant.dao.UserMapper.selectUserByPhone".equals(ms.getId())){ return true; } return false; }); ... }
上面這兩種方式實現(xiàn)的功能相同,但是如果需要過濾的SQL語句很多,那么第二種方式配置起來會比較麻煩,因此建議通過注解的方式進行過濾。
除此之外,還有一個比較容易踩的坑就是在復(fù)制Bean時,不要復(fù)制租戶id字段,否則會導(dǎo)致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又自動添加一次租戶查詢條件,因此導(dǎo)致了報錯:
我們可以修改復(fù)制Bean語句,手動忽略租戶id字段,這里使用的是hutool的BeanUtil工具類,可以添加忽略字段。
BeanUtil.copyProperties(user,userSnapshot,"tenantId");
在忽略了租戶id的拷貝后,查詢可以正常執(zhí)行。
最后,再來看一下對聯(lián)表查詢的支持,首先看一下包含子查詢的SQL:
@Select("select * from user where id in (select id from user_snapshot)") List<User> selectSnapshot();
查看執(zhí)行結(jié)果,可以看見,在子查詢的內(nèi)部也自動添加的租戶查詢條件:
再來看一下使用Join進行聯(lián)表查詢:
@Select("select u.* from user u left join user_snapshot us on u.id=us.id") List<User> selectSnapshot();
同樣,會在左右兩張表上都添加租戶的過濾條件:
再看一下不使用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)鍵字后面的第一張表上添加了租戶的過濾條件,因此如果使用這種查詢方式,需要額外注意,用戶需要手動在SQL語句中添加租戶過濾。
感謝各位的閱讀!關(guān)于“如何使用Mybatis-plus實現(xiàn)多租戶架構(gòu)”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。