构建租户之城:Spring Boot如何实现多租户架构

构建租户之城:Spring Boot如何实现多租户架构_第1张图片

文章目录

    • 1. 数据隔离
    • 2. 租户识别
    • 3. 动态数据源切换

在 Spring Boot 中实现多租户架构需要考虑数据隔离、租户识别和动态数据源切换等方面。下面是一些优雅的实现方式:

1. 数据隔离

使用数据库级别的数据隔离是实现多租户架构的一种常见方式。可以为每个租户创建单独的数据库,或者使用数据库中的分表或分库来实现数据隔离。在 Spring Boot 中,可以使用多数据源配置来管理不同租户的数据库连接。

数据隔离是指在多租户架构中,保持各个租户之间的数据相互独立,确保一个租户的数据不会被其他租户访问或篡改。常用的方式是通过数据库级别的数据隔离来实现,即为每个租户创建独立的数据库、分表、分库等。

以下是一个简单的示例代码,演示如何在 Spring Boot 中使用多数据源配置实现数据隔离:

  1. 创建多个数据库配置文件:

    • application.properties:
    spring.datasource.primary.url=jdbc:mysql://localhost:3306/db_primary
    spring.datasource.primary.username=root
    spring.datasource.primary.password=password
    
    spring.datasource.secondary.url=jdbc:mysql://localhost:3306/db_secondary
    spring.datasource.secondary.username=root
    spring.datasource.secondary.password=password
    
  2. 创建多数据源配置类:

    @Configuration
    public class DataSourceConfig {
    
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource.primary")
        public DataSource primaryDataSource() {
            return DataSourceBuilder.create().build();
        }
    
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource.secondary")
        public DataSource secondaryDataSource() {
            return DataSourceBuilder.create().build();
        }
    }
    
  3. 创建基于数据源的 JdbcTemplate:

    @Component
    public class TenantJdbcTemplate {
    
        private final Map<String, JdbcTemplate> jdbcTemplateMap;
    
        public TenantJdbcTemplate(DataSourceConfig dataSourceConfig) {
            jdbcTemplateMap = new HashMap<>();
            DataSource primaryDataSource = dataSourceConfig.primaryDataSource();
            DataSource secondaryDataSource = dataSourceConfig.secondaryDataSource();
            // 其他数据源...
    
            jdbcTemplateMap.put("primary", new JdbcTemplate(primaryDataSource));
            jdbcTemplateMap.put("secondary", new JdbcTemplate(secondaryDataSource));
            // 其他数据源...
        }
    
        public JdbcTemplate getJdbcTemplate(String tenant) {
            return jdbcTemplateMap.get(tenant);
        }
    }
    
  4. 在服务类中使用不同租户的 JdbcTemplate:

    @Service
    public class ProductService {
    
        private final TenantJdbcTemplate tenantJdbcTemplate;
    
        public ProductService(TenantJdbcTemplate tenantJdbcTemplate) {
            this.tenantJdbcTemplate = tenantJdbcTemplate;
        }
    
        public List<Product> getProductsByTenant(String tenant) {
            JdbcTemplate jdbcTemplate = tenantJdbcTemplate.getJdbcTemplate(tenant);
            String query = "SELECT * FROM products";
            return jdbcTemplate.query(query, (rs, rowNum) -> {
                Product product = new Product();
                product.setId(rs.getInt("id"));
                product.setName(rs.getString("name"));
                // 设置其他属性...
                return product;
            });
        }
    }
    

在上述示例中,通过配置多个数据源并创建对应的 JdbcTemplate,每个租户可以使用自己的数据源进行数据访问。在服务类中,根据租户标识获取对应的 JdbcTemplate,并执行查询操作。

需要注意的是,示例中仅展示了基于 Spring Boot 的数据隔离实现方式,实际应用中可能需要结合更多的业务需求和安全措施进行调整和扩展。

2. 租户识别

在多租户架构中,需要识别当前请求所属的租户,并根据租户信息进行相应的数据访问操作。这可以通过在请求头、请求参数、用户认证信息中传递租户标识来实现。在 Spring Boot 中,可以使用拦截器、过滤器或注解来提取租户标识,并将其存储在 ThreadLocal 等线程上下文中供后续使用。

租户识别是在多租户架构中,根据当前请求的租户标识来确定请求所属的租户,并使用相应的租户信息进行数据访问和处理。租户标识可以通过请求头、请求参数、用户认证信息等方式传递。

以下是一个简单的示例代码,演示如何在 Spring Boot 中实现租户识别:

  1. 创建租户上下文类:
public class TenantContext {

    private static final ThreadLocal<String> tenantIdHolder = new ThreadLocal<>();

    public static String getTenantId() {
        return tenantIdHolder.get();
    }

    public static void setTenantId(String tenantId) {
        tenantIdHolder.set(tenantId);
    }

    public static void clear() {
        tenantIdHolder.remove();
    }
}
  1. 创建拦截器或过滤器实现租户识别:
@Component
public class TenantInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String tenantId = // 从请求中获取租户标识,例如从请求头或请求参数中获取
        TenantContext.setTenantId(tenantId);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        TenantContext.clear();
    }
}

注意:如果使用过滤器,则需要在Spring Boot配置类中添加对该过滤器的配置。

  1. 在服务类中使用租户标识进行数据访问:
@Service
public class ProductService {

    private final JdbcTemplate jdbcTemplate;

    public ProductService(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public List<Product> getProductsByTenant(String tenantId) {
        String query = "SELECT * FROM products WHERE tenant_id = ?";
        return jdbcTemplate.query(query, new Object[]{tenantId}, (rs, rowNum) -> {
            Product product = new Product();
            product.setId(rs.getInt("id"));
            product.setName(rs.getString("name"));
            // 设置其他属性...
            return product;
        });
    }
}

在上述示例中,我们使用了一个 TenantContext 类来保存当前请求的租户标识。通过拦截器或过滤器,我们可以从请求中提取租户标识并将其设置到 TenantContext 中。在服务类中,可以通过 TenantContext.getTenantId() 来获取当前请求的租户标识,并根据它进行数据访问。

需要注意的是,上述示例中仅展示了基于 Spring Boot 的租户识别实现方式,实际应用中可能需要结合更多的业务需求和安全措施进行调整和扩展。

3. 动态数据源切换

多租户架构中,需要根据当前请求的租户动态切换数据源。Spring Boot 提供了多种方式来实现动态数据源切换,例如使用 AbstractRoutingDataSource 或自定义注解配合 AOP 等方式。在数据源切换时,可以根据租户标识选择相应的数据源,并将数据源绑定到当前线程的上下文中。

动态数据源切换是指在运行时根据需求动态切换使用不同的数据源。通常在多租户、多数据库、读写分离等场景下,会需要动态切换数据源。Spring框架提供了AbstractRoutingDataSource类来实现动态数据源切换。

以下是一个简单的示例代码,演示如何在 Spring Boot 中实现动态数据源切换:

  1. 创建动态数据源配置类:
@Configuration
public class DynamicDataSourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public DynamicDataSource dynamicDataSource(DataSource primaryDataSource, DataSource secondaryDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("primary", primaryDataSource);
        targetDataSources.put("secondary", secondaryDataSource);

        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSources);
        dataSource.setDefaultTargetDataSource(primaryDataSource);
        return dataSource;
    }
}
  1. 创建动态数据源类:
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSourceKey();
    }
}
  1. 创建数据源上下文类:
public class DataSourceContextHolder {

    private static final ThreadLocal<String> dataSourceKeyHolder = new ThreadLocal<>();

    public static void setDataSourceKey(String dataSourceKey) {
        dataSourceKeyHolder.set(dataSourceKey);
    }

    public static String getDataSourceKey() {
        return dataSourceKeyHolder.get();
    }

    public static void clearDataSourceKey() {
        dataSourceKeyHolder.remove();
    }
}
  1. 创建拦截器或过滤器获取数据源标识:
@Component
public class DataSourceInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String dataSourceKey = // 根据请求信息获取数据源标识,例如从请求头、请求参数等
        DataSourceContextHolder.setDataSourceKey(dataSourceKey);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        DataSourceContextHolder.clearDataSourceKey();
    }
}

注意:如果使用过滤器,则需要在Spring Boot配置类中添加对该过滤器的配置。

  1. 在服务类中使用动态数据源:
@Service
public class ProductService {

    private final JdbcTemplate jdbcTemplate;

    public ProductService(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public List<Product> getProducts() {
        String query = "SELECT * FROM products";
        return jdbcTemplate.query(query, (rs, rowNum) -> {
            Product product = new Product();
            product.setId(rs.getInt("id"));
            product.setName(rs.getString("name"));
            // 设置其他属性...
            return product;
        });
    }
}

在上述示例中,我们通过配置多个数据源,并将其放入 DynamicDataSource 中。在 determineCurrentLookupKey() 方法中,根据 DataSourceContextHolder 中保存的数据源标识来决定使用哪个数据源。通过拦截器或过滤器,我们可以从请求中提取数据源标识并设置到 DataSourceContextHolder 中。在服务类中,无需显式指定数据源,直接使用 JdbcTemplate 进行数据访问即可。

需要注意的是,上述示例中仅展示了基于 Spring Boot 的动态数据源切换实现方式,实际应用中可能需要结合更多的业务需求和安全措施进行调整和扩展。

除了上述核心实现方式外,还可以考虑其他辅助功能的实现,例如租户注册、租户上下文缓存、数据初始化等。此外,安全性也是多租户架构需要考虑的重要因素,需要合理设计权限控制和数据访问策略。

你可能感兴趣的:(spring,boot,架构,后端)