偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

干掉單體!用 Spring Boot + PostgreSQL 搞定多租戶架構(gòu)

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

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

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

多租戶實現(xiàn)的三種常見方式

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

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

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

Schema-per-Tenant 思路概覽

實現(xiàn)思路大致如下:

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

這樣,每個請求都會自動路由到對應(yīng)的 Schema,保證數(shù)據(jù)邏輯隔離。

項目依賴

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

  • spring-boot-starter-web —— 構(gòu)建 REST API
  • spring-boot-starter-data-jpa —— Hibernate ORM 支持
  • postgresql —— PostgreSQL 驅(qū)動
  • slf4j —— 日志框架

核心代碼實現(xiàn)

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

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

作用: 實現(xiàn) Hibernate 的 CurrentTenantIdentifierResolver,根據(jù)上下文獲取當前租戶。

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

作用: 在獲取連接時動態(tài)切換 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

數(shù)據(jù)庫準備:

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;

動態(tài)創(chuàng)建租戶 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 發(fā)起請求時,帶上 Header:

X-TenantID: tenanta

即可將數(shù)據(jù)寫入 tenanta Schema,切換成 tenantb 就能實現(xiàn)隔離存儲。

結(jié)論

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

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

未來可以繼續(xù)在此基礎(chǔ)上增強:

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

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

責(zé)任編輯:武曉燕 來源: 路條編程
相關(guān)推薦

2020-11-09 14:03:51

Spring BootMaven遷移

2020-05-14 18:04:20

Spring BootSaaS平臺

2025-06-26 02:22:00

Spring接口國際化

2023-11-06 08:26:11

Spring微服務(wù)架構(gòu)

2020-09-15 07:00:00

SaaS架構(gòu)架構(gòu)

2025-06-26 01:10:00

服務(wù)定位解析器Spring

2022-02-16 19:42:25

Spring配置開發(fā)

2024-08-09 08:52:26

2019-04-25 14:25:24

Spring Bootif elseJava

2025-01-09 14:39:40

2025-03-12 14:09:56

2025-05-20 03:00:00

2024-05-28 08:17:54

2025-03-03 08:49:59

2022-03-14 19:40:40

PostgreSQL多租戶應(yīng)用程序Citus

2022-01-12 17:39:16

Spring多租戶數(shù)據(jù)

2020-11-05 10:40:07

Spring Boot多模塊Java

2020-12-18 07:33:20

SpringSchedule組件

2025-07-08 08:20:39

2025-03-03 08:00:00

SpringBootEasyExcel數(shù)據(jù)導(dǎo)出
點贊
收藏

51CTO技術(shù)棧公眾號