javaspringoracle-databasespring-bootoracle-aq

Spring boot with Oracle AQ and Universal Connection Pool


I am using Oracle AQ with spring boot with as follows:

Gradle dependency:

implementation "com.oracle.database.spring:oracle-spring-boot-starter-aqjms:23.4.0"

JmsConfiguration:

    @Configuration
    @EnableJms
    public class JmsConfiguration {
    
        @Bean
        public ConnectionFactory connectionFactory(DataSource dataSource) {
            return AQjmsFactory.getQueueConnectionFactory(dataSource);
        }

Now I try to set up some properties for Oracle Universal Connection Pool via application.yaml based on this oracle docs, but properties are not applied:

spring:
  datasource:
    url: jdbc:oracle:thin:@localhost:1521/ORCLPDB1
    username: AQ_USER
    password: your_password
    oracleucp:
      initial-pool-size: 5
      min-pool-size: 10
      max-pool-size: 30
      connection-wait-timeout: 2
      connection-factory-class-name: oracle.jdbc.pool.OracleDataSource
      connection-pool-name: some_pool_name
    type: oracle.ucp.jdbc.PoolDataSource

Whereas if I am setting them via code, then it works:

import jakarta.jms.ConnectionFactory;
import jakarta.jms.JMSException;
import oracle.jakarta.jms.AQjmsFactory;
import oracle.ucp.jdbc.PoolDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.annotation.EnableJms;

import java.sql.SQLException;

@Configuration
@EnableJms
public class JmsConfiguration {

    @Bean
    public ConnectionFactory connectionFactory(PoolDataSource poolDataSource) throws JMSException, SQLException {
        poolDataSource.setInitialPoolSize(5);
        poolDataSource.setMinPoolSize(10);
        poolDataSource.setMaxPoolSize(30);
        poolDataSource.setConnectionWaitTimeout(3000);

        return AQjmsFactory.getQueueConnectionFactory(poolDataSource);
    }

To see if the settings were applied or not, I use this:

import oracle.ucp.jdbc.PoolDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.context.event.EventListener;

import java.sql.SQLException;
import java.util.List;

@SpringBootApplication
@ConfigurationPropertiesScan
public class RequestBufferingServiceApplication {

    @Autowired
    PoolDataSource poolDataSource;

    public static void main(String[] args) {
        SpringApplication.run(RequestBufferingServiceApplication.class, args);
    }

    @EventListener(ApplicationReadyEvent.class)
    public void foo() throws SQLException {
        System.out.println("------");
        System.out.println("Init pool size: " + poolDataSource.getInitialPoolSize());
        System.out.println("Min pool size: " + poolDataSource.getMinPoolSize());
        System.out.println("Max pool size: " + poolDataSource.getMaxPoolSize());
        System.out.println("ConnectionWaitTimeout: " + poolDataSource.getConnectionWaitTimeout());
        System.out.println("------");
    }
}

How to apply Oracle UCP properties via application.yaml properly?

EDIT:

Output with no impact of application.yaml:

------
Init pool size: 0
Min pool size: 0
Max pool size: 2147483647
ConnectionWaitTimeout: 3
------

Output with impact of java config (desired):

------
Init pool size: 5
Min pool size: 10
Max pool size: 30
ConnectionWaitTimeout: 2
------

build.gradle:

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.3.3'
    id 'io.spring.dependency-management' version '1.1.6'
    id 'io.freefair.lombok' version '8.11'
    id "io.qameta.allure-report" version "2.11.2"
    id 'org.sonarqube' version '5.0.0.4638'
    id 'jacoco'
}

group = 'de.telekom.mff'

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}

bootJar {
    archiveFileName = "request-buffering-service.${archiveExtension.get()}"
}

jar {
    enabled = true
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

ext {
    versions = [
            oracleSpringBootStarterAqjms: "23.4.0",
            allureCucumber7Jvm          : "2.27.0",
            testcontainers              : "1.20.1",
            springCloud                 : "2023.0.3",
            awaitility                  : "4.2.2",
            xmlUnit                     : "2.10.0",
            jsonUnitAssertj             : "3.2.2",
            wiremock                    : "4.1.4"
    ]
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${versions.springCloud}"
    }
}

dependencies {
    implementation "org.springframework.boot:spring-boot-starter-web"
    implementation "org.springframework.boot:spring-boot-starter-webflux"
    implementation "org.springframework.boot:spring-boot-starter-log4j2"
    implementation "org.springframework.boot:spring-boot-starter-actuator"
    implementation "org.springframework:spring-aspects"
    implementation "org.springframework.cloud:spring-cloud-starter-kubernetes-fabric8-config"
    implementation "org.springframework.cloud:spring-cloud-kubernetes-fabric8-leader"
    implementation "com.oracle.database.spring:oracle-spring-boot-starter-aqjms:${versions.oracleSpringBootStarterAqjms}"
    testImplementation "org.springframework.boot:spring-boot-starter-test"
    testImplementation "io.fabric8:kubernetes-server-mock"
    testImplementation "org.springframework.cloud:spring-cloud-contract-wiremock:${versions.wiremock}"
    testImplementation "org.testcontainers:oracle-free:${versions.testcontainers}"
    testImplementation "org.testcontainers:junit-jupiter:${versions.testcontainers}"
    testImplementation "org.awaitility:awaitility:${versions.awaitility}"
    testImplementation "org.xmlunit:xmlunit-core:${versions.xmlUnit}"
    testImplementation "io.qameta.allure:allure-cucumber7-jvm:${versions.allureCucumber7Jvm}"
    testImplementation "net.javacrumbs.json-unit:json-unit-assertj:${versions.jsonUnitAssertj}"
    testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}

Spring start debug log (using 23.4.0 aqjms starter, where url, user, password for db are auto configured via application.yaml):

Negative matches:

DataSourceAutoConfiguration: Did not match: - @ConditionalOnClass did not find required class 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType' (OnClassCondition)

DataSourceInitializationConfiguration: Did not match: - @ConditionalOnClass did not find required class 'org.springframework.jdbc.datasource.init.DatabasePopulator' (OnClassCondition)

DataSourceTransactionManagerAutoConfiguration: Did not match: - @ConditionalOnClass did not find required class 'org.springframework.jdbc.core.JdbcTemplate' (OnClassCondition)


Solution

  • The oracle-spring-boot-starter-aqjms contains auto-configuration itself which will configure the DataSource and ConnectionFactory.

    This configuration that kicks in when including this starter. The configuration will only apply the spring.datasource.url, spring.datasource.username and spring.datasource.password properties and ignore the other ones. Now as there is a pre-configured DataSource the auto-configuration from Spring Boot itself doesn't apply

    Version 23.4.0

    The oracle-spring-boot-starter-aqjms (version 23.4.0) contains auto configuration for the DataSource and ConnectionFactory. This configuration isn't part of the oracle-spring-boot-starter-ucp which will take the regular Spring Boot configuration.

    Which explains why it works.

    So if you want to use the full Spring Boot datasource configuration you would need to exclude the com.oracle.spring.aqjms.AqJmsAutoConfiguration from being applied.

    @SpringBootApplication(excludeName = {"com.oracle.spring.aqjms.AqJmsAutoConfiguration"})
    

    or you can add an exclude to your application.yml.

    spring:
      autoconfigure:
        exclude: com.oracle.spring.aqjms.AqJmsAutoConfiguration
    

    Version 24.4.0

    Now for the oracle-spring-boot-starter-aqjms version 24.4.0 things have changed and the auto configuration for the datasource moved to the oracle-spring-boot-starter-ucp instead. So for that to work you would need to exclude a different auto-config the com.oracle.spring.ucp.UCPAutoConfiguration which you can exclude in either of the ways mentioned above.

    With the exclude in place, when running the application you will now have a properly configured Oracle Pooled Datasource as the regular Spring Boot auto-configuration will kick in. Or at least the one you expected to be there.

    Caveat

    The Spring Boot auto configuration for a DataSource, at the moment of writing this answer, does require spring-jdbc to be present on the classpath. This is due the check for both javax.sql.DataSource and the org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType from spring-jdbc.

    See also https://github.com/spring-projects/spring-boot/issues/43786