spring-projects / spring-data-relational

Spring Data Relational. Home of Spring Data JDBC and Spring Data R2DBC.
https://spring.io/projects/spring-data-jdbc
Apache License 2.0
737 stars 339 forks source link

@EntityScan fails when setting up multiple datasources #1807

Closed alorseg closed 4 weeks ago

alorseg commented 1 month ago

After properly configuring two jdbc datasources and their repositories, the entity scan seems to fail, and all the entity callback features won't work.

Here is the datasource config class:

@EntityScan
@Configuration
public class Bar {

@Bean
@ConfigurationProperties("spring.datasource.bar")
public DataSourceProperties barDataSourceProperties() {
    return new DataSourceProperties();
}

@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public HikariDataSource barHikariDataSource() {
    return barDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
}

@Bean
public DataSource barDataSource() {
    return barDataSourceProperties()
            .initializeDataSourceBuilder()
            .build();
}

@Bean
public NamedParameterJdbcTemplate barNamedParameterJdbcTemplate(@Qualifier("barDataSource") DataSource dataSource) {
    return new NamedParameterJdbcTemplate(dataSource);
}

@Bean
public DataAccessStrategy barDataAccessStrategy(@Qualifier("barNamedParameterJdbcTemplate") NamedParameterJdbcOperations operations,
                                                JdbcConverter jdbcConverter,
                                                JdbcMappingContext context,
                                                Dialect dialect) {

    final SqlGeneratorSource sqlGeneratorSource =
            new SqlGeneratorSource(
                    context,
                    jdbcConverter,
                    dialect
            );

    final DataAccessStrategyFactory factory =
            new DataAccessStrategyFactory(
                    sqlGeneratorSource,
                    jdbcConverter,
                    operations,
                    new SqlParametersFactory(context, jdbcConverter),
                    new InsertStrategyFactory(operations, dialect)
            );

    return factory.create();
}

And my AuditableEntityCallback (which any entity will trigger):

@Component
public class AuditableEntityCallback implements BeforeSaveCallback<AuditableEntity> {
private final AuditorAware<String> auditorAware;

public AuditableEntityCallback(AuditorAware<String> auditorAware) {
    this.auditorAware = auditorAware;
}

@Override
public AuditableEntity onBeforeSave(AuditableEntity entity,
                                    MutableAggregateChange<AuditableEntity> entityChange) {

    String currentAuditor = auditorAware.getCurrentAuditor().orElse("system");
    LocalDateTime now = LocalDateTime.now();

    if (entity.getCreatedDate() == null) {
        entity.setCreatedBy(currentAuditor);
        entity.setCreatedDate(now.toString());
    }

    entity.setLastModifiedBy(currentAuditor);
    entity.setLastModifiedDate(now.toString());

    return entity;
}
}

Is there any chance to enhance this feature isntead of manually code the packagesToScan?

mp911de commented 1 month ago

@EntityScan isn't required for Spring Data JDBC. The code above doesn't show JdbcAggregateTemplate creation. How do you create that one?

alorseg commented 1 month ago

Is It needed even if there is some global config class extending AbstractJdbcConfiguration? And if so, could you provide an example?

mp911de commented 1 month ago

If you configure multiple data sources and multiple NamedParameterJdbcTemplates, then you should have also multiple instances of JdbcAggregateTemplate as JdbcAggregateTemplate invokes entity callbacks (alongside the repository layer).

With the little information you provided, that's all I can say. Any reproducer/example project would greatly increase access to more details.

alorseg commented 1 month ago

This is my fooConfig:

@Configuration
public class FooConfig {

@Bean
@ConfigurationProperties("spring.datasource.foo")
public DataSourceProperties fooDataSourceProperties() {
    return new DataSourceProperties();
}

@Bean
public DataSource fooDataSource() {
    return fooDataSourceProperties()
            .initializeDataSourceBuilder()
            .build();
}

@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource")
public HikariDataSource fooHikariDataSource() {
    return fooDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
}

@Bean
@Primary
public NamedParameterJdbcTemplate fooNamedParameterJdbcTemplate(
@Qualifier("fooDataSource") DataSource dataSource) {
    return new NamedParameterJdbcTemplate(dataSource);
}

@Bean
@Primary
public DataAccessStrategy fooDataAccessStrategy(
@Qualifier("fooNamedParameterJdbcTemplate") NamedParameterJdbcOperations operations,
                                                JdbcConverter jdbcConverter,
                                                JdbcMappingContext context,
                                                Dialect dialect) {

    final SqlGeneratorSource sqlGeneratorSource =
            new SqlGeneratorSource(
                    context,
                    jdbcConverter,
                    dialect
            );

    final DataAccessStrategyFactory factory =
            new DataAccessStrategyFactory(
                    sqlGeneratorSource,
                    jdbcConverter,
                    operations,
                    new SqlParametersFactory(context, jdbcConverter),
                    new InsertStrategyFactory(operations, dialect)
            );

    return factory.create();
}
}

Here is my global config for jdbcCustomConverters:

@Configuration
public class DataJdbcConvertersConfig extends AbstractJdbcConfiguration {

@Override
@NonNull
public List<?> userConverters() {
    return List.of(
            new firstCustomConverter(),
            new SecondCustomConverter());
}

@Bean
public AbstractJdbcConfiguration jdbcCustomConvertersConfiguration() {
    return new DataJdbcConvertersConfig();
}
}

And this is the jdbcAggregateTemplate I added to my barConfig and fooConfig (changing the qualifier beans):

@Bean
public JdbcAggregateTemplate barJdbcAggregateTemplate(
ApplicationEventPublisher publisher,
RelationalMappingContext context,
JdbcConverter converter,
@Qualifier("barDataAccessStrategy")
DataAccessStrategy dataAccessStrategy) {
    return new JdbcAggregateTemplate(publisher, context, converter, dataAccessStrategy);
}
mp911de commented 1 month ago

Alright, please create JdbcAggregateTemplate by using ApplicationContext instead of ApplicationEventPublisher. Passing in only the publisher will not initialize EntityCallbacks.

alorseg commented 4 weeks ago

Turns out it's a configuration problem, I'll close the thread, thank you very much @mp911de

Have a nice one!