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

SpringBoot多租戶系統(tǒng)的五種架構(gòu)設(shè)計(jì)方案

開發(fā) 前端
多租戶(Multi-tenancy)是一種軟件架構(gòu)模式,允許單個(gè)應(yīng)用實(shí)例服務(wù)于多個(gè)客戶(租戶),同時(shí)保持租戶數(shù)據(jù)的隔離性和安全性。通過合理的多租戶設(shè)計(jì),企業(yè)可以顯著降低運(yùn)維成本、提升資源利用率,并實(shí)現(xiàn)更高效的服務(wù)交付。

多租戶(Multi-tenancy)是一種軟件架構(gòu)模式,允許單個(gè)應(yīng)用實(shí)例服務(wù)于多個(gè)客戶(租戶),同時(shí)保持租戶數(shù)據(jù)的隔離性和安全性。

通過合理的多租戶設(shè)計(jì),企業(yè)可以顯著降低運(yùn)維成本、提升資源利用率,并實(shí)現(xiàn)更高效的服務(wù)交付。

本文將分享SpringBoot環(huán)境下實(shí)現(xiàn)多租戶系統(tǒng)的5種架構(gòu)設(shè)計(jì)方案

方案一:獨(dú)立數(shù)據(jù)庫模式

原理與特點(diǎn)

獨(dú)立數(shù)據(jù)庫模式為每個(gè)租戶提供完全獨(dú)立的數(shù)據(jù)庫實(shí)例,是隔離級(jí)別最高的多租戶方案。在這種模式下,租戶數(shù)據(jù)完全分離,甚至可以部署在不同的服務(wù)器上。

實(shí)現(xiàn)步驟

1、創(chuàng)建多數(shù)據(jù)源配置:為每個(gè)租戶配置獨(dú)立的數(shù)據(jù)源

@Configuration
public class MultiTenantDatabaseConfig {
    
    @Autowired
    private TenantDataSourceProperties properties;
    
    @Bean
    public DataSource dataSource() {
        AbstractRoutingDataSource multiTenantDataSource = new TenantAwareRoutingDataSource();
        
        Map<Object, Object> targetDataSources = new HashMap<>();
        
        // 為每個(gè)租戶創(chuàng)建數(shù)據(jù)源
        for (TenantDataSourceProperties.TenantProperties tenant : properties.getTenants()) {
            DataSource tenantDataSource = createDataSource(tenant);
            targetDataSources.put(tenant.getTenantId(), tenantDataSource);
        }
        
        multiTenantDataSource.setTargetDataSources(targetDataSources);
        return multiTenantDataSource;
    }
    
    private DataSource createDataSource(TenantDataSourceProperties.TenantProperties tenant) {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl(tenant.getUrl());
        dataSource.setUsername(tenant.getUsername());
        dataSource.setPassword(tenant.getPassword());
        dataSource.setDriverClassName(tenant.getDriverClassName());
        return dataSource;
    }
}

2、實(shí)現(xiàn)租戶感知的數(shù)據(jù)源路由

public class TenantAwareRoutingDataSource extends AbstractRoutingDataSource {
    
    @Override
    protected Object determineCurrentLookupKey() {
        return TenantContextHolder.getTenantId();
    }
}

3、租戶上下文管理

public classTenantContextHolder {
    
    privatestatic final ThreadLocal<String> CONTEXT = newThreadLocal<>();
    
    publicstaticvoidsetTenantId(String tenantId) {
        CONTEXT.set(tenantId);
    }
    
    publicstaticStringgetTenantId() {
        returnCONTEXT.get();
    }
    
    publicstaticvoidclear() {
        CONTEXT.remove();
    }
}

4、添加租戶識(shí)別攔截器

@Component
publicclassTenantIdentificationInterceptorimplementsHandlerInterceptor {
    
    @Override
    publicbooleanpreHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String tenantId = extractTenantId(request);
        if (tenantId != null) {
            TenantContextHolder.setTenantId(tenantId);
            returntrue;
        }
        
        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        returnfalse;
    }
    
    @Override
    publicvoidafterCompletion(HttpServletRequest request, HttpServletResponse response, 
                                Object handler, Exception ex) {
        TenantContextHolder.clear();
    }
    
    privateStringextractTenantId(HttpServletRequest request) {
        // 從請(qǐng)求頭中獲取租戶ID
        String tenantId = request.getHeader("X-TenantID");
        
        // 或者從子域名提取
        if (tenantId == null) {
            String host = request.getServerName();
            if (host.contains(".")) {
                tenantId = host.split("\.")[0];
            }
        }
        
        return tenantId;
    }
}

5、配置攔截器

@Configuration
publicclassWebConfigimplementsWebMvcConfigurer {
    
    @Autowired
    privateTenantIdentificationInterceptor tenantInterceptor;
    
    @Override
    publicvoidaddInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(tenantInterceptor)
                .addPathPatterns("/api/**");
    }
}

6、實(shí)現(xiàn)動(dòng)態(tài)租戶管理

@Entity
@Table(name = "tenant")
publicclassTenant {
    
    @Id
    privateString id;
    
    @Column(nullable = false)
    privateString name;
    
    @Column(nullable = false)
    privateString databaseUrl;
    
    @Column(nullable = false)
    privateString username;
    
    @Column(nullable = false)
    privateString password;
    
    @Column(nullable = false)
    privateString driverClassName;
    
    @Column
    privateboolean active = true;
    
    // getters and setters
}

@Repository
publicinterfaceTenantRepositoryextendsJpaRepository<Tenant, String> {
    List<Tenant> findByActive(boolean active);
}

