基於Mybatis-plus多租戶實現方案

點擊上方藍字關注我們

一、引言


多租戶的概念:

一文帶您瞭解軟件多租戶技術架構


二、具體實現


這裏採用方案三,即共享數據庫,共享數據架構,因爲這種方案服務器成本最低,但是提高了開發成本。

實現架構邏輯



Mybatis-plus實現多租戶方案   

Mybatis-plus就提供了一種多租戶的解決方案,實現方式是基於分頁插件(攔截器)進行實現的;

第一步:在應用添加維護一張tenant(租戶表),在需要進行隔離的數據表上新增租戶id;
第二步:實現TenantHandler接口並實現它的方法:
public interface TenantHandler {

/**
* 獲取租戶 ID 值表達式,支持多個 ID 條件查詢
* <p>
* 支持自定義表達式,比如:tenant_id in (1,2) @since 2019-8-2
*
* @param where 參數 true 表示爲 where 條件 false 表示爲 insert 或者 select 條件
* @return 租戶 ID 值表達式
*/

Expression getTenantId(boolean where);

/**
* 獲取租戶字段名
*
* @return 租戶字段名
*/

String getTenantIdColumn();

/**
* 根據表名判斷是否進行過濾
*
* @param tableName 表名
* @return 是否進行過濾, true:表示忽略,false:需要解析多租戶字段
*/

boolean doTableFilter(String tableName);
}

PreTenantHandler 實現 TenantHandler

@Slf4j
@Component
public class PreTenantHandler implements TenantHandler {

@Autowired
private PreTenantConfigProperties configProperties;

/**
* 租戶Id
*
* @return
*/

@Override
public Expression getTenantId(boolean where) {
//可以通過過濾器從請求中獲取對應租戶id
Long tenantId = PreTenantContextHolder.getCurrentTenantId();
log.debug("當前租戶爲{}", tenantId);
if (tenantId == null) {
return new NullValue();
}
return new LongValue(tenantId);
}
/**
* 租戶字段名
*
* @return
*/

@Override
public String getTenantIdColumn() {
return configProperties.getTenantIdColumn();
}

/**
* 根據表名判斷是否進行過濾
* 忽略掉一些表:如租戶表(sys_tenant)本身不需要執行這樣的處理
*
* @param tableName
* @return
*/

@Override
public boolean doTableFilter(String tableName) {
return configProperties.getIgnoreTenantTables().stream().anyMatch((e) -> e.equalsIgnoreCase(tableName));
}
}

第三步:配置mybatisPlus的分頁插件配置

@EnableTransactionManagement
@Configuration
@MapperScan({"com.xd.pre.**.mapper"})
public class MyBatisPlusConfig {

@Autowired
private PreTenantHandler preTenantHandler;

/**
* 分頁插件
*/

@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
List<ISqlParser> sqlParserList = new ArrayList<>();
// 攻擊 SQL 阻斷解析器、加入解析鏈
sqlParserList.add(new BlockAttackSqlParser());
// 多租戶攔截
TenantSqlParser tenantSqlParser = new TenantSqlParser();
tenantSqlParser.setTenantHandler(preTenantHandler);
sqlParserList.add(tenantSqlParser);
paginationInterceptor.setSqlParserList(sqlParserList);
return paginationInterceptor;
}
}
配置好之後,不管是查詢、新增、修改刪除方法,MP都會自動加上租戶ID的標識,測試如下:
    @Test
public void select(){
List<User> users = userMapper.selectList(Wrappers.<User>lambdaQuery().eq(User::getAge, 18));
users.forEach(System.out::println);
}

運行sql實例:

DEBUG==>  Preparing: SELECT id, login_name, name, password, 
email, salt, sex, age, phone, user_type, status,
organization_id, create_time, update_time, version,
tenant_id FROM sys_user
WHERE sys_user.tenant_id = '001' AND is_delete = '0' AND age = ?

注:特定SQL過濾

 如果在程序中,有部分SQL不需要加上租戶ID的表示,需要過濾特定的sql,可以通過如下兩種方式:

方式一:在配置分頁插件中加上配置ISqlParserFilter解析器,如果配置SQL很多,比較麻煩,不建議;
paginationInterceptor.setSqlParserFilter(new ISqlParserFilter() {
@Override
public boolean doFilter(MetaObject metaObject) {
MappedStatement ms = SqlParserHelper.getMappedStatement(metaObject);
// 對應Mapper、dao中的方法
if("com.example.demo.mapper.UserMapper.selectList".equals(ms.getId())){
return true;
}
return false;
}
});
方式二:通過租戶註解 @SqlParser(filter = true) 的形式,目前只能作用於Mapper的方法上:
public interface UserMapper extends BaseMapper<User> {

/**
* 自定Wrapper修改
*
* @param userWrapper 條件構造器
* @param user 修改的對象參數
* @return
*/

@SqlParser(filter = true)
int updateByMyWrapper(@Param(Constants.WRAPPER) Wrapper<User> userWrapper, @Param("user") User user);

}
注:

SpringBoot 集成 Redis 實現消息隊列


ZooKeeper典型應用場景


戳這兒


本文分享自微信公衆號 - 俠夢的開發筆記(xmdevnote)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章