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

溫馨提示×

溫馨提示×

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

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

Springboot多租戶SaaS如何搭建

發布時間:2022-06-15 16:17:35 來源:億速云 閱讀:483 作者:iii 欄目:開發技術

這篇文章主要介紹“Springboot多租戶SaaS如何搭建”,在日常操作中,相信很多人在Springboot多租戶SaaS如何搭建問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Springboot多租戶SaaS如何搭建”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

技術框架

springboot版本為2.3.4.RELEASE

持久層采用JPA

租戶Model設計

因為saas應用所有租戶都使用同個服務和數據庫,為隔離好租戶數據,這里創建一個BaseSaasEntity

public abstract class BaseSaasEntity {
    @JsonIgnore
    @Column(nullable = false, updatable = false)
    protected Long tenantId;
    }

里面只有一個字段tenantId,對應的就是租戶Id,所有租戶業務entity都繼承這個父類。最后通過tenantId來區分數據是哪個租戶。

sql租戶數據過濾

按往常,表建好就該接著對應的模塊的CURD。但saas應用最基本的要求就是租戶數據隔離,就是公司B的人不能看到公司A的數據,怎么過濾呢,這里上面我們建立的BaseSaasEntity就起作用了,通過區分當前請求是來自那個公司后,在所有tenant業務sql中加上where tenant=?就實現了租戶數據過濾。

Hibernate filter

如果讓我們在業務中都去加上租戶sql過濾代碼,那工作量不僅大,而且出錯的概率也很大。理想是過濾sql拼接統一放在一起處理,在租戶業務接口開啟sql過濾。因為JPA是有hibernate實現的,這里我們可以利用hibernate的一些功能

@MappedSuperclass
@Data
@FilterDef(name = "tenantFilter", parameters = {@ParamDef(name = "tenantId", type = "long")})
@Filter(condition = "tenant_id=:tenantId", name = "tenantFilter")
public abstract class BaseSaasEntity {
    @JsonIgnore
    @Column(nullable = false, updatable = false)
    protected Long tenantId;


    @PrePersist
    public void onPrePersist() {
        if (getTenantId() != null) {
            return;
        }
        Long tenantId = TenantContext.getTenantId();
        Check.notNull(tenantId, "租戶不存在");
        setTenantId(tenantId);
    }
}

Hibernate3 提供了一種創新的方式來處理具有“顯性(visibility)”規則的數據,那就是使用Hibernate 過濾器。Hibernate 過濾器是全局有效的、具有名字、可以帶參數的過濾器,對于某個特定的 Hibernate session 您可以選擇是否啟用(或禁用)某個過濾器。

這里我們通過@FilterDef和@Filter預先定義了一個sql過濾條件。然后通過一個@TenantFilter注解來標識接口需要進行數據過濾

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Transactional
public @interface TenantFilter {

    boolean readOnly() default true;

}

可以看出這個接口是放在方法上,對應的就是Controller層。@Transactional增加事務注解的意義是因為激活hibernate filter必須要開啟事務,這里默認是只讀事務。 最后定義一個切面來激活filter

@Aspect
@Slf4j
@RequiredArgsConstructor
public class TenantSQLAspect {
    private static final String FILTER_NAME = "tenantFilter";
    private final EntityManager entityManager;
    @SneakyThrows
    @Around("@annotation(com.lvjusoft.njcommon.annotation.TenantFilter)")
    public Object aspect(ProceedingJoinPoint joinPoint) {
        Session session = entityManager.unwrap(Session.class);
        try {
            Long tenantId = TenantContext.getTenantId();
            Check.notNull(tenantId, "租戶不存在");
            session.enableFilter(FILTER_NAME).setParameter("tenantId", tenantId);
            return joinPoint.proceed();
        } finally {
            session.disableFilter(FILTER_NAME);
        }
    }
}

這里切面的對象就是剛才自定義的@TenantFilter注解,在方法執行前拿到當前租戶id,開啟filter,這樣租戶數據隔離就大功告成了,只需要在租戶業務接口上增加@TenantFilter注解即可, 開發只用關心業務代碼。上圖中的TenantContext是當前線程租戶context,通過和前端約定好,接口請求頭中增加租戶id,服務端利用攔截器把獲取到的租戶id緩存在ThreadLocal中

public class IdentityInterceptor extends HandlerInterceptorAdapter {
    public IdentityInterceptor() {
        log.info("IdentityInterceptor init");
    }
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String token = request.getHeader(AuthConstant.USER_TOKEN_HEADER_NAME);
        UserContext.setToken(token);
        String tenantId = request.getHeader(AuthConstant.TENANT_TOKEN_HEADER_NAME);
        TenantContext.setTenantUUId(tenantId);
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        UserContext.clear();
        TenantContext.clear();
    }
}

分庫

隨著租戶數量的增加,mysql單庫單表的數據肯定會達到瓶頸,這里只采用分庫的手段。利用多數據源,將租戶和數據源進行多對一的映射。

public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
    private Map<Object, Object> targetDataSources;
    public DynamicRoutingDataSource() {
        targetDataSources =new HashMap<>();
        DruidDataSource druidDataSource1 = new DruidDataSource();
        druidDataSource1.setUsername("username");
        druidDataSource1.setPassword("password");
        druidDataSource1.setUrl("jdbc:mysql://localhost:3306/db?useSSL=false&useUnicode=true&characterEncoding=utf-8");
        targetDataSources.put("db1",druidDataSource1);
        
        DruidDataSource druidDataSource2 = new DruidDataSource();
        druidDataSource2.setUsername("username");
        druidDataSource2.setPassword("password");
        druidDataSource2.setUrl("jdbc:mysql://localhost:3306/db?useSSL=false&useUnicode=true&characterEncoding=utf-8");
        targetDataSources.put("db2",druidDataSource1);
        
        this.targetDataSources = targetDataSources;
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }
    public void addDataSource(String key, DataSource dataSource) {
        if (targetDataSources.containsKey(key)) {
            throw new IllegalArgumentException("dataSource key exist");
        }
        targetDataSources.put(key, dataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContext.getSource();
    }
}

通過實現AbstractRoutingDataSource來聲明一個動態路由數據源,在框架使用datesource前,spring會調用determineCurrentLookupKey()方法來確定使用哪個數據源。這里的DataSourceContext和上面的TenantContext類似,在攔截器中獲取到tenantInfo后,找到當前租戶對應的數據源key并設置在ThreadLocal中。

到此,關于“Springboot多租戶SaaS如何搭建”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

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

AI

株洲市| 正蓝旗| 习水县| 桦南县| 尼玛县| 台北市| 天气| 广河县| 汕尾市| 蓬安县| 蓬莱市| 玉田县| 卓尼县| 灵宝市| 高清| 韶关市| 尤溪县| 扎鲁特旗| 黔西县| 弥渡县| 敖汉旗| 平江县| 香港| 淳化县| 金乡县| 吕梁市| 蒙城县| 峨眉山市| 扎兰屯市| 九江市| 永定县| 长汀县| 珲春市| 横峰县| 延寿县| 奉化市| 中阳县| 克拉玛依市| 什邡市| 焦作市| 东辽县|