Spring MVC 多租户架构与数据隔离教程

文章目录

目录

  1. 多租户架构概述
  2. 多租户数据隔离策略
  3. 租户识别与路由机制
  4. [Spring MVC多租户实现](#Spring MVC多租户实现)
  5. 数据访问层多租户设计
  6. 缓存策略与租户隔离
  7. 安全与权限控制
  8. 性能优化与监控
  9. 最佳实践
  10. 常见问题解决
  11. 总结

多租户架构概述

多租户架构定义

多租户架构(Multi-Tenancy)是一种软件架构模式,其中单个应用程序实例为多个租户(客户、组织或用户组)提供服务,同时确保租户之间的数据隔离和安全性。

多租户架构优势

1. 成本效益

  • 共享基础设施,降低运营成本
  • 统一维护和升级
  • 资源利用率高

2. 可扩展性

  • 支持大量租户
  • 弹性扩展能力
  • 快速部署新租户

3. 维护便利

  • 统一代码库
  • 集中式管理
  • 标准化流程

多租户架构模式

1. 数据库级隔离(Database per Tenant)

复制代码
租户A → 数据库A
租户B → 数据库B
租户C → 数据库C

2. 表级隔离(Schema per Tenant)

复制代码
数据库
├── tenant_a_schema
├── tenant_b_schema
└── tenant_c_schema

3. 行级隔离(Row Level Security)

复制代码
用户表
├── id, name, tenant_id
├── 1, Alice, tenant_a
├── 2, Bob, tenant_b
└── 3, Carol, tenant_a

多租户数据隔离策略

数据库级隔离实现

Maven依赖配置

xml 复制代码
<dependencies>
    <!-- Spring Boot Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- Spring Data JPA -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    
    <!-- 多数据源支持 -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
        <version>3.5.2</version>
    </dependency>
    
    <!-- 租户上下文 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
    </dependency>
</dependencies>

多数据源配置

java 复制代码
@Configuration
@EnableTransactionManagement
public class MultiTenantDataSourceConfig {
    
    @Bean
    @ConfigurationProperties("spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean
    @ConfigurationProperties("spring.datasource.tenant")
    public DataSource tenantDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean
    public DataSource routingDataSource() {
        DynamicRoutingDataSource routingDataSource = new DynamicRoutingDataSource();
        
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put("master", masterDataSource());
        dataSourceMap.put("tenant", tenantDataSource());
        
        routingDataSource.setDefaultTargetDataSource(masterDataSource());
        routingDataSource.setTargetDataSources(dataSourceMap);
        
        return routingDataSource;
    }
}

表级隔离实现

租户Schema管理

java 复制代码
@Component
public class TenantSchemaManager {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public void createTenantSchema(String tenantId) {
        String schemaName = "tenant_" + tenantId;
        
        // 创建租户Schema
        jdbcTemplate.execute("CREATE SCHEMA IF NOT EXISTS " + schemaName);
        
        // 创建租户表
        createTenantTables(schemaName);
    }
    
    private void createTenantTables(String schemaName) {
        String createUserTable = """
            CREATE TABLE IF NOT EXISTS %s.users (
                id BIGINT PRIMARY KEY AUTO_INCREMENT,
                username VARCHAR(50) NOT NULL,
                email VARCHAR(100) NOT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
            """.formatted(schemaName);
            
        jdbcTemplate.execute(createUserTable);
    }
    
    public void switchToTenantSchema(String tenantId) {
        String schemaName = "tenant_" + tenantId;
        jdbcTemplate.execute("USE " + schemaName);
    }
}

行级隔离实现

租户上下文管理

java 复制代码
public class TenantContext {
    private static final ThreadLocal<String> currentTenant = new ThreadLocal<>();
    
    public static void setCurrentTenant(String tenantId) {
        currentTenant.set(tenantId);
    }
    
    public static String getCurrentTenant() {
        return currentTenant.get();
    }
    
    public static void clear() {
        currentTenant.remove();
    }
}

租户实体基类

java 复制代码
@MappedSuperclass
public abstract class TenantAwareEntity {
    
    @Column(name = "tenant_id", nullable = false)
    private String tenantId;
    
    @PrePersist
    protected void onCreate() {
        if (tenantId == null) {
            tenantId = TenantContext.getCurrentTenant();
        }
    }
    
    // getter and setter
    public String getTenantId() {
        return tenantId;
    }
    
    public void setTenantId(String tenantId) {
        this.tenantId = tenantId;
    }
}

租户识别与路由机制

子域名识别

租户解析器

java 复制代码
@Component
public class SubdomainTenantResolver implements TenantResolver {
    
    @Override
    public String resolveTenant(HttpServletRequest request) {
        String serverName = request.getServerName();
        
        // 解析子域名:tenant1.example.com -> tenant1
        if (serverName.contains(".")) {
            String[] parts = serverName.split("\\.");
            if (parts.length >= 3) {
                return parts[0];
            }
        }
        
        return "default";
    }
}

URL路径识别

路径租户解析器

java 复制代码
@Component
public class PathTenantResolver implements TenantResolver {
    
    @Override
    public String resolveTenant(HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        
        // 解析路径:/tenant/tenant1/users -> tenant1
        Pattern pattern = Pattern.compile("^/tenant/([^/]+)/");
        Matcher matcher = pattern.matcher(requestURI);
        
        if (matcher.find()) {
            return matcher.group(1);
        }
        
        return "default";
    }
}

请求头识别

Header租户解析器

java 复制代码
@Component
public class HeaderTenantResolver implements TenantResolver {
    
    private static final String TENANT_HEADER = "X-Tenant-ID";
    
    @Override
    public String resolveTenant(HttpServletRequest request) {
        String tenantId = request.getHeader(TENANT_HEADER);
        return tenantId != null ? tenantId : "default";
    }
}

租户拦截器

租户上下文拦截器

java 复制代码
@Component
public class TenantContextInterceptor implements HandlerInterceptor {
    
    @Autowired
    private List<TenantResolver> tenantResolvers;
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        
        String tenantId = resolveTenant(request);
        
        if (tenantId == null || tenantId.isEmpty()) {
            response.setStatus(HttpStatus.BAD_REQUEST.value());
            response.getWriter().write("Tenant ID is required");
            return false;
        }
        
        // 验证租户是否存在
        if (!isValidTenant(tenantId)) {
            response.setStatus(HttpStatus.FORBIDDEN.value());
            response.getWriter().write("Invalid tenant ID");
            return false;
        }
        
        TenantContext.setCurrentTenant(tenantId);
        return true;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, 
                               HttpServletResponse response, 
                               Object handler, Exception ex) {
        TenantContext.clear();
    }
    
    private String resolveTenant(HttpServletRequest request) {
        for (TenantResolver resolver : tenantResolvers) {
            String tenantId = resolver.resolveTenant(request);
            if (tenantId != null && !tenantId.equals("default")) {
                return tenantId;
            }
        }
        return "default";
    }
    
    private boolean isValidTenant(String tenantId) {
        // 实现租户验证逻辑
        return tenantService.exists(tenantId);
    }
}

Spring MVC多租户实现

租户配置管理

租户配置实体

java 复制代码
@Entity
@Table(name = "tenant_configs")
public class TenantConfig {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "tenant_id", nullable = false)
    private String tenantId;
    
    @Column(name = "config_key", nullable = false)
    private String configKey;
    
    @Column(name = "config_value")
    private String configValue;
    
    @Column(name = "config_type")
    private String configType;
    
    // constructors, getters, setters
}

租户配置服务

java 复制代码
@Service
public class TenantConfigService {
    
    @Autowired
    private TenantConfigRepository configRepository;
    
    public String getConfig(String key, String defaultValue) {
        String tenantId = TenantContext.getCurrentTenant();
        
        Optional<TenantConfig> config = configRepository
            .findByTenantIdAndConfigKey(tenantId, key);
            
        return config.map(TenantConfig::getConfigValue)
                    .orElse(defaultValue);
    }
    
    public void setConfig(String key, String value) {
        String tenantId = TenantContext.getCurrentTenant();
        
        TenantConfig config = configRepository
            .findByTenantIdAndConfigKey(tenantId, key)
            .orElse(new TenantConfig());
            
        config.setTenantId(tenantId);
        config.setConfigKey(key);
        config.setConfigValue(value);
        
        configRepository.save(config);
    }
}

多租户控制器

租户感知控制器

java 复制代码
@RestController
@RequestMapping("/api/tenant")
public class TenantAwareController {
    
    @Autowired
    private UserService userService;
    
    @Autowired
    private TenantConfigService configService;
    
    @GetMapping("/users")
    public ResponseEntity<List<User>> getUsers() {
        // 自动使用当前租户上下文
        List<User> users = userService.findAll();
        return ResponseEntity.ok(users);
    }
    
    @PostMapping("/users")
    public ResponseEntity<User> createUser(@RequestBody User user) {
        // 租户ID会自动设置
        User savedUser = userService.save(user);
        return ResponseEntity.ok(savedUser);
    }
    
    @GetMapping("/config/{key}")
    public ResponseEntity<String> getConfig(@PathVariable String key) {
        String value = configService.getConfig(key, "");
        return ResponseEntity.ok(value);
    }
}

租户数据源路由

动态数据源路由

java 复制代码
@Component
public class TenantDataSourceRouter {
    
    @Autowired
    private DataSource masterDataSource;
    
    private final Map<String, DataSource> tenantDataSources = new ConcurrentHashMap<>();
    
    public DataSource getDataSource(String tenantId) {
        if ("master".equals(tenantId)) {
            return masterDataSource;
        }
        
        return tenantDataSources.computeIfAbsent(tenantId, this::createTenantDataSource);
    }
    
    private DataSource createTenantDataSource(String tenantId) {
        // 根据租户ID创建数据源
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/tenant_" + tenantId);
        config.setUsername("tenant_user");
        config.setPassword("tenant_password");
        
        return new HikariDataSource(config);
    }
}

数据访问层多租户设计

租户感知Repository

基础租户Repository

java 复制代码
@NoRepositoryBean
public interface TenantAwareRepository<T, ID> extends JpaRepository<T, ID> {
    
    @Query("SELECT e FROM #{#entityName} e WHERE e.tenantId = :tenantId")
    List<T> findByTenantId(@Param("tenantId") String tenantId);
    
    @Query("SELECT e FROM #{#entityName} e WHERE e.tenantId = :tenantId AND e.id = :id")
    Optional<T> findByTenantIdAndId(@Param("tenantId") String tenantId, @Param("id") ID id);
    
    @Modifying
    @Query("DELETE FROM #{#entityName} e WHERE e.tenantId = :tenantId")
    void deleteByTenantId(@Param("tenantId") String tenantId);
}

用户Repository实现

java 复制代码
@Repository
public interface UserRepository extends TenantAwareRepository<User, Long> {
    
    List<User> findByTenantIdAndUsernameContaining(String tenantId, String username);
    
    @Query("SELECT u FROM User u WHERE u.tenantId = :tenantId AND u.email = :email")
    Optional<User> findByTenantIdAndEmail(@Param("tenantId") String tenantId, @Param("email") String email);
}

租户数据过滤器

JPA租户过滤器

java 复制代码
@Component
public class TenantFilter {
    
    @EventListener
    public void handleTenantFilter(EntityManagerFactory emf) {
        EntityManager em = emf.createEntityManager();
        
        // 设置租户过滤条件
        String tenantId = TenantContext.getCurrentTenant();
        if (tenantId != null) {
            em.setProperty("tenant.id", tenantId);
        }
    }
}

Hibernate过滤器配置

java 复制代码
@Entity
@FilterDef(name = "tenantFilter", parameters = @ParamDef(name = "tenantId", type = "string"))
@Filter(name = "tenantFilter", condition = "tenant_id = :tenantId")
public class User extends TenantAwareEntity {
    // entity fields
}

多租户事务管理

租户事务管理器

java 复制代码
@Configuration
@EnableTransactionManagement
public class TenantTransactionConfig {
    
    @Bean
    public PlatformTransactionManager tenantTransactionManager() {
        return new DataSourceTransactionManager(tenantDataSource());
    }
    
    @Bean
    public TransactionTemplate tenantTransactionTemplate() {
        return new TransactionTemplate(tenantTransactionManager());
    }
}

缓存策略与租户隔离

租户缓存配置

Redis租户缓存配置

java 复制代码
@Configuration
@EnableCaching
public class TenantCacheConfig {
    
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        RedisCacheManager.Builder builder = RedisCacheManager
            .RedisCacheManagerBuilder
            .fromConnectionFactory(connectionFactory)
            .cacheDefaults(cacheConfiguration());
            
        return builder.build();
    }
    
    private RedisCacheConfiguration cacheConfiguration() {
        return RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(60))
            .serializeKeysWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new GenericJackson2JsonRedisSerializer()));
    }
}

租户缓存键策略

租户缓存键生成器

java 复制代码
@Component
public class TenantCacheKeyGenerator implements KeyGenerator {
    
    @Override
    public Object generate(Object target, Method method, Object... params) {
        String tenantId = TenantContext.getCurrentTenant();
        String className = target.getClass().getSimpleName();
        String methodName = method.getName();
        
        StringBuilder keyBuilder = new StringBuilder();
        keyBuilder.append(tenantId).append(":");
        keyBuilder.append(className).append(":");
        keyBuilder.append(methodName);
        
        for (Object param : params) {
            keyBuilder.append(":").append(param.toString());
        }
        
        return keyBuilder.toString();
    }
}

缓存服务实现

租户缓存服务

java 复制代码
@Service
public class TenantCacheService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    public void put(String key, Object value) {
        String tenantId = TenantContext.getCurrentTenant();
        String tenantKey = tenantId + ":" + key;
        redisTemplate.opsForValue().set(tenantKey, value);
    }
    
    public Object get(String key) {
        String tenantId = TenantContext.getCurrentTenant();
        String tenantKey = tenantId + ":" + key;
        return redisTemplate.opsForValue().get(tenantKey);
    }
    
    public void evict(String key) {
        String tenantId = TenantContext.getCurrentTenant();
        String tenantKey = tenantId + ":" + key;
        redisTemplate.delete(tenantKey);
    }
    
    public void evictByPattern(String pattern) {
        String tenantId = TenantContext.getCurrentTenant();
        String tenantPattern = tenantId + ":" + pattern;
        
        Set<String> keys = redisTemplate.keys(tenantPattern);
        if (!keys.isEmpty()) {
            redisTemplate.delete(keys);
        }
    }
}

安全与权限控制

租户权限管理

租户权限实体

java 复制代码
@Entity
@Table(name = "tenant_permissions")
public class TenantPermission {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "tenant_id", nullable = false)
    private String tenantId;
    
    @Column(name = "user_id", nullable = false)
    private Long userId;
    
    @Column(name = "resource", nullable = false)
    private String resource;
    
    @Column(name = "action", nullable = false)
    private String action;
    
    @Column(name = "granted", nullable = false)
    private Boolean granted;
    
    // constructors, getters, setters
}

租户权限服务

java 复制代码
@Service
public class TenantPermissionService {
    
    @Autowired
    private TenantPermissionRepository permissionRepository;
    
    public boolean hasPermission(String resource, String action) {
        String tenantId = TenantContext.getCurrentTenant();
        Long userId = getCurrentUserId();
        
        Optional<TenantPermission> permission = permissionRepository
            .findByTenantIdAndUserIdAndResourceAndAction(
                tenantId, userId, resource, action);
                
        return permission.map(TenantPermission::getGranted).orElse(false);
    }
    
    public void grantPermission(Long userId, String resource, String action) {
        String tenantId = TenantContext.getCurrentTenant();
        
        TenantPermission permission = new TenantPermission();
        permission.setTenantId(tenantId);
        permission.setUserId(userId);
        permission.setResource(resource);
        permission.setAction(action);
        permission.setGranted(true);
        
        permissionRepository.save(permission);
    }
    
    private Long getCurrentUserId() {
        // 从安全上下文获取当前用户ID
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && authentication.getPrincipal() instanceof UserDetails) {
            UserDetails userDetails = (UserDetails) authentication.getPrincipal();
            return Long.valueOf(userDetails.getUsername());
        }
        return null;
    }
}

租户安全拦截器

租户权限拦截器

java 复制代码
@Component
public class TenantSecurityInterceptor implements HandlerInterceptor {
    
    @Autowired
    private TenantPermissionService permissionService;
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        
        String tenantId = TenantContext.getCurrentTenant();
        String resource = getResourceFromRequest(request);
        String action = getActionFromRequest(request);
        
        if (!permissionService.hasPermission(resource, action)) {
            response.setStatus(HttpStatus.FORBIDDEN.value());
            response.getWriter().write("Access denied for tenant: " + tenantId);
            return false;
        }
        
        return true;
    }
    
    private String getResourceFromRequest(HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        // 解析资源名称
        return requestURI.split("/")[2]; // 假设格式为 /api/tenant/{resource}
    }
    
    private String getActionFromRequest(HttpServletRequest request) {
        String method = request.getMethod();
        return method.toLowerCase();
    }
}

性能优化与监控

租户性能监控

租户性能指标

java 复制代码
@Component
public class TenantPerformanceMetrics {
    
    private final MeterRegistry meterRegistry;
    private final Map<String, Timer> tenantTimers = new ConcurrentHashMap<>();
    
    public TenantPerformanceMetrics(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }
    
    public void recordRequest(String tenantId, String endpoint, Duration duration) {
        Timer timer = tenantTimers.computeIfAbsent(tenantId, 
            id -> Timer.builder("tenant.request.duration")
                .tag("tenant", id)
                .register(meterRegistry));
                
        timer.record(duration);
        
        // 记录租户特定指标
        Counter.builder("tenant.request.count")
            .tag("tenant", tenantId)
            .tag("endpoint", endpoint)
            .register(meterRegistry)
            .increment();
    }
    
    public void recordError(String tenantId, String errorType) {
        Counter.builder("tenant.error.count")
            .tag("tenant", tenantId)
            .tag("error.type", errorType)
            .register(meterRegistry)
            .increment();
    }
}

租户资源限制

租户资源限制器

java 复制代码
@Component
public class TenantResourceLimiter {
    
    private final Map<String, AtomicInteger> tenantRequestCounts = new ConcurrentHashMap<>();
    private final Map<String, Long> tenantLastReset = new ConcurrentHashMap<>();
    
    public boolean isWithinLimits(String tenantId, int maxRequestsPerMinute) {
        long currentTime = System.currentTimeMillis();
        long oneMinuteAgo = currentTime - 60000;
        
        // 重置计数器
        if (tenantLastReset.getOrDefault(tenantId, 0L) < oneMinuteAgo) {
            tenantRequestCounts.put(tenantId, new AtomicInteger(0));
            tenantLastReset.put(tenantId, currentTime);
        }
        
        AtomicInteger count = tenantRequestCounts.computeIfAbsent(tenantId, 
            k -> new AtomicInteger(0));
            
        return count.incrementAndGet() <= maxRequestsPerMinute;
    }
    
    public void recordRequest(String tenantId) {
        tenantRequestCounts.computeIfAbsent(tenantId, k -> new AtomicInteger(0))
            .incrementAndGet();
    }
}

租户缓存预热

租户缓存预热服务

java 复制代码
@Service
public class TenantCacheWarmupService {
    
    @Autowired
    private TenantCacheService cacheService;
    
    @Autowired
    private UserService userService;
    
    @EventListener
    public void handleTenantActivation(TenantActivatedEvent event) {
        String tenantId = event.getTenantId();
        
        // 预热用户数据
        List<User> users = userService.findAll();
        cacheService.put("users", users);
        
        // 预热配置数据
        Map<String, String> configs = loadTenantConfigs(tenantId);
        cacheService.put("configs", configs);
    }
    
    private Map<String, String> loadTenantConfigs(String tenantId) {
        // 加载租户配置
        return new HashMap<>();
    }
}

最佳实践

1. 租户隔离最佳实践

数据隔离原则

java 复制代码
// ✅ 好的实践:始终检查租户ID
@Service
public class UserService {
    
    public User findById(Long id) {
        String tenantId = TenantContext.getCurrentTenant();
        return userRepository.findByTenantIdAndId(tenantId, id)
            .orElseThrow(() -> new UserNotFoundException(id));
    }
}

// ❌ 不好的实践:忽略租户隔离
@Service
public class BadUserService {
    
    public User findById(Long id) {
        return userRepository.findById(id)  // 可能返回其他租户的数据
            .orElseThrow(() -> new UserNotFoundException(id));
    }
}

2. 性能优化最佳实践

连接池配置

yaml 复制代码
spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000

缓存策略

java 复制代码
@Service
public class OptimizedUserService {
    
    @Cacheable(value = "users", key = "#tenantId + ':' + #id")
    public User findById(String tenantId, Long id) {
        return userRepository.findByTenantIdAndId(tenantId, id)
            .orElseThrow(() -> new UserNotFoundException(id));
    }
    
    @CacheEvict(value = "users", key = "#tenantId + ':' + #user.id")
    public User updateUser(String tenantId, User user) {
        return userRepository.save(user);
    }
}

3. 安全最佳实践

租户验证

java 复制代码
@Component
public class TenantValidator {
    
    public void validateTenantAccess(String tenantId, String resourceId) {
        String currentTenant = TenantContext.getCurrentTenant();
        
        if (!currentTenant.equals(tenantId)) {
            throw new TenantAccessDeniedException(
                "Access denied: tenant mismatch");
        }
    }
    
    public void validateResourceOwnership(String tenantId, String resourceId) {
        // 验证资源是否属于当前租户
        if (!isResourceOwnedByTenant(resourceId, tenantId)) {
            throw new ResourceAccessDeniedException(
                "Resource does not belong to tenant");
        }
    }
}

常见问题解决

1. 租户上下文丢失

问题描述:异步操作中租户上下文丢失

解决方案

java 复制代码
@Service
public class AsyncTenantService {
    
    @Async
    public CompletableFuture<Void> processAsync(String tenantId) {
        // 手动设置租户上下文
        TenantContext.setCurrentTenant(tenantId);
        
        try {
            // 执行业务逻辑
            doProcess();
            return CompletableFuture.completedFuture(null);
        } finally {
            TenantContext.clear();
        }
    }
}

2. 缓存污染

问题描述:不同租户数据在缓存中混合

解决方案

java 复制代码
@Component
public class TenantAwareCacheManager {
    
    public void put(String key, Object value) {
        String tenantId = TenantContext.getCurrentTenant();
        String tenantKey = tenantId + ":" + key;
        cacheManager.getCache("default").put(tenantKey, value);
    }
    
    public Object get(String key) {
        String tenantId = TenantContext.getCurrentTenant();
        String tenantKey = tenantId + ":" + key;
        return cacheManager.getCache("default").get(tenantKey, Object.class);
    }
}

3. 事务回滚问题

问题描述:多租户事务回滚影响其他租户

解决方案

java 复制代码
@Service
@Transactional
public class TenantAwareService {
    
    @Transactional(rollbackFor = Exception.class)
    public void processTenantData(String tenantId) {
        try {
            TenantContext.setCurrentTenant(tenantId);
            // 执行业务逻辑
            doProcess();
        } catch (Exception e) {
            // 只回滚当前租户的数据
            throw new TenantProcessingException("Failed to process tenant data", e);
        } finally {
            TenantContext.clear();
        }
    }
}

总结

Spring MVC多租户架构与数据隔离是现代SaaS应用的核心技术。通过合理的设计和实现,可以实现:

核心优势

  • 数据安全:租户间数据完全隔离
  • 性能优化:共享基础设施,降低成本
  • 可扩展性:支持大量租户并发访问
  • 维护便利:统一代码库,集中管理

关键技术点

  • 租户识别与路由机制
  • 数据隔离策略选择
  • 缓存策略与租户隔离
  • 安全与权限控制
  • 性能监控与优化

实施建议

  1. 根据业务需求选择合适的隔离策略
  2. 建立完善的租户管理机制
  3. 实施严格的安全控制
  4. 持续监控和优化性能
  5. 制定详细的运维规范

通过本教程的学习,小伙伴们将掌握Spring MVC多租户架构的设计与实现,为构建企业级SaaS应用奠定坚实基础。

相关推荐
Knight_AL4 小时前
代理模式 vs AOP:支付服务中的日志增强实践(含执行顺序详解)
spring·代理模式
失散134 小时前
分布式专题——45 ElasticSearch基础数据管理详解
java·分布式·elasticsearch·架构
Zz_waiting.5 小时前
Spring Cloud 概述
后端·spring·spring cloud
心灵宝贝5 小时前
nginx-1.16.1-2.p01.ky10.sw_64.rpm 安装教程(详细步骤,适用于Kylin V10/SW64架构)
nginx·架构·kylin
西蓝花MQ5 小时前
Spring Cloud微服务篇面试题总结
spring·spring cloud·微服务
devnullcoffee5 小时前
深度解析:如何构建企业级电商数据采集架构?Pangolin API实战指南
架构·scrape api·pangolin api·亚马逊数据采集 api·amazon 爬虫
canonical_entropy5 小时前
告别异常继承树:从 NopException 的设计看“组合”模式如何重塑错误处理
后端·架构
凯子坚持 c7 小时前
通往Docker之路:从单机到容器编排的架构演进全景
docker·容器·架构
西陵14 小时前
Nx带来极致的前端开发体验——任务缓存
前端·javascript·架构