0

I followed that tutorial to implement multitenancy on my spring application and everything works fine.

When I tried to replace pure jdbcTemplate with spring-data-jdbc to use crudRepositories i have this error:

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

That exception seems caused by there is no default datasource configured in my application and on startup seems required to the autoconfiguration. I tried to exclude JdbcRepositoriesAutoConfiguration on startup but, in that way, spring doesn't initialize repository bean. Any ideas how to solve this issue?

Luca Farsetti
  • 257
  • 2
  • 14
  • We'd need at least the full stack trace. So far it looks like whatever is supposed to provide the key for selecting the data source didn't. – Jens Schauder Nov 17 '22 at 10:07

1 Answers1

0

I think i found the solution.

Without a default DataSource, spring autoconfiguration can't initialize some beans because it's not able to determine de dialect.

After some research, this is my configuration:

@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))
                 );
    }

}
Luca Farsetti
  • 257
  • 2
  • 14