You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot按租户分库多租户:如何取消默认数据源并延迟EntityManager创建?

多租户分库方案:避免默认数据源与延迟EntityManager初始化

问题描述

我正在评估采用按租户分库的多租户解决方案,参考Spring Data官方的多租户分库示例实现了基于AbstractRoutingDataSource的原型,但当前实现要求配置默认数据库——这仅仅是为了让Spring Boot(Spring Data JPA)能启动并初始化EntityManager。实现代码如下:

@Component
public class TenantRoutingDatasource extends AbstractRoutingDataSource {

    @Autowired private TenantIdentifierResolver tenantIdentifierResolver;

    TenantRoutingDatasource() {

        setDefaultTargetDataSource(createEmbeddedDatabase("default"));

        HashMap<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("VMWARE", createEmbeddedDatabase("VMWARE"));
        targetDataSources.put("PIVOTAL", createEmbeddedDatabase("PIVOTAL"));
        setTargetDataSources(targetDataSources);
    }

    @Override
    protected String determineCurrentLookupKey() {
        return tenantIdentifierResolver.resolveCurrentTenantIdentifier();
    }

    private EmbeddedDatabase createEmbeddedDatabase(String name) {

        return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).setName(name).addScript("manual-schema.sql")
                .build();
    }
}

请问是否有办法避免配置默认数据源?能否让Spring Boot延迟EntityManager的创建,直到仓库等组件调用时再执行?


解决方案

一、避免配置实际默认数据源

1. 用占位符+异常拦截替代实际默认数据源

你不需要配置真实的默认嵌入式数据库,而是可以给AbstractRoutingDataSource设置一个空的数据源占位符,同时在租户标识未找到时抛出明确的业务异常,防止无效请求继续执行:

修改后的TenantRoutingDatasource示例:

@Component
public class TenantRoutingDatasource extends AbstractRoutingDataSource {

    @Autowired private TenantIdentifierResolver tenantIdentifierResolver;

    TenantRoutingDatasource() {
        // 设置空数据源占位符,仅用于Spring初始化流程
        setDefaultTargetDataSource(new DataSource() {
            @Override
            public Connection getConnection() throws SQLException {
                throw new IllegalStateException("未指定有效的租户标识");
            }

            @Override
            public Connection getConnection(String username, String password) throws SQLException {
                throw new IllegalStateException("未指定有效的租户标识");
            }

            @Override
            public <T> T unwrap(Class<T> iface) throws SQLException {
                throw new UnsupportedOperationException();
            }

            @Override
            public boolean isWrapperFor(Class<?> iface) throws SQLException {
                return false;
            }
        });

        HashMap<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("VMWARE", createEmbeddedDatabase("VMWARE"));
        targetDataSources.put("PIVOTAL", createEmbeddedDatabase("PIVOTAL"));
        setTargetDataSources(targetDataSources);
    }

    @Override
    protected String determineCurrentLookupKey() {
        String tenantId = tenantIdentifierResolver.resolveCurrentTenantIdentifier();
        // 如果租户标识为空,直接抛出异常,避免触发默认数据源
        if (tenantId == null || tenantId.isBlank()) {
            throw new IllegalStateException("当前请求未携带租户标识");
        }
        return tenantId;
    }

    private EmbeddedDatabase createEmbeddedDatabase(String name) {
        return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).setName(name).addScript("manual-schema.sql")
                .build();
    }
}

这种方式既满足了Spring初始化AbstractRoutingDataSource的要求,又不会创建无用的默认数据库,同时能在租户标识缺失时快速报错。

2. 启动时预加载所有租户数据源(适用于租户固定场景)

如果你的系统在启动时就能获取所有租户的配置信息,可以提前初始化所有租户数据源,同时在Spring启动阶段临时设置一个有效的租户上下文,让EntityManager初始化时能绑定到一个真实的数据源。这种方式仅适用于租户数量固定且启动前已知的场景。


二、延迟EntityManager的创建

通过以下配置和代码调整,可以让Spring Boot延迟EntityManager的初始化,直到首次调用仓库组件时再执行:

1. 配置JPA参数禁用启动时元数据加载

在application.properties中添加以下配置:

# 禁止Hibernate在启动时加载JDBC元数据
spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false
# 延迟数据源初始化到应用启动后
spring.jpa.defer-datasource-initialization=true
# 禁用自动DDL,避免启动时执行数据库脚本
spring.jpa.hibernate.ddl-auto=none

2. 手动配置EntityManagerFactory并开启懒初始化

自定义LocalContainerEntityManagerFactoryBean,设置懒初始化属性,让Spring只在首次使用EntityManager时创建该Bean:

@Configuration
public class JpaConfig {

    @Bean
    @Lazy
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource);
        em.setPackagesToScan("com.your.project.entity"); // 替换为你的实体类包路径
        em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
        
        // 配置JPA属性
        Properties jpaProperties = new Properties();
        jpaProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
        em.setJpaProperties(jpaProperties);
        
        // 开启懒初始化
        em.setLazyInit(true);
        return em;
    }
}

3. 给Spring Data JPA仓库添加懒加载注解

在你的仓库接口上添加@Lazy注解,确保仓库Bean也是懒初始化的,只有当首次调用仓库方法时才会触发EntityManager的创建:

@Lazy
public interface TenantEntityRepository extends JpaRepository<TenantEntity, Long> {
    // 仓库方法定义
}

内容的提问来源于stack exchange,提问作者Martin Ahrer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 19:37:15