<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
多租戶(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語句,可以看到自動在查詢條件後加上了租戶過濾條件:
那麼在實際的專案中,怎麼將租戶資訊傳給租戶處理器呢,根據情況我們可以從快取或者請求頭中獲取,以從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,會導致後續報錯空指標異常:
修改的話也非常簡單,開啟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表的所有查詢都不進行過濾:
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又自動新增一次租戶查詢條件,因此導致了報錯:
我們可以修改複製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();
檢視執行結果,可以看見,在子查詢的內部也自動新增的租戶查詢條件:
再來看一下使用Join進行聯表查詢:
@Select("select u.* from user u left join user_snapshot us on u.id=us.id") List<User> selectSnapshot();
同樣,會在左右兩張表上都新增租戶的過濾條件:
再看一下不使用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();
檢視執行結果,可以看見在這種情況下,只在FROM關鍵字後面的第一張表上新增了租戶的過濾條件,因此如果使用這種查詢方式,需要額外注意,使用者需要手動在SQL語句中新增租戶過濾。
總結
到此這篇關於基於Mybatis-plus實現多租戶架構的文章就介紹到這了,更多相關Mybatis-plus實現多租戶架構內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45