spring-boot

multiple datasources in springboot 3


I have working project with springboot 2, with 2 datasources configured as shown in here or here. I upgraded to springboot 3. All LocalContainerEntityManagerFactoryBean, DataSource, DataSourceProperties and PlatformTransactionManager remained the same. But when I run the project now, I'm getting:

PersistenceUnitInfo [
name: read-only
persistence provider classname: null
classloader: jdk.internal.loader.ClassLoaders$AppClassLoader@4e0e2f2a
excludeUnlistedClasses: true
JTA datasource: null
Non JTA datasource: net.ttddyy.dsproxy.support.ProxyDataSource@6f36267d
Transaction type: RESOURCE_LOCAL
PU root URL: file:…/target/classes/
Shared Cache Mode: UNSPECIFIED
Validation Mode: AUTO
Jar files URLs []
Managed classes names []
Mapping files names []
Properties []

Yes, if I create MWE in initializr, just put there spring.datasource.url = …, and otherwise rely on spring-magic, it will work.

Can someone advise what am I missing, what was changed in springboot 3 in configuring jpa, or what can I debug to find out why none jpa entity is discovered?

EDIT: So far I was able to get to this method, org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypesScanner#scan:

public PersistenceManagedTypes scan(String... packagesToScan) {
    ScanResult scanResult = new ScanResult();
    for (String pkg : packagesToScan) {
        scanPackage(pkg, scanResult);
    }
    return scanResult.toJpaManagedTypes();
}

in springboot2 the scanPackage discovered entities in given package, in springboot3 it does not find anything. This class is new in springboot 3 so either it requires something new in order to scan actually work, or it does not work.

EDIT 2: full configuration:

@Bean
@Primary
@ConfigurationProperties("appname.datasource.main")
public DataSourceProperties mainDataSourceProperties() {
    return new DataSourceProperties();
}

@Bean(destroyMethod = "")
@Primary
public DataSource mainDataSource(ThreadQueryCountHolder threadQueryCountHolder,
                                 Environment environment,
                                 SqlFailedQueryExecutionListener sqlFailedQueryExecutionListener) {
    DataSourceProperties dataSourceProperties = mainDataSourceProperties();

    HikariDataSource hikariDataSource = dataSourceProperties
            .initializeDataSourceBuilder().type(HikariDataSource.class).build();

    Binder.get(environment).bind("appname.datasource.main.hikari", Bindable.ofInstance(hikariDataSource));

    return wrapWithDataSourceProxy(dataSourceProperties,
            hikariDataSource,
            threadQueryCountHolder,
            sqlFailedQueryExecutionListener);
}

@Primary
@Bean(name = MAIN_ENTITY_MANAGER_FACTORY)
public LocalContainerEntityManagerFactoryBean mainEntityManagerFactory(EntityManagerFactoryBuilder builder,
                                                                       ThreadQueryCountHolder threadQueryCountHolder,
                                                                       SpringJpaConfigProperties springJpaConfigProperties,
                                                                       Environment environment,
                                                                       SqlFailedQueryExecutionListener sqlFailedQueryExecutionListener,
                                                                       DefaultListableBeanFactory defaultListableBeanFactory) {
    LocalContainerEntityManagerFactoryBean em = builder
            .dataSource(mainDataSource(threadQueryCountHolder, environment, sqlFailedQueryExecutionListener))
            .packages(PredictorResultEntity.class, SelfieEntity.class)
            .build();

    JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
    em.setJpaPropertyMap(springJpaConfigProperties.getHibernateProperties());
    em.setJpaVendorAdapter(vendorAdapter);
    em.getJpaPropertyMap().put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(defaultListableBeanFactory));

    return em;
}

@Primary
@Bean(MAIN_TRANSACTION_MANAGER)
public PlatformTransactionManager mainTransactionManager(@Qualifier(MAIN_ENTITY_MANAGER_FACTORY)
                                                                     LocalContainerEntityManagerFactoryBean mainEntityManagerFactory) {
    return new JpaTransactionManager(Objects.requireNonNull(mainEntityManagerFactory.getObject()));
}

@Primary
@Bean(MAIN_JDBC_TEMPLATE)
public JdbcTemplate jdbcTemplate(ThreadQueryCountHolder threadQueryCountHolder,
                                 Environment environment,
                                 SqlFailedQueryExecutionListener sqlFailedQueryExecutionListener) {
    return new JdbcTemplate(mainDataSource(threadQueryCountHolder, environment,
            sqlFailedQueryExecutionListener));
}

@Bean
public ThreadQueryCountHolder threadQueryCountHolder() {
    return new ThreadQueryCountHolder();
}



private static DataSource wrapWithDataSourceProxy(DataSourceProperties dataSourceProperties,
                                                  DataSource dataSource,
                                                  ThreadQueryCountHolder threadQueryCountHolder,
                                                  SqlFailedQueryExecutionListener sqlFailedQueryExecutionListener) {
    return ProxyDataSourceBuilder.create(dataSource)
            .asJson()
            .logSlowQueryBySlf4j(20L, TimeUnit.SECONDS, SLF4JLogLevel.WARN, "SlowSql")
            .name(dataSourceProperties.getName())
            .countQuery(threadQueryCountHolder)
            .logQueryBySlf4j(SLF4JLogLevel.DEBUG)
            .listener(sqlFailedQueryExecutionListener)
            .build();
}

EDIT 3: pom.xml inherits from spring parent:

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>3.3.1</version>
  <relativePath/> <!-- lookup parent from repository -->
</parent>

and only dependencies are:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
  <groupId>org.hibernate.orm</groupId>
  <artifactId>hibernate-jpamodelgen</artifactId>
  <optional>true</optional>
</dependency>

<dependency>
  <groupId>org.hibernate.orm</groupId>
  <artifactId>hibernate-envers</artifactId>
</dependency>

Solution

  • Cause of not seeing entities in my case was using

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-indexer</artifactId>
      <version>5.3.20</version>
      <optional>true</optional>
    </dependency>
    

    this was part of project, and I overlooked during upgrade, that this dependency versino was not taken from BOM. Thus it wasn't updated. It kinda worked, but generated caches was corrupted, which was the reason why no entities was discovered.