【问题标题】:Multitenancy with spring data jdbcspring data jdbc 的多租户
【发布时间】:2022-11-18 19:10:49
【问题描述】:

我按照 tutorial 在我的 spring 应用程序上实现多租户,一切正常。

当我尝试用 spring-data-jdbc 替换纯 jdbcTemplate 以使用 crudRepositories 时,出现此错误:

java.lang.IllegalStateException: Cannot determine target DataSource for lookup key [null]

该异常似乎是由于我的应用程序中没有配置默认数据源引起的,并且在启动时似乎需要自动配置。 我试图在启动时排除 JdbcRepositoriesAutoConfiguration 但是,那样的话,spring 不会初始化存储库 bean。 任何想法如何解决这个问题?

【问题讨论】:

  • 我们至少需要完整的堆栈跟踪。到目前为止,看起来应该提供选择数据源的密钥的任何东西都没有。

标签: spring spring-data multi-tenant spring-data-jdbc


【解决方案1】:

我想我找到了解决方案。

没有默认的 DataSource,spring 自动配置无法初始化某些 bean,因为它无法确定 de dialect。

经过一番研究,这是我的配置:

@Configuration
@EnableJdbcRepositories(
  basePackages = {
    "it.lucafarsetti.multitenant"
  }
)
@EnableAutoConfiguration(exclude = {
  DataSourceAutoConfiguration.class,
  JdbcRepositoriesAutoConfiguration.class
})
public class MultitenantDataSourceConfiguration {

    private final GenericWebApplicationContext context;

    public MultitenantDataSourceConfiguration(GenericWebApplicationContext context) {
        this.context = context;
    }

    private static DataSource createDataSourceFrom(MFieldDataSource mFieldDataSource) {
        var dsp = new DataSourceProperties();
        dsp.setPassword(mFieldDataSource.getPassword());
        dsp.setUsername(mFieldDataSource.getUsername());
        dsp.setUrl(mFieldDataSource.getUrl());
        dsp.setDriverClassName(mFieldDataSource.getDriverClassName());

        return dsp.initializeDataSourceBuilder()
                  .type(HikariDataSource.class)
                  .build();
    }

    @Bean
    @Primary
    public DataSource multitenantDataSource(@Value("classpath:datasources.json") Resource fileName) throws IOException {
        Map<Object, Object> dataSources = initializeDataSources(fileName);

        dataSources.entrySet()
                   .forEach(tenant -> registerTenantDatasourceBean((String) tenant.getKey(), (DataSource) tenant.getValue()));

        var mds = new MultitenantDataSource();
        mds.setTargetDataSources(dataSources);

        return mds;
    }

    @Bean
    public Dialect jdbcDialect() {
        return MariaDbDialect.INSTANCE;
    }

    @Bean
    public NamedParameterJdbcOperations jdbcOperationsDataBase1(DataSource dataSourceGlobal) {
        return new NamedParameterJdbcTemplate(dataSourceGlobal);
    }

    @Bean
    public JdbcConverter jdbcConverter(JdbcMappingContext mappingContext,
                                      NamedParameterJdbcOperations jdbcOperationsDataBase1,
                                       @Lazy RelationResolver relationResolver,
                                       JdbcCustomConversions conversions,
                                       Dialect dialect) {

        DefaultJdbcTypeFactory jdbcTypeFactory = new DefaultJdbcTypeFactory(jdbcOperationsDataBase1.getJdbcOperations());
        return new BasicJdbcConverter(mappingContext, relationResolver, conversions, jdbcTypeFactory,
                                      dialect.getIdentifierProcessing());
    }

    @Bean
    public PlatformTransactionManager transactionManager(final DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    public JdbcCustomConversions jdbcCustomConversions() {
        return new JdbcCustomConversions();
    }

    @Bean
    public NamingStrategy namingStrategy() {
        return NamingStrategy.INSTANCE;
    }

    @Bean
    public JdbcMappingContext jdbcMappingContext(NamingStrategy namingStrategy, JdbcCustomConversions customConversions) {
        JdbcMappingContext mappingContext = new JdbcMappingContext(namingStrategy);
        mappingContext.setSimpleTypeHolder(customConversions.getSimpleTypeHolder());
        return mappingContext;
    }

    private void registerTenantDatasourceBean(String key, DataSource dataSource) {
        context.registerBean(key, DataSource.class, () -> dataSource);
    }

    private Map<Object, Object> initializeDataSources(Resource resource) throws IOException {
        List<MFieldDataSource> dataSources = new ObjectMapper()
                                               .readValue(resource
                                                            .getInputStream(), new TypeReference<>() {
                                               });
        return dataSources
                 .stream()
                 .collect(Collectors.toMap(
                   e -> e.getId(),
                   e -> createDataSourceFrom(e))
                 );
    }

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-01-10
    • 2017-03-05
    • 1970-01-01
    • 2014-04-19
    • 2019-01-03
    • 1970-01-01
    • 2017-11-19
    相关资源
    最近更新 更多