@Service
publicclassTenantManagementService {
    
    @Autowired
    privateTenantRepository tenantRepository;
    
    @Autowired
    privateDataSource dataSource;
    
    @Autowired
    privateApplicationContext applicationContext;
    
    // 用ConcurrentHashMap存儲(chǔ)租戶數(shù)據(jù)源
    private final Map<String, DataSource> tenantDataSources = newConcurrentHashMap<>();
    
    @PostConstruct
    publicvoidinitializeTenants() {
        List<Tenant> activeTenants = tenantRepository.findByActive(true);
        for (Tenant tenant : activeTenants) {
            addTenant(tenant);
        }
    }
    
    publicvoidaddTenant(Tenant tenant) {
        // 創(chuàng)建新的數(shù)據(jù)源
        HikariDataSource dataSource = newHikariDataSource();
        dataSource.setJdbcUrl(tenant.getDatabaseUrl());
        dataSource.setUsername(tenant.getUsername());
        dataSource.setPassword(tenant.getPassword());
        dataSource.setDriverClassName(tenant.getDriverClassName());
        
        // 存儲(chǔ)數(shù)據(jù)源
        tenantDataSources.put(tenant.getId(), dataSource);
        
        // 更新路由數(shù)據(jù)源
        updateRoutingDataSource();
        
        // 保存租戶信息到數(shù)據(jù)庫
        tenantRepository.save(tenant);
    }
    
    publicvoidremoveTenant(String tenantId) {
        DataSource dataSource = tenantDataSources.remove(tenantId);
        if (dataSource != null && dataSource instanceofHikariDataSource) {
            ((HikariDataSource) dataSource).close();
        }
        
        // 更新路由數(shù)據(jù)源
        updateRoutingDataSource();
        
        // 從數(shù)據(jù)庫移除租戶
        tenantRepository.deleteById(tenantId);
    }
    
    privatevoidupdateRoutingDataSource() {
        try {
            TenantAwareRoutingDataSource routingDataSource = (TenantAwareRoutingDataSource) dataSource;
            
            // 使用反射訪問AbstractRoutingDataSource的targetDataSources字段
            Field targetDataSourcesField = AbstractRoutingDataSource.class.getDeclaredField("targetDataSources");
            targetDataSourcesField.setAccessible(true);
            
            Map<Object, Object> targetDataSources = newHashMap<>(tenantDataSources);
            targetDataSourcesField.set(routingDataSource, targetDataSources);
            
            // 調(diào)用afterPropertiesSet初始化數(shù)據(jù)源
            routingDataSource.afterPropertiesSet();
        } catch (Exception e) {
            thrownewRuntimeException("Failed to update routing data source", e);
        }
    }
}

7、提供租戶管理API

@RestController
@RequestMapping("/admin/tenants")
public class TenantAdminController {
    
    @Autowired
    private TenantManagementService tenantService;
    
    @GetMapping
    public List<Tenant> getAllTenants() {
        returntenantService.getAllTenants();
    }
    
    @PostMapping
    publicResponseEntity<Tenant> createTenant(@RequestBody Tenant tenant) {
        tenantService.addTenant(tenant);
        returnResponseEntity.status(HttpStatus.CREATED).body(tenant);
    }
    
    @DeleteMapping("/{tenantId}")
    publicResponseEntity<Void> deleteTenant(@PathVariable String tenantId) {
        tenantService.removeTenant(tenantId);
        returnResponseEntity.noContent().build();
    }
}

優(yōu)缺點(diǎn)分析

優(yōu)點(diǎn):

? 數(shù)據(jù)隔離級(jí)別最高,安全性最佳

? 租戶可以使用不同的數(shù)據(jù)庫版本或類型

? 易于實(shí)現(xiàn)租戶特定的數(shù)據(jù)庫優(yōu)化

? 故障隔離,一個(gè)租戶的數(shù)據(jù)庫問題不影響其他租戶

? 便于獨(dú)立備份、恢復(fù)和遷移

缺點(diǎn):

? 資源利用率較低,成本較高

? 運(yùn)維復(fù)雜度高,需要管理多個(gè)數(shù)據(jù)庫實(shí)例

? 跨租戶查詢困難

? 每增加一個(gè)租戶需要?jiǎng)?chuàng)建新的數(shù)據(jù)庫實(shí)例

? 數(shù)據(jù)庫連接池管理復(fù)雜

適用場(chǎng)景

? 高要求的企業(yè)級(jí)SaaS應(yīng)用

? 租戶數(shù)量相對(duì)較少但數(shù)據(jù)量大的場(chǎng)景

? 租戶愿意支付更高費(fèi)用獲得更好隔離性的場(chǎng)景

方案二:共享數(shù)據(jù)庫,獨(dú)立Schema模式

原理與特點(diǎn)

在這種模式下,所有租戶共享同一個(gè)數(shù)據(jù)庫實(shí)例,但每個(gè)租戶擁有自己獨(dú)立的Schema(在PostgreSQL中)或數(shù)據(jù)庫(在MySQL中)。這種方式在資源共享和數(shù)據(jù)隔離之間取得了平衡。

實(shí)現(xiàn)步驟

1、創(chuàng)建租戶Schema配置

@Configuration
publicclassMultiTenantSchemaConfig {
    
    @Autowired
    privateDataSource dataSource;
    
    @Autowired
    privateTenantRepository tenantRepository;
    
    @PostConstruct
    publicvoidinitializeSchemas() {
        for (Tenant tenant : tenantRepository.findByActive(true)) {
            createSchemaIfNotExists(tenant.getSchemaName());
        }
    }
    
    privatevoidcreateSchemaIfNotExists(String schema) {
        try (Connection connection = dataSource.getConnection()) {
            // PostgreSQL語法,MySQL使用CREATE DATABASE IF NOT EXISTS
            String sql = "CREATE SCHEMA IF NOT EXISTS " + schema;
            try (Statement stmt = connection.createStatement()) {
                stmt.execute(sql);
            }
        } catch (SQLException e) {
            thrownewRuntimeException("Failed to create schema: " + schema, e);
        }
    }
}

2、租戶實(shí)體和存儲(chǔ)

@Entity
@Table(name = "tenant")
public class Tenant {
    
    @Id
    private String id;
    
    @Column(nullable = false)
    private String name;
    
    @Column(nullable = false, unique = true)
    private String schemaName;
    
    @Column
    private boolean active = true;
    
    // getters and setters
}

@Repository
public interface TenantRepository extends JpaRepository<Tenant, String> {
    List<Tenant> findByActive(boolean active);
    Optional<Tenant> findBySchemaName(String schemaName);
}

3、配置Hibernate多租戶支持

@Configuration
@EnableJpaRepositories(basePackages = "com.example.repository")
@EntityScan(basePackages = "com.example.entity")
public class JpaConfig {
    
    @Autowired
    private DataSource dataSource;
    
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            EntityManagerFactoryBuilder builder) {
        
        Map<String, Object> properties = newHashMap<>();
        properties.put(org.hibernate.cfg.Environment.MULTI_TENANT, 
                MultiTenancyStrategy.SCHEMA);
        properties.put(org.hibernate.cfg.Environment.MULTI_TENANT_CONNECTION_PROVIDER, 
                multiTenantConnectionProvider());
        properties.put(org.hibernate.cfg.Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, 
                currentTenantIdentifierResolver());
        
        // 其他Hibernate配置...
        
        returnbuilder
                .dataSource(dataSource)
                .packages("com.example.entity")
                .properties(properties)
                .build();
    }
    
    @Bean
    publicMultiTenantConnectionProvidermultiTenantConnectionProvider() {
        returnnewSchemaBasedMultiTenantConnectionProvider();
    }
    
    @Bean
    publicCurrentTenantIdentifierResolvercurrentTenantIdentifierResolver() {
        returnnewTenantSchemaIdentifierResolver();
    }
}

4、實(shí)現(xiàn)多租戶連接提供者

