精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

干掉單體!用 Spring Boot + PostgreSQL 搞定多租戶架構

開發 架構
通過 Spring Boot + PostgreSQL,我們成功構建了一個 Schema-per-Tenant 的多租戶架構。? 這種方式兼顧了性能和隔離性,既避免了數據庫級方案的高昂成本,又優于表字段區分的低隔離模式。

在現代 SaaS 系統中,多租戶架構是支撐平臺高效運行的關鍵。傳統的單體數據庫設計一旦用戶量暴增,往往難以支撐隔離性、安全性和擴展性的需求。相比之下,多租戶架構讓不同客戶的數據邏輯上分隔開,既能節省成本,又能提升靈活性。

本文將帶你從零開始,在 Spring Boot + PostgreSQL 項目中實現 Schema-per-Tenant 的多租戶模式。我們會完整走通:依賴配置、核心代碼、數據庫建模和測試驗證,最后還會額外實現一個 動態創建租戶的服務。

多租戶實現的三種常見方式

在落地前,先快速對比一下三種主流多租戶實現思路:

  • 數據庫級(Database-per-Tenant) 每個租戶獨立數據庫,隔離性最強,但資源開銷大,遷移和備份操作復雜。
  • Schema級(Schema-per-Tenant) 共享同一個數據庫,每個租戶的數據放在獨立的 Schema 下。隔離性與資源利用率之間取得平衡,是企業實踐的主流方案。
  • 表字段級(Discriminator Column) 所有租戶數據放在同一套表中,依靠 tenant_id 字段區分。實現簡單,但需要額外小心防止越權訪問。

本文聚焦于 Schema-per-Tenant 模式,這是在 PostgreSQL 下常見且高效的實現方式。

Schema-per-Tenant 思路概覽

實現思路大致如下:

  1. PostgreSQL 一個數據庫里存在多個 Schema(如 tenanta、tenantb)。
  2. Hibernate 配置成多租戶模式(SCHEMA)。
  3. 定義一個 自定義連接提供器,在連接獲取時執行 SET search_path TO <schema>,保證 SQL 跑到正確的 Schema。
  4. 通過 Servlet Filter 攔截 HTTP 請求,從請求頭讀取 X-TenantID,并放入 ThreadLocal 上下文供 Hibernate 使用。

這樣,每個請求都會自動路由到對應的 Schema,保證數據邏輯隔離。

項目依賴

項目使用 Gradle + Java 17,主要依賴如下:

  • spring-boot-starter-web —— 構建 REST API
  • spring-boot-starter-data-jpa —— Hibernate ORM 支持
  • postgresql —— PostgreSQL 驅動
  • slf4j —— 日志框架

核心代碼實現

下面我們逐個文件梳理核心代碼

src/main/java/com/icoderoad/multitenant/context/AppTenantContext.java

作用: 攔截 HTTP 請求,解析請求頭 X-TenantID,寫入 ThreadLocal 并集成 MDC 日志上下文。

package com.icoderoad.multitenant.context;


import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Objects;


@Component
public class AppTenantContext implements Filter {


    private static final String LOGGER_TENANT_ID = "tenant_id";
    public static final String TENANT_HEADER = "X-TenantID";
    private static final String DEFAULT_TENANT = "public";
    private static final ThreadLocal<String> currentTenant = new ThreadLocal<>();


    public static String getCurrentTenant() {
        return Objects.requireNonNullElse(currentTenant.get(), DEFAULT_TENANT);
    }


    public static void setCurrentTenant(String tenant) {
        MDC.put(LOGGER_TENANT_ID, tenant);
        currentTenant.set(tenant);
    }


    public static void clear() {
        MDC.clear();
        currentTenant.remove();
    }


    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        String tenant = req.getHeader(TENANT_HEADER);
        if (tenant != null) {
            setCurrentTenant(tenant);
        }
        chain.doFilter(request, response);
        clear();
    }
}

src/main/java/com/icoderoad/multitenant/resolver/CurrentTenantIdentifierResolverImpl.java

作用: 實現 Hibernate 的 CurrentTenantIdentifierResolver,根據上下文獲取當前租戶。

package com.icoderoad.multitenant.resolver;


import com.icoderoad.multitenant.context.AppTenantContext;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import java.util.Objects;


public class CurrentTenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver<String> {


    @Override
    public String resolveCurrentTenantIdentifier() {
        return Objects.requireNonNullElse(AppTenantContext.getCurrentTenant(), "public");
    }


    @Override
    public boolean validateExistingCurrentSessions() {
        return true;
    }
}

src/main/java/com/icoderoad/multitenant/provider/MultiTenantConnectionProviderImpl.java

作用: 在獲取連接時動態切換 Schema。

package com.icoderoad.multitenant.provider;


import org.hibernate.engine.jdbc.connections.spi.AbstractDataSourceBasedMultiTenantConnectionProviderImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;


import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;


@Component
public class MultiTenantConnectionProviderImpl extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl {


    private static final Logger logger = LoggerFactory.getLogger(MultiTenantConnectionProviderImpl.class);
    private final DataSource dataSource;


    public MultiTenantConnectionProviderImpl(DataSource dataSource) {
        this.dataSource = dataSource;
    }


    @Override
    protected DataSource selectAnyDataSource() {
        return dataSource;
    }


    @Override
    protected DataSource selectDataSource(Object tenantIdentifier) {
        return dataSource;
    }


    @Override
    public Connection getConnection(Object tenantIdentifier) throws SQLException {
        String tenantId = tenantIdentifier != null ? tenantIdentifier.toString() : "public";
        logger.info("Switching schema to {}", tenantId);
        Connection connection = getAnyConnection();
        try (Statement statement = connection.createStatement()) {
            statement.execute(String.format("SET search_path TO %s;", tenantId));
        }
        return connection;
    }


    @Override
    public void releaseConnection(Object tenantIdentifier, Connection connection) throws SQLException {
        try (Statement statement = connection.createStatement()) {
            statement.execute("SET search_path TO public;");
        }
        releaseAnyConnection(connection);
    }
}

src/main/java/com/icoderoad/multitenant/config/HibernateConfig.java

作用: 配置 Hibernate 的多租戶支持。

package com.icoderoad.multitenant.config;


import com.icoderoad.multitenant.provider.MultiTenantConnectionProviderImpl;
import com.icoderoad.multitenant.resolver.CurrentTenantIdentifierResolverImpl;
import org.hibernate.cfg.Environment;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;


import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;


@Configuration
public class HibernateConfig {


    private final JpaProperties jpaProperties;


    public HibernateConfig(JpaProperties jpaProperties) {
        this.jpaProperties = jpaProperties;
    }


    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        return new HibernateJpaVendorAdapter();
    }


    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            DataSource dataSource,
            MultiTenantConnectionProviderImpl multiTenantConnectionProvider) {


        Map<String, Object> props = new HashMap<>(jpaProperties.getProperties());
        props.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider);
        props.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, new CurrentTenantIdentifierResolverImpl());


        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource);
        em.setPackagesToScan("com.icoderoad.multitenant.entity");
        em.setJpaVendorAdapter(jpaVendorAdapter());
        em.setJpaPropertyMap(props);
        return em;
    }
}

配置文件 application.properties;

server.port=8082


spring.datasource.url=jdbc:postgresql://localhost:5432/testdb
spring.datasource.username=your_username
spring.datasource.password=your_password
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect


spring.jpa.properties.hibernate.multiTenancy=SCHEMA
spring.jpa.hibernate.ddl-auto=none
spring.jpa.show-sql=true

數據庫準備:

CREATE DATABASE testdb;
CREATE SCHEMA tenanta;
CREATE SCHEMA tenantb;


GRANT USAGE ON SCHEMA tenanta TO your_username;
GRANT USAGE ON SCHEMA tenantb TO your_username;

動態創建租戶 API;

//TenantService` & `TenantController
@Service
public class TenantService {
    @Autowired
    private JdbcTemplate jdbcTemplate;


    public void createTenant(String tenantName) {
        if (!tenantName.matches("^[a-zA-Z0-9_]+$")) {
            throw new IllegalArgumentException("Invalid tenant name.");
        }
        jdbcTemplate.execute("CREATE SCHEMA IF NOT EXISTS " + tenantName);
    }
}


@RestController
@RequestMapping("/tenants")
public class TenantController {
    @Autowired
    private TenantService tenantService;


    @PostMapping("/create")
    public String createTenant(@RequestParam("tenantName") String tenantName) {
        tenantService.createTenant(tenantName);
        return "Tenant " + tenantName + " created successfully";
    }
}

測試

在 Postman 發起請求時,帶上 Header:

X-TenantID: tenanta

即可將數據寫入 tenanta Schema,切換成 tenantb 就能實現隔離存儲。

結論

通過 Spring Boot + PostgreSQL,我們成功構建了一個 Schema-per-Tenant 的多租戶架構。 這種方式兼顧了性能和隔離性,既避免了數據庫級方案的高昂成本,又優于表字段區分的低隔離模式。

更進一步,我們還擴展了 動態創建租戶的能力,讓平臺具備了 SaaS 化的核心競爭力。

未來可以繼續在此基礎上增強:

  • 租戶生命周期管理(創建、禁用、刪除)
  • Schema 自動遷移與升級
  • 多租戶下的監控與審計

這樣,一個靈活、安全、可擴展的 多租戶 SaaS 平臺 才算真正搭建完成。

責任編輯:武曉燕 來源: 路條編程
相關推薦

2020-11-09 14:03:51

Spring BootMaven遷移

2020-05-14 18:04:20

Spring BootSaaS平臺

2023-11-06 08:26:11

Spring微服務架構

2025-06-26 02:22:00

Spring接口國際化

2020-09-15 07:00:00

SaaS架構架構

2025-06-26 01:10:00

服務定位解析器Spring

2022-02-16 19:42:25

Spring配置開發

2024-08-09 08:52:26

2019-04-25 14:25:24

Spring Bootif elseJava

2025-01-09 14:39:40

2025-05-20 03:00:00

2025-03-12 14:09:56

2024-05-28 08:17:54

2025-03-03 08:49:59

2022-03-14 19:40:40

PostgreSQL多租戶應用程序Citus

2022-01-12 17:39:16

Spring多租戶數據

2020-12-18 07:33:20

SpringSchedule組件

2022-06-06 08:42:04

spring-boo開發接口防盜刷

2024-05-31 14:04:18

2025-03-03 08:00:00

SpringBootEasyExcel數據導出
點贊
收藏

51CTO技術棧公眾號

成人情趣视频网站| free性欧美hd另类精品| 在线综合亚洲| 原创国产精品91| 亚洲精品在线视频播放| 成人高潮aa毛片免费| 久久久亚洲精品一区二区三区 | 在线观看日本黄色| 精品视频在线一区| 欧美性生交大片免费| 一道精品一区二区三区| 欧洲成人一区二区三区| 蜜臀久久99精品久久久久宅男| 欧美另类极品videosbestfree| 一本色道综合久久欧美日韩精品| 欧美网站免费| 欧美日韩一区二区在线 | 可以直接看的无码av| 欧美啪啪网站| 欧美日韩在线观看视频| 亚洲成人动漫在线| 男人天堂网在线| 国产999精品久久久久久| 国产精品美女网站| 日韩久久精品视频| 欧美成人综合| 日韩在线观看免费全集电视剧网站| 在线精品视频播放| 色综合久久久| 欧洲亚洲国产日韩| 成年人视频观看| 性爱视频在线播放| 亚洲欧洲国产日韩| 亚洲电影免费观看高清完整版在线| 亚洲性69xxxbbb| 亚洲一区精品视频在线观看| 台湾佬中文娱乐久久久| 午夜伊人狠狠久久| a级免费在线观看| av片在线观看永久免费| 国产精品色哟哟| 日本不卡二区| 蜜芽tv福利在线视频| 成人毛片在线观看| 国产91视觉| www.av日韩| 国产乱码精品一区二区三区五月婷| 国产女人18毛片水18精品| 亚洲成熟少妇视频在线观看| 亚洲一区二区三区四区五区午夜 | 超碰aⅴ人人做人人爽欧美| 亚洲黄色在线视频| 激情五月五月婷婷| 91精品国产91久久久久久青草| 中文字幕在线观看一区| 亚洲一区二区三区涩| av影片在线看| 国产精品区一区二区三区| 日本在线成人一区二区| 高清在线观看av| 国产精品青草综合久久久久99| 亚洲欧美国产不卡| 国产在线观看av| 亚洲激情图片一区| 无码专区aaaaaa免费视频| а√在线中文网新版地址在线| 亚洲国产成人porn| 免费看一级大黄情大片| 日本三级一区| 欧美综合天天夜夜久久| 天天综合网久久| 成人乱码手机视频| 欧美sm美女调教| 中文字幕免费高清视频| 亚洲国产最新| 色综合伊人色综合网站| 日韩高清dvd碟片| 欧美电影免费观看网站| aiai久久| 91麻豆精品国产91久久久久| 操人视频免费看| 99精品在免费线中文字幕网站一区| 精品久久久久久无| 野花社区视频在线观看| 成人午夜av| 欧美理论电影在线播放| 国产成人在线免费观看视频| 久久久久久久高潮| 成人激情视频在线播放| 欧美熟妇另类久久久久久不卡 | 丁香花高清在线观看完整版| 精品国产91久久久久久| 爆乳熟妇一区二区三区霸乳| 99久久久国产| 亚洲精品黄网在线观看| 最近日本中文字幕| 欧美wwwww| 97国产在线视频| 伊人免费在线观看| 大陆成人av片| 一区二区在线不卡| 玖玖在线播放| 欧美一级片免费看| 欧美图片一区二区| 欧美黄色一区| 国产成人精品久久亚洲高清不卡| 国产草草影院ccyycom| 久久久久久久久免费| 日韩欧美视频免费在线观看| 日本一区免费网站| 亚洲护士老师的毛茸茸最新章节| 麻豆视频免费在线播放| 性久久久久久| 成人免费视频视频在| freemovies性欧美| 粉嫩老牛aⅴ一区二区三区| 婷婷激情5月天| 国产麻豆精品久久| 欧美高清性猛交| 亚洲综合免费视频| 国产视频一区在线播放| 亚洲国产精品无码av| 欧洲精品久久久久毛片完整版| 爽好多水快深点欧美视频| 正在播放亚洲一区| 扒开jk护士狂揉免费| 激情久久五月| 亚洲影视九九影院在线观看| 在线免费黄色| 一本一本久久a久久精品综合麻豆| 激情成人在线观看| 日韩大片在线观看| 国产精品福利网| 日韩美女一级视频| 亚洲第一搞黄网站| 少妇搡bbbb搡bbb搡打电话| 亚洲第一天堂| 成人免费看吃奶视频网站| 国产福利在线视频| 色乱码一区二区三区88| 少妇特黄一区二区三区| 亚洲欧美一级二级三级| 成人国产精品久久久| 成人免费在线电影| 一本色道久久综合亚洲aⅴ蜜桃 | 欧美壮男野外gaytube| 欧美综合视频在线| 午夜一区二区三区视频| 制服丝袜在线第一页| 爱豆国产剧免费观看大全剧苏畅| 欧美成人免费全部网站| 尤物精品国产第一福利三区| 99久久久无码国产精品免费蜜柚| 久久视频一区二区| 欧在线一二三四区| 成人av动漫在线观看| 国产精品视频区1| 免费a级人成a大片在线观看| 欧美精品tushy高清| 91麻豆精品成人一区二区| 国产一区二区三区香蕉| 日本在线视频www色| aaa国产精品视频| 97成人精品视频在线观看| 亚洲欧洲视频在线观看| 欧美午夜精品久久久久久久| 日本xxxxxxxxx18| 久久国产福利国产秒拍| 在线观看18视频网站| 哺乳一区二区三区中文视频| 97视频色精品| 国产小视频免费在线网址| 色系网站成人免费| 久久久99999| 国产91精品免费| 免费在线观看亚洲视频| 精品久久视频| 亚洲在线一区二区| 欧美男人天堂| 最新中文字幕亚洲| 亚洲国产精品一| 日韩欧美在线观看| 日韩av手机在线免费观看| 熟女高潮一区二区三区| 亚洲视屏一区| 欧美日韩国产一二| 粉嫩一区二区三区在线观看| 久久免费福利视频| 91社区在线观看播放| 日韩一区二区视频| 在线能看的av| 亚洲免费观看高清完整版在线观看熊| 91九色蝌蚪porny| 日韩黄色免费电影| 日产精品久久久久久久蜜臀| 国产成人三级| 国产精品久久亚洲7777| 电影在线观看一区二区| 九九热这里只有精品免费看| 可以免费看污视频的网站在线| 欧美精品一二三四| 在线能看的av| 一区二区三区毛片| 国产综合精品久久久久成人av| 国产99久久久国产精品| 亚欧美在线观看| 99视频在线精品国自产拍免费观看| 亚洲人成影视在线观看| 青青一区二区| 亚洲伊人一本大道中文字幕| 午夜无码国产理论在线| 欧美高清videos高潮hd| 天天综合视频在线观看| 亚洲男人天堂手机在线| 好吊色一区二区| 欧美福利视频导航| 一级黄色在线观看| 亚洲v日本v欧美v久久精品| 91精品一区二区三区蜜桃| 久久综合九色欧美综合狠狠| 绯色av蜜臀vs少妇| 狠狠色2019综合网| 中文字幕第80页| 羞羞答答国产精品www一本| 国产精品va在线观看无码| 天天插综合网| 亚洲精品免费在线看| 亚洲品质自拍| 精品一区国产| 福利在线一区| 99re国产在线播放| 日韩在线网址| 亚洲专区国产精品| 国产午夜亚洲精品一级在线| 国产裸体写真av一区二区| 韩国三级一区| 国产成人中文字幕| 韩国美女久久| 国产成人精品一区二区| 欧美大片高清| 青草青草久热精品视频在线网站| 草草在线观看| 97精品国产91久久久久久| 成年男女免费视频网站不卡| 性欧美长视频免费观看不卡 | 午夜精品福利在线观看| 国产蜜臀一区二区打屁股调教| 久久99精品久久久久久青青91| dj大片免费在线观看| 欧美精品一区二区免费| 哥也色在线视频| 欧美精品在线免费播放| 青草视频在线免费直播| 欧美日韩国产二区| 国内老司机av在线| 97精品久久久中文字幕免费| 一级毛片久久久| 日本电影亚洲天堂| 成人一级视频| 成人久久18免费网站图片| 国产精品毛片aⅴ一区二区三区| 成人淫片在线看| 日韩中文字幕在线一区 | 欧美三级美国一级| 亚洲精品国产精品久久| 在线成人激情| 男人添女人下部高潮视频在观看| 国产视频一区欧美| 另类小说第一页| 韩国毛片一区二区三区| 成人免费播放视频| www.激情成人| 一级片久久久久| 亚洲天堂网中文字| 久久精品久久国产| 在线视频亚洲一区| 国产精品高潮呻吟AV无码| 精品国产网站在线观看| 四虎影视2018在线播放alocalhost| 精品性高朝久久久久久久| av在线免费观看网| 色综合91久久精品中文字幕 | 欧美日本网站| 欧美一区二区三区日韩| 无套内谢的新婚少妇国语播放| 亚洲天堂成人在线视频| 免费网站成人| 51久久精品夜色国产麻豆| 视频91a欧美| 国产精品久久久久免费| 日韩av片子| 日日摸日日碰夜夜爽无码| 美女mm1313爽爽久久久蜜臀| 免费欧美一级片| 久久久另类综合| 免费在线观看黄色av| 色婷婷国产精品| 国内老熟妇对白hdxxxx| 亚洲欧美三级在线| 91cn在线观看| 国产精品情侣自拍| 高潮按摩久久久久久av免费| 一区二区三区四区免费视频| 99精品99| 日本女人性视频| 日本一区二区动态图| www.av视频在线观看| 欧美日韩一级大片网址| 香蕉视频成人在线| 欧美日韩成人精品| 国产精品99久久久久久董美香 | 这里只有精品在线| 青青草av网站| 久久亚洲精品国产精品紫薇| 免费视频网站www| 欧美精品日日鲁夜夜添| 欧美男男同志| 国产+成+人+亚洲欧洲| 精品国产一区二区三区2021| 亚洲成人自拍| 免费视频久久| 国产又黄又粗又猛又爽的视频| 亚洲激情网站免费观看| 国产又黄又粗又硬| 中文字幕综合在线| 色成人免费网站| 久久综合久久久| 夜夜嗨av一区二区三区网站四季av| 国产又粗又猛大又黄又爽| 中文字幕亚洲视频| 亚洲av无码乱码国产精品fc2| 国产视频一区在线| 日韩精品美女| 精品乱子伦一区二区三区| 国产中文一区| 久久久久亚洲av无码专区首jn| 17c精品麻豆一区二区免费| 中文字幕在线网站| 在线播放国产一区二区三区| 日韩免费福利视频| 日本不卡二区| 免费在线观看视频一区| 一二三四在线观看视频| 欧美性猛交一区二区三区精品| 极品白浆推特女神在线观看| 欧美在线影院在线视频| 欧美国产极品| 午夜肉伦伦影院| 天堂网在线资源| 亚洲视频免费一区| 日本高清不卡一区二区三区视频 | 91在线高清| 国产精品永久免费视频| 欧美gayvideo| 深夜做爰性大片蜜桃| 一区二区三区中文在线观看| 亚洲黄色精品视频| 91精品国产乱码久久久久久久久 | 国产不卡在线视频| 国产乡下妇女做爰视频| 日韩av在线不卡| 三级成人在线| 亚洲免费不卡| 国产福利91精品| 97人人澡人人爽人人模亚洲| 日韩成人av网址| av成人免费看| 400部精品国偷自产在线观看| 成人avav影音| 最近中文字幕在线免费观看| 久久五月情影视| julia中文字幕一区二区99在线| 可以在线看的av网站| 久久九九久久九九| 国产理论片在线观看| 91av免费观看91av精品在线| av在线不卡免费观看| 亚洲第一区第二区第三区| 亚洲h动漫在线| 啊v在线视频| 99re资源| 久久五月激情| 91视频综合网| 亚洲美女av电影| 国产精品久久久久久久久久辛辛| 欧美又粗又长又爽做受| 久久精品一区八戒影视| av中文字幕免费在线观看| 欧美在线影院在线视频| 亚洲国产日韩欧美在线| 中文字幕av网址| 欧美一区二区三区免费| 自拍一区在线观看| 中国黄色录像片| 久久精品一区二区三区不卡牛牛| av男人天堂av| 国产精品久久一| 在线欧美三区| 国产福利视频网站| 亚洲社区在线观看|