public classSchemaBasedMultiTenantConnectionProvider
        implementsMultiTenantConnectionProvider {
    
    privatestaticfinallongserialVersionUID=1L;
    
    @Autowired
    private DataSource dataSource;
    
    @Override
    public Connection getAnyConnection()throws SQLException {
        return dataSource.getConnection();
    }
    
    @Override
    publicvoidreleaseAnyConnection(Connection connection)throws SQLException {
        connection.close();
    }
    
    @Override
    public Connection getConnection(String tenantIdentifier)throws SQLException {
        finalConnectionconnection= getAnyConnection();
        try {
            // PostgreSQL語法,MySQL使用USE database_name
            connection.createStatement()
                    .execute(String.format("SET SCHEMA '%s'", tenantIdentifier));
        } catch (SQLException e) {
            thrownewHibernateException("Could not alter JDBC connection to schema ["
                    + tenantIdentifier + "]", e);
        }
        return connection;
    }
    
    @Override
    publicvoidreleaseConnection(String tenantIdentifier, Connection connection)
            throws SQLException {
        try {
            // 恢復(fù)到默認(rèn)Schema
            connection.createStatement().execute("SET SCHEMA 'public'");
        } catch (SQLException e) {
            // 忽略錯(cuò)誤,確保連接關(guān)閉
        }
        connection.close();
    }
    
    @Override
    publicbooleansupportsAggressiveRelease() {
        returnfalse;
    }
    
    @Override
    publicbooleanisUnwrappableAs(Class unwrapType) {
        returnfalse;
    }
    
    @Override
    public <T> T unwrap(Class<T> unwrapType) {
        returnnull;
    }
}

5、實(shí)現(xiàn)租戶標(biāo)識(shí)解析器

public classTenantSchemaIdentifierResolverimplementsCurrentTenantIdentifierResolver {
    
    privatestatic final StringDEFAULT_TENANT = "public";
    
    @Override
    publicStringresolveCurrentTenantIdentifier() {
        String tenantId = TenantContextHolder.getTenantId();
        return tenantId != null ? tenantId : DEFAULT_TENANT;
    }
    
    @Override
    publicbooleanvalidateExistingCurrentSessions() {
        returntrue;
    }
}

6、動(dòng)態(tài)租戶管理服務(wù)

@Service
publicclassTenantSchemaManagementService {
    
    @Autowired
    privateTenantRepository tenantRepository;
    
    @Autowired
    privateDataSource dataSource;
    
    @Autowired
    privateEntityManagerFactory entityManagerFactory;
    
    publicvoidcreateTenant(Tenant tenant) {
        // 1. 創(chuàng)建Schema
        createSchemaIfNotExists(tenant.getSchemaName());
        
        // 2. 保存租戶信息
        tenantRepository.save(tenant);
        
        // 3. 初始化Schema的表結(jié)構(gòu)
        initializeSchema(tenant.getSchemaName());
    }
    
    publicvoiddeleteTenant(String tenantId) {
        Tenant tenant = tenantRepository.findById(tenantId)
                .orElseThrow(() -> newRuntimeException("Tenant not found: " + tenantId));
        
        // 1. 刪除Schema
        dropSchema(tenant.getSchemaName());
        
        // 2. 刪除租戶信息
        tenantRepository.delete(tenant);
    }
    
    privatevoidcreateSchemaIfNotExists(String schema) {
        try (Connection connection = dataSource.getConnection()) {
            String sql = "CREATE SCHEMA IF NOT EXISTS " + schema;
            try (Statement stmt = connection.createStatement()) {
                stmt.execute(sql);
            }
        } catch (SQLException e) {
            thrownewRuntimeException("Failed to create schema: " + schema, e);
        }
    }
    
    privatevoiddropSchema(String schema) {
        try (Connection connection = dataSource.getConnection()) {
            String sql = "DROP SCHEMA IF EXISTS " + schema + " CASCADE";
            try (Statement stmt = connection.createStatement()) {
                stmt.execute(sql);
            }
        } catch (SQLException e) {
            thrownewRuntimeException("Failed to drop schema: " + schema, e);
        }
    }
    
    privatevoidinitializeSchema(String schemaName) {
        // 設(shè)置當(dāng)前租戶上下文
        String previousTenant = TenantContextHolder.getTenantId();
        try {
            TenantContextHolder.setTenantId(schemaName);
            
            // 使用JPA/Hibernate工具初始化Schema
            // 可以使用SchemaExport或更推薦使用Flyway/Liquibase
            Session session = entityManagerFactory.createEntityManager().unwrap(Session.class);
            session.doWork(connection -> {
                // 執(zhí)行DDL語句
            });
            
        } finally {
            // 恢復(fù)之前的租戶上下文
            if (previousTenant != null) {
                TenantContextHolder.setTenantId(previousTenant);
            } else {
                TenantContextHolder.clear();
            }
        }
    }
}

7、租戶管理API

@RestController
@RequestMapping("/admin/tenants")
public class TenantSchemaController {
    
    @Autowired
    private TenantSchemaManagementService tenantService;
    
    @Autowired
    private TenantRepository tenantRepository;
    
    @GetMapping
    public List<Tenant> getAllTenants() {
        returntenantRepository.findAll();
    }
    
    @PostMapping
    publicResponseEntity<Tenant> createTenant(@RequestBody Tenant tenant) {
        tenantService.createTenant(tenant);
        returnResponseEntity.status(HttpStatus.CREATED).body(tenant);
    }
    
    @DeleteMapping("/{tenantId}")
    publicResponseEntity<Void> deleteTenant(@PathVariable String tenantId) {
        tenantService.deleteTenant(tenantId);
        returnResponseEntity.noContent().build();
    }
}

優(yōu)缺點(diǎn)分析

優(yōu)點(diǎn):

? 資源利用率高于獨(dú)立數(shù)據(jù)庫模式

? 較好的數(shù)據(jù)隔離性

? 運(yùn)維復(fù)雜度低于獨(dú)立數(shù)據(jù)庫模式

? 容易實(shí)現(xiàn)租戶特定的表結(jié)構(gòu)

? 數(shù)據(jù)庫級(jí)別的權(quán)限控制

缺點(diǎn):

? 數(shù)據(jù)庫管理復(fù)雜度增加

? 可能存在Schema數(shù)量限制

? 跨租戶查詢?nèi)匀焕щy

? 無法為不同租戶使用不同的數(shù)據(jù)庫類型

? 所有租戶共享數(shù)據(jù)庫資源,可能出現(xiàn)資源爭(zhēng)用

適用場(chǎng)景

? 中型SaaS應(yīng)用

? 租戶數(shù)量中等但增長(zhǎng)較快的場(chǎng)景

? 需要較好數(shù)據(jù)隔離但成本敏感的應(yīng)用

? PostgreSQL或MySQL等支持Schema/數(shù)據(jù)庫隔離的數(shù)據(jù)庫環(huán)境

方案三:共享數(shù)據(jù)庫,共享Schema,獨(dú)立表模式

原理與特點(diǎn)

在這種模式下,所有租戶共享同一個(gè)數(shù)據(jù)庫和Schema,但每個(gè)租戶有自己的表集合,通常通過表名前綴或后綴區(qū)分不同租戶的表。

實(shí)現(xiàn)步驟

1、實(shí)現(xiàn)多租戶命名策略

@Component
public class TenantTableNameStrategy extends PhysicalNamingStrategyStandardImpl {
    
    private static final long serialVersionUID = 1L;
    
    @Override
    public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
        String tenantId = TenantContextHolder.getTenantId();
        if (tenantId != null && !tenantId.isEmpty()) {
            String tablePrefix = tenantId + "_";
            returnnewIdentifier(tablePrefix + name.getText(), name.isQuoted());
        }
        returnsuper.toPhysicalTableName(name, context);
    }
}

2、配置Hibernate命名策略

@Configuration
@EnableJpaRepositories(basePackages = "com.example.repository")
@EntityScan(basePackages = "com.example.entity")
public class JpaConfig {
    
    @Autowired
    private TenantTableNameStrategy tableNameStrategy;
    
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            EntityManagerFactoryBuilder builder,
            DataSource dataSource) {
        
        Map<String, Object> properties = newHashMap<>();
        properties.put("hibernate.physical_naming_strategy", 
                tableNameStrategy);
        
        // 其他Hibernate配置...
        
        returnbuilder
                .dataSource(dataSource)
                .packages("com.example.entity")
                .properties(properties)
                .build();
    }
}

3、租戶實(shí)體和倉庫

@Entity
@Table(name = "tenant_info") // 避免與租戶表前綴沖突
public class Tenant {
    
    @Id
    private String id;
    
    @Column(nullable = false)
    private String name;
    
    @Column
    private boolean active = true;
    
    // getters and setters
}

@Repository
public interface TenantRepository extends JpaRepository<Tenant, String> {
    List<Tenant> findByActive(boolean active);
}

4、表初始化管理器

@Component
publicclassTenantTableManager {
    
    @Autowired
    private EntityManagerFactory entityManagerFactory;
    
    @Autowired
    private TenantRepository tenantRepository;
    
    @PersistenceContext
    private EntityManager entityManager;
    
    publicvoidinitializeTenantTables(String tenantId) {
        StringpreviousTenant= TenantContextHolder.getTenantId();
        try {
            TenantContextHolder.setTenantId(tenantId);
            
            // 使用JPA/Hibernate初始化表結(jié)構(gòu)
            // 在生產(chǎn)環(huán)境中,推薦使用Flyway或Liquibase進(jìn)行更精細(xì)的控制
            Sessionsession= entityManager.unwrap(Session.class);
            session.doWork(connection -> {
                // 執(zhí)行建表語句
                // 這里可以使用Hibernate的SchemaExport,但為簡(jiǎn)化,直接使用SQL
                
                // 示例:創(chuàng)建用戶表
                StringcreateUserTable="CREATE TABLE IF NOT EXISTS " + tenantId + "_users (" +
                        "id BIGINT NOT NULL AUTO_INCREMENT, " +
                        "username VARCHAR(255) NOT NULL, " +
                        "email VARCHAR(255) NOT NULL, " +
                        "created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " +
                        "PRIMARY KEY (id)" +
                        ")";
                
                try (Statementstmt= connection.createStatement()) {
                    stmt.execute(createUserTable);
                    // 創(chuàng)建其他表...
                }
            });
            
        } finally {
            if (previousTenant != null) {
                TenantContextHolder.setTenantId(previousTenant);
            } else {
                TenantContextHolder.clear();
            }
        }
    }
    
    publicvoiddropTenantTables(String tenantId) {
        // 獲取數(shù)據(jù)庫中所有表
        try (Connectionconnection= entityManager.unwrap(SessionImplementor.class).connection()) {
            DatabaseMetaDatametaData= connection.getMetaData();
            StringtablePrefix= tenantId + "_";
            
            try (ResultSettables= metaData.getTables(
                    connection.getCatalog(), connection.getSchema(), tablePrefix + "%", newString[]{"TABLE"})) {
                
                List<String> tablesToDrop = newArrayList<>();
                while (tables.next()) {
                    tablesToDrop.add(tables.getString("TABLE_NAME"));
                }
                
                // 刪除所有表
                for (String tableName : tablesToDrop) {
                    try (Statementstmt= connection.createStatement()) {
                        stmt.execute("DROP TABLE " + tableName);
                    }
                }
            }
        } catch (SQLException e) {
            thrownewRuntimeException("Failed to drop tenant tables", e);
        }
    }
}

5、租戶管理服務(wù)

@Service
publicclassTenantTableManagementService {
    
    @Autowired
    privateTenantRepository tenantRepository;
    
    @Autowired
    privateTenantTableManager tableManager;
    
    @PostConstruct
    publicvoidinitializeAllTenants() {
        for (Tenant tenant : tenantRepository.findByActive(true)) {
            tableManager.initializeTenantTables(tenant.getId());
        }
    }
    
    @Transactional
    publicvoidcreateTenant(Tenant tenant) {
        // 1. 保存租戶信息
        tenantRepository.save(tenant);
        
        // 2. 初始化租戶表
        tableManager.initializeTenantTables(tenant.getId());
    }
    
    @Transactional
    publicvoiddeleteTenant(String tenantId) {
        // 1. 刪除租戶表
        tableManager.dropTenantTables(tenantId);
        
        // 2. 刪除租戶信息
        tenantRepository.deleteById(tenantId);
    }
}

6、提供租戶管理API

@RestController
@RequestMapping("/admin/tenants")
public class TenantTableController {
    
    @Autowired
    private TenantTableManagementService tenantService;
    
    @Autowired
    private TenantRepository tenantRepository;
    
    @GetMapping
    public List<Tenant> getAllTenants() {
        returntenantRepository.findAll();
    }
    
    @PostMapping
    publicResponseEntity<Tenant> createTenant(@RequestBody Tenant tenant) {
        tenantService.createTenant(tenant);
        returnResponseEntity.status(HttpStatus.CREATED).body(tenant);
    }
    
    @DeleteMapping("/{tenantId}")
    publicResponseEntity<Void> deleteTenant(@PathVariable String tenantId) {
        tenantService.deleteTenant(tenantId);
        returnResponseEntity.noContent().build();
    }
}

優(yōu)缺點(diǎn)分析

優(yōu)點(diǎn):

? 簡(jiǎn)單易實(shí)現(xiàn),特別是對(duì)現(xiàn)有應(yīng)用的改造

? 資源利用率高

? 跨租戶查詢相對(duì)容易實(shí)現(xiàn)

? 維護(hù)成本低

? 租戶間表結(jié)構(gòu)可以不同

缺點(diǎn):

? 數(shù)據(jù)隔離級(jí)別較低

? 隨著租戶數(shù)量增加,表數(shù)量會(huì)急劇增長(zhǎng)

? 數(shù)據(jù)庫對(duì)象(如表、索引)數(shù)量可能達(dá)到數(shù)據(jù)庫限制

? 備份和恢復(fù)單個(gè)租戶數(shù)據(jù)較為復(fù)雜

? 可能需要處理表名長(zhǎng)度限制問題

適用場(chǎng)景

? 租戶數(shù)量適中且表結(jié)構(gòu)相對(duì)簡(jiǎn)單的SaaS應(yīng)用

? 需要為不同租戶提供不同表結(jié)構(gòu)的場(chǎng)景

? 快速原型開發(fā)或MVP(最小可行產(chǎn)品)

? 從單租戶向多租戶過渡的系統(tǒng)

方案四:共享數(shù)據(jù)庫,共享Schema,共享表模式

原理與特點(diǎn)

這是隔離級(jí)別最低但資源效率最高的方案。所有租戶共享相同的數(shù)據(jù)庫、Schema和表,通過在每個(gè)表中添加"租戶ID"列來區(qū)分不同租戶的數(shù)據(jù)。

實(shí)現(xiàn)步驟

1、創(chuàng)建租戶感知的實(shí)體基類

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Data
public abstract class TenantAwareEntity {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "tenant_id", nullable = false)
    private String tenantId;
    
    @CreatedDate
    @Column(name = "created_at", updatable = false)
    private LocalDateTime createdAt;
    
    @LastModifiedDate
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
    
    @PrePersist
    public void onPrePersist() {
        tenantId = TenantContextHolder.getTenantId();
    }
}

2、租戶實(shí)體和倉庫

@Entity
@Table(name = "tenants")
public class Tenant {
    
    @Id
    private String id;
    
    @Column(nullable = false)
    private String name;
    
    @Column
    private boolean active = true;
    
    // getters and setters
}

@Repository
public interface TenantRepository extends JpaRepository<Tenant, String> {
    List<Tenant> findByActive(boolean active);
}

3、實(shí)現(xiàn)租戶數(shù)據(jù)過濾器

@Component
publicclassTenantFilterInterceptorimplementsHandlerInterceptor {
    
    @Autowired
    privateEntityManager entityManager;
    
    @Override
    publicbooleanpreHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String tenantId = TenantContextHolder.getTenantId();
        if (tenantId != null) {
            // 設(shè)置Hibernate過濾器
            Session session = entityManager.unwrap(Session.class);
            Filter filter = session.enableFilter("tenantFilter");
            filter.setParameter("tenantId", tenantId);
            returntrue;
        }
        
        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        returnfalse;
    }
    
    @Override
    publicvoidafterCompletion(HttpServletRequest request, HttpServletResponse response, 
                               Object handler, Exception ex) {
        Session session = entityManager.unwrap(Session.class);
        session.disableFilter("tenantFilter");
    }
}

4、為實(shí)體添加過濾器注解

@Entity
@Table(name = "users")
@FilterDef(name = "tenantFilter", parameters = {
    @ParamDef(name = "tenantId", type = "string")
})
@Filter(name = "tenantFilter", condition = "tenant_id = :tenantId")
public class User extends TenantAwareEntity {
    
    @Column(name = "username", nullable = false)
    private String username;
    
    @Column(name = "email", nullable = false)
    private String email;
    
    // 其他字段和方法...
}

5、租戶管理服務(wù)

@Service
publicclassSharedTableTenantService {
    
    @Autowired
    privateTenantRepository tenantRepository;
    
    @Autowired
    privateEntityManager entityManager;
    
    @Transactional
    publicvoidcreateTenant(Tenant tenant) {
        // 直接保存租戶信息
        tenantRepository.save(tenant);
        
        // 初始化租戶默認(rèn)數(shù)據(jù)
        initializeTenantData(tenant.getId());
    }
    
    @Transactional
    publicvoiddeleteTenant(String tenantId) {
        // 刪除該租戶的所有數(shù)據(jù)
        deleteAllTenantData(tenantId);
        
        // 刪除租戶記錄
        tenantRepository.deleteById(tenantId);
    }
    
    privatevoidinitializeTenantData(String tenantId) {
        String previousTenant = TenantContextHolder.getTenantId();
        try {
            TenantContextHolder.setTenantId(tenantId);
            
            // 創(chuàng)建默認(rèn)用戶、角色等
            // ...
            
        } finally {
            if (previousTenant != null) {
                TenantContextHolder.setTenantId(previousTenant);
            } else {
                TenantContextHolder.clear();
            }
        }
    }
    
    privatevoiddeleteAllTenantData(String tenantId) {
        // 獲取所有帶有tenant_id列的表
        List<String> tables = getTablesWithTenantIdColumn();
        
        // 從每個(gè)表中刪除該租戶的數(shù)據(jù)
        for (String table : tables) {
            entityManager.createNativeQuery("DELETE FROM " + table + " WHERE tenant_id = :tenantId")
                    .setParameter("tenantId", tenantId)
                    .executeUpdate();
        }
    }
    
    privateList<String> getTablesWithTenantIdColumn() {
        List<String> tables = newArrayList<>();
        
        try (Connection connection = entityManager.unwrap(SessionImplementor.class).connection()) {
            DatabaseMetaData metaData = connection.getMetaData();
            
            try (ResultSet rs = metaData.getTables(
                    connection.getCatalog(), connection.getSchema(), "%", newString[]{"TABLE"})) {
                
                while (rs.next()) {
                    String tableName = rs.getString("TABLE_NAME");
                    
                    // 檢查表是否有tenant_id列
                    try (ResultSet columns = metaData.getColumns(
                            connection.getCatalog(), connection.getSchema(), tableName, "tenant_id")) {
                        
                        if (columns.next()) {
                            tables.add(tableName);
                        }
                    }
                }
            }
        } catch (SQLException e) {
            thrownewRuntimeException("Failed to get tables with tenant_id column", e);
        }
        
        return tables;
    }
}

6、租戶管理API

@RestController
@RequestMapping("/admin/tenants")
public class SharedTableTenantController {
    
    @Autowired
    private SharedTableTenantService tenantService;
    
    @Autowired
    private TenantRepository tenantRepository;
    
    @GetMapping
    public List<Tenant> getAllTenants() {
        returntenantRepository.findAll();
    }
    
    @PostMapping
    publicResponseEntity<Tenant> createTenant(@RequestBody Tenant tenant) {
        tenantService.createTenant(tenant);
        returnResponseEntity.status(HttpStatus.CREATED).body(tenant);
    }
    
    @DeleteMapping("/{tenantId}")
    publicResponseEntity<Void> deleteTenant(@PathVariable String tenantId) {
        tenantService.deleteTenant(tenantId);
        returnResponseEntity.noContent().build();
    }
}

優(yōu)缺點(diǎn)分析

優(yōu)點(diǎn):

? 資源利用率最高

? 維護(hù)成本最低

? 實(shí)現(xiàn)簡(jiǎn)單,對(duì)現(xiàn)有單租戶系統(tǒng)改造容易

? 跨租戶查詢簡(jiǎn)單

? 節(jié)省存儲(chǔ)空間,特別是當(dāng)數(shù)據(jù)量小時(shí)

缺點(diǎn):

? 數(shù)據(jù)隔離級(jí)別最低

? 安全風(fēng)險(xiǎn)較高,一個(gè)錯(cuò)誤可能導(dǎo)致跨租戶數(shù)據(jù)泄露

? 所有租戶共享相同的表結(jié)構(gòu)

? 需要在所有數(shù)據(jù)訪問層強(qiáng)制租戶過濾

適用場(chǎng)景

? 租戶數(shù)量多但每個(gè)租戶數(shù)據(jù)量小的場(chǎng)景

? 成本敏感的應(yīng)用

? 原型驗(yàn)證或MVP階段

方案五:混合租戶模式

原理與特點(diǎn)

混合租戶模式結(jié)合了多種隔離策略,根據(jù)租戶等級(jí)、重要性或特定需求為不同租戶提供不同級(jí)別的隔離。例如,免費(fèi)用戶可能使用共享表模式,而付費(fèi)企業(yè)用戶可能使用獨(dú)立數(shù)據(jù)庫模式。

實(shí)現(xiàn)步驟

1、租戶類型和存儲(chǔ)

@Entity
@Table(name = "tenants")
publicclassTenant {
    
    @Id
    privateString id;
    
    @Column(nullable = false)
    privateString name;
    
    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    privateTenantTypetype;
    
    @Column
    privateString databaseUrl;
    
    @Column
    privateString username;
    
    @Column
    privateString password;
    
    @Column
    privateString driverClassName;
    
    @Column
    privateString schemaName;
    
    @Column
    privateboolean active = true;
    
    publicenumTenantType {
        DEDICATED_DATABASE,
        DEDICATED_SCHEMA,
        DEDICATED_TABLE,
        SHARED_TABLE
    }
    
    // getters and setters
}

@Repository
publicinterfaceTenantRepositoryextendsJpaRepository<Tenant, String> {
    List<Tenant> findByActive(boolean active);
    List<Tenant> findByType(Tenant.TenantTypetype);
}

2、創(chuàng)建租戶分類策略

@Component
publicclassTenantIsolationStrategy {
    
    @Autowired
    privateTenantRepository tenantRepository;
    
    private final Map<String, Tenant> tenantCache = newConcurrentHashMap<>();
    
    @PostConstruct
    publicvoidloadTenants() {
        tenantRepository.findByActive(true).forEach(tenant -> 
            tenantCache.put(tenant.getId(), tenant));
    }
    
    publicTenant.TenantTypegetIsolationTypeForTenant(String tenantId) {
        Tenant tenant = tenantCache.get(tenantId);
        if (tenant == null) {
            tenant = tenantRepository.findById(tenantId)
                    .orElseThrow(() -> newRuntimeException("Tenant not found: " + tenantId));
            tenantCache.put(tenantId, tenant);
        }
        return tenant.getType();
    }
    
    publicTenantgetTenant(String tenantId) {
        Tenant tenant = tenantCache.get(tenantId);
        if (tenant == null) {
            tenant = tenantRepository.findById(tenantId)
                    .orElseThrow(() -> newRuntimeException("Tenant not found: " + tenantId));
            tenantCache.put(tenantId, tenant);
        }
        return tenant;
    }
    
    publicvoidevictFromCache(String tenantId) {
        tenantCache.remove(tenantId);
    }
}

3、實(shí)現(xiàn)混合數(shù)據(jù)源路由

@Component
publicclassHybridTenantRouter {
    
    @Autowired
    privateTenantIsolationStrategy isolationStrategy;
    
    private final Map<String, DataSource> dedicatedDataSources = newConcurrentHashMap<>();
    
    @Autowired
    privateDataSource sharedDataSource;
    
    publicDataSourcegetDataSourceForTenant(String tenantId) {
        Tenant.TenantType isolationType = isolationStrategy.getIsolationTypeForTenant(tenantId);
        
        if (isolationType == Tenant.TenantType.DEDICATED_DATABASE) {
            // 對(duì)于獨(dú)立數(shù)據(jù)庫的租戶,查找或創(chuàng)建專用數(shù)據(jù)源
            return dedicatedDataSources.computeIfAbsent(tenantId, this::createDedicatedDataSource);
        }
        
        return sharedDataSource;
    }
    
    privateDataSourcecreateDedicatedDataSource(String tenantId) {
        Tenant tenant = isolationStrategy.getTenant(tenantId);
        
        HikariDataSource dataSource = newHikariDataSource();
        dataSource.setJdbcUrl(tenant.getDatabaseUrl());
        dataSource.setUsername(tenant.getUsername());
        dataSource.setPassword(tenant.getPassword());
        dataSource.setDriverClassName(tenant.getDriverClassName());
        
        return dataSource;
    }
    
    publicvoidremoveDedicatedDataSource(String tenantId) {
        DataSource dataSource = dedicatedDataSources.remove(tenantId);
        if (dataSource instanceofHikariDataSource) {
            ((HikariDataSource) dataSource).close();
        }
    }
}

4、混合租戶路由數(shù)據(jù)源

public class HybridRoutingDataSource extends AbstractRoutingDataSource {
    
    @Autowired
    privateHybridTenantRouter tenantRouter;
    
    @Autowired
    privateTenantIsolationStrategy isolationStrategy;
    
    @Override
    protectedObject determineCurrentLookupKey() {
        String tenantId = TenantContextHolder.getTenantId();
        if (tenantId == null) {
            return"default";
        }
        
        Tenant.TenantType isolationType = isolationStrategy.getIsolationTypeForTenant(tenantId);
        
        if (isolationType == Tenant.TenantType.DEDICATED_DATABASE) {
            return tenantId;
        }
        
        return"shared";
    }
    
    @Override
    protectedDataSource determineTargetDataSource() {
        String tenantId = TenantContextHolder.getTenantId();
        if (tenantId == null) {
            returnsuper.determineTargetDataSource();
        }
        
        return tenantRouter.getDataSourceForTenant(tenantId);
    }
}

5、混合租戶攔截器

@Component
publicclassHybridTenantInterceptorimplementsHandlerInterceptor {
    
    @Autowired
    privateTenantIsolationStrategy isolationStrategy;
    
    @Autowired
    privateEntityManager entityManager;
    
    @Override
    publicbooleanpreHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String tenantId = extractTenantId(request);
        if (tenantId != null) {
            TenantContextHolder.setTenantId(tenantId);
            
            Tenant.TenantType isolationType = isolationStrategy.getIsolationTypeForTenant(tenantId);
            
            // 根據(jù)隔離類型應(yīng)用不同策略
            switch (isolationType) {
                caseDEDICATED_DATABASE:
                    // 已由數(shù)據(jù)源路由處理
                    break;
                caseDEDICATED_SCHEMA:
                    setSchema(isolationStrategy.getTenant(tenantId).getSchemaName());
                    break;
                caseDEDICATED_TABLE:
                    // 由命名策略處理
                    break;
                caseSHARED_TABLE:
                    enableTenantFilter(tenantId);
                    break;
            }
            
            returntrue;
        }
        
        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        returnfalse;
    }
    
    @Override
    publicvoidafterCompletion(HttpServletRequest request, HttpServletResponse response, 
                               Object handler, Exception ex) {
        String tenantId = TenantContextHolder.getTenantId();
        if (tenantId != null) {
            Tenant.TenantType isolationType = isolationStrategy.getIsolationTypeForTenant(tenantId);
            
            if (isolationType == Tenant.TenantType.SHARED_TABLE) {
                disableTenantFilter();
            }
        }
        
        TenantContextHolder.clear();
    }
    
    privatevoidsetSchema(String schema) {
        try {
            entityManager.createNativeQuery("SET SCHEMA '" + schema + "'").executeUpdate();
        } catch (Exception e) {
            // 處理異常
        }
    }
    
    privatevoidenableTenantFilter(String tenantId) {
        Session session = entityManager.unwrap(Session.class);
        Filter filter = session.enableFilter("tenantFilter");
        filter.setParameter("tenantId", tenantId);
    }
    
    privatevoiddisableTenantFilter() {
        Session session = entityManager.unwrap(Session.class);
        session.disableFilter("tenantFilter");
    }
    
    privateStringextractTenantId(HttpServletRequest request) {
        // 從請(qǐng)求中提取租戶ID的邏輯
        return request.getHeader("X-TenantID");
    }
}

6、綜合租戶管理服務(wù)

@Service
publicclassHybridTenantManagementService {
    
    @Autowired
    private TenantRepository tenantRepository;
    
    @Autowired
    private TenantIsolationStrategy isolationStrategy;
    
    @Autowired
    private HybridTenantRouter tenantRouter;
    
    @Autowired
    private EntityManager entityManager;
    
    @Autowired
    private DataSource dataSource;
    
    // 不同隔離類型的初始化策略
    privatefinal Map<Tenant.TenantType, TenantInitializer> initializers = newHashMap<>();
    
    @PostConstruct
    publicvoidinit() {
        initializers.put(Tenant.TenantType.DEDICATED_DATABASE, this::initializeDedicatedDatabase);
        initializers.put(Tenant.TenantType.DEDICATED_SCHEMA, this::initializeDedicatedSchema);
        initializers.put(Tenant.TenantType.DEDICATED_TABLE, this::initializeDedicatedTables);
        initializers.put(Tenant.TenantType.SHARED_TABLE, this::initializeSharedTables);
    }
    
    @Transactional
    publicvoidcreateTenant(Tenant tenant) {
        // 1. 保存租戶基本信息
        tenantRepository.save(tenant);
        
        // 2. 根據(jù)隔離類型初始化
        TenantInitializerinitializer= initializers.get(tenant.getType());
        if (initializer != null) {
            initializer.initialize(tenant);
        }
        
        // 3. 更新緩存
        isolationStrategy.evictFromCache(tenant.getId());
    }
    
    @Transactional
    publicvoiddeleteTenant(String tenantId) {
        Tenanttenant= tenantRepository.findById(tenantId)
                .orElseThrow(() -> newRuntimeException("Tenant not found: " + tenantId));
        
        // 1. 根據(jù)隔離類型清理資源
        switch (tenant.getType()) {
            case DEDICATED_DATABASE:
                cleanupDedicatedDatabase(tenant);
                break;
            case DEDICATED_SCHEMA:
                cleanupDedicatedSchema(tenant);
                break;
            case DEDICATED_TABLE:
                cleanupDedicatedTables(tenant);
                break;
            case SHARED_TABLE:
                cleanupSharedTables(tenant);
                break;
        }
        
        // 2. 刪除租戶信息
        tenantRepository.delete(tenant);
        
        // 3. 更新緩存
        isolationStrategy.evictFromCache(tenantId);
    }
    
    // 獨(dú)立數(shù)據(jù)庫初始化
    privatevoidinitializeDedicatedDatabase(Tenant tenant) {
        // 創(chuàng)建數(shù)據(jù)源
        DataSourcededicatedDs= tenantRouter.getDataSourceForTenant(tenant.getId());
        
        // 初始化數(shù)據(jù)庫結(jié)構(gòu)
        try (Connectionconn= dedicatedDs.getConnection()) {
            // 執(zhí)行DDL腳本
            // ...
        } catch (SQLException e) {
            thrownewRuntimeException("Failed to initialize database for tenant: " + tenant.getId(), e);
        }
    }
    
    // Schema初始化
    privatevoidinitializeDedicatedSchema(Tenant tenant) {
        try (Connectionconn= dataSource.getConnection()) {
            // 創(chuàng)建Schema
            try (Statementstmt= conn.createStatement()) {
                stmt.execute("CREATE SCHEMA IF NOT EXISTS " + tenant.getSchemaName());
            }
            
            // 切換到該Schema
            conn.setSchema(tenant.getSchemaName());
            
            // 創(chuàng)建表結(jié)構(gòu)
            // ...
            
        } catch (SQLException e) {
            thrownewRuntimeException("Failed to initialize schema for tenant: " + tenant.getId(), e);
        }
    }
    
    // 獨(dú)立表初始化
    privatevoidinitializeDedicatedTables(Tenant tenant) {
        // 設(shè)置線程上下文中的租戶ID以使用正確的表名前綴
        StringpreviousTenant= TenantContextHolder.getTenantId();
        try {
            TenantContextHolder.setTenantId(tenant.getId());
            
            // 創(chuàng)建表
            // ...
            
        } finally {
            if (previousTenant != null) {
                TenantContextHolder.setTenantId(previousTenant);
            } else {
                TenantContextHolder.clear();
            }
        }
    }
    
    // 共享表初始化
    privatevoidinitializeSharedTables(Tenant tenant) {
        // 共享表模式下,只需插入租戶特定的初始數(shù)據(jù)
        StringpreviousTenant= TenantContextHolder.getTenantId();
        try {
            TenantContextHolder.setTenantId(tenant.getId());
            
            // 插入初始數(shù)據(jù)
            // ...
            
        } finally {
            if (previousTenant != null) {
                TenantContextHolder.setTenantId(previousTenant);
            } else {
                TenantContextHolder.clear();
            }
        }
    }
    
    // 清理方法
    privatevoidcleanupDedicatedDatabase(Tenant tenant) {
        // 關(guān)閉并移除數(shù)據(jù)源
        tenantRouter.removeDedicatedDataSource(tenant.getId());
        
        // 注意:通常不會(huì)自動(dòng)刪除實(shí)際的數(shù)據(jù)庫,這需要DBA手動(dòng)操作
    }
    
    privatevoidcleanupDedicatedSchema(Tenant tenant) {
        try (Connectionconn= dataSource.getConnection()) {
            try (Statementstmt= conn.createStatement()) {
                stmt.execute("DROP SCHEMA IF EXISTS " + tenant.getSchemaName() + " CASCADE");
            }
        } catch (SQLException e) {
            thrownewRuntimeException("Failed to drop schema for tenant: " + tenant.getId(), e);
        }
    }
    
    privatevoidcleanupDedicatedTables(Tenant tenant) {
        // 查找并刪除該租戶的所有表
        try (Connectionconn= dataSource.getConnection()) {
            DatabaseMetaDatametaData= conn.getMetaData();
            StringtablePrefix= tenant.getId() + "_";
            
            try (ResultSettables= metaData.getTables(
                    conn.getCatalog(), conn.getSchema(), tablePrefix + "%", newString[]{"TABLE"})) {
                
                while (tables.next()) {
                    StringtableName= tables.getString("TABLE_NAME");
                    try (Statementstmt= conn.createStatement()) {
                        stmt.execute("DROP TABLE " + tableName);
                    }
                }
            }
        } catch (SQLException e) {
            thrownewRuntimeException("Failed to drop tables for tenant: " + tenant.getId(), e);
        }
    }
    
    privatevoidcleanupSharedTables(Tenant tenant) {
        // 從所有帶有tenant_id列的表中刪除該租戶的數(shù)據(jù)
        entityManager.createNativeQuery(
                "SELECT table_name FROM information_schema.columns " +
                "WHERE column_name = 'tenant_id'")
                .getResultList()
                .forEach(tableName -> 
                    entityManager.createNativeQuery(
                            "DELETE FROM " + tableName + " WHERE tenant_id = :tenantId")
                            .setParameter("tenantId", tenant.getId())
                            .executeUpdate()
                );
    }
    
    // 租戶初始化策略接口
    @FunctionalInterface
    privateinterfaceTenantInitializer {
        voidinitialize(Tenant tenant);
    }
}

7、提供租戶管理API

@RestController
@RequestMapping("/admin/tenants")
public class HybridTenantController {
    
    @Autowired
    private HybridTenantManagementService tenantService;
    
    @Autowired
    private TenantRepository tenantRepository;
    
    @GetMapping
    public List<Tenant> getAllTenants() {
        returntenantRepository.findAll();
    }
    
    @PostMapping
    publicResponseEntity<Tenant> createTenant(@RequestBody Tenant tenant) {
        tenantService.createTenant(tenant);
        returnResponseEntity.status(HttpStatus.CREATED).body(tenant);
    }
    
    @PutMapping("/{tenantId}")
    publicResponseEntity<Tenant> updateTenant(
            @PathVariable String tenantId, 
            @RequestBody Tenant tenant) {
        
        tenant.setId(tenantId);
        tenantService.updateTenant(tenant);
        returnResponseEntity.ok(tenant);
    }
    
    @DeleteMapping("/{tenantId}")
    publicResponseEntity<Void> deleteTenant(@PathVariable String tenantId) {
        tenantService.deleteTenant(tenantId);
        returnResponseEntity.noContent().build();
    }
    
    @GetMapping("/types")
    publicResponseEntity<List<Tenant.TenantType>> getTenantTypes() {
        returnResponseEntity.ok(Arrays.asList(Tenant.TenantType.values()));
    }
}

優(yōu)缺點(diǎn)分析

優(yōu)點(diǎn):

? 最大的靈活性,可根據(jù)租戶需求提供不同隔離級(jí)別

? 可以實(shí)現(xiàn)資源和成本的平衡

? 可以根據(jù)業(yè)務(wù)價(jià)值分配資源

? 適應(yīng)不同客戶的安全和性能需求

缺點(diǎn):

? 實(shí)現(xiàn)復(fù)雜度最高

? 維護(hù)和測(cè)試成本高

? 需要處理多種數(shù)據(jù)訪問模式

? 可能引入不一致的用戶體驗(yàn)

? 錯(cuò)誤處理更加復(fù)雜

適用場(chǎng)景

? 需要提供靈活定價(jià)模型的應(yīng)用

? 資源需求差異大的租戶集合

方案對(duì)比

隔離模式

數(shù)據(jù)隔離級(jí)別

資源利用率

成本

復(fù)雜度

適用場(chǎng)景

獨(dú)立數(shù)據(jù)庫

最高

企業(yè)級(jí)應(yīng)用、金融/醫(yī)療行業(yè)

獨(dú)立Schema

中型SaaS、安全要求較高的場(chǎng)景

獨(dú)立表

中高

中低

中小型應(yīng)用、原型驗(yàn)證

共享表

最高

大量小租戶、成本敏感場(chǎng)景

混合模式

可變

可變

中高

多層級(jí)服務(wù)、復(fù)雜業(yè)務(wù)需求

總結(jié)

多租戶架構(gòu)是構(gòu)建現(xiàn)代SaaS應(yīng)用的關(guān)鍵技術(shù),選擇多租戶模式需要平衡數(shù)據(jù)隔離、資源利用、成本和復(fù)雜度等多種因素。

通過深入理解這些架構(gòu)模式及其權(quán)衡,可以根據(jù)實(shí)際情況選擇適合的多租戶架構(gòu),構(gòu)建可擴(kuò)展、安全且經(jīng)濟(jì)高效的企業(yè)級(jí)應(yīng)用。


責(zé)任編輯:武曉燕 來源: JAVA日知錄
相關(guān)推薦

2024-10-17 08:26:53

ELKmongodb方案

2024-05-28 08:17:54

2025-02-18 16:27:01

2025-10-24 14:18:55

2023-02-24 08:27:56

RabbitMQKafka架構(gòu)

2025-03-03 00:45:00

2022-06-09 10:34:44

架構(gòu)數(shù)據(jù)

2024-10-15 11:04:18

2009-10-19 14:39:10

2023-06-07 13:50:00

SaaS多租戶系統(tǒng)

2024-04-17 08:03:45

架構(gòu)設(shè)計(jì)Java

2023-07-05 08:00:52

MetrAuto系統(tǒng)架構(gòu)

2020-05-14 14:48:15

架構(gòu)模式單庫

2009-10-15 14:21:57

大樓綜合布線系統(tǒng)

2009-09-25 16:54:02

機(jī)房UPS供電系統(tǒng)

2009-10-14 13:19:20

2025-06-09 01:22:00

2020-09-15 07:00:00

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

2011-05-17 09:15:45

布線光纖快速以太網(wǎng)

2018-09-27 15:56:15

點(diǎn)贊
收藏

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