Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;

import java.util.HashMap;
import java.util.Map;

/**
Expand All @@ -52,6 +54,7 @@ public class DatasourceConfiguration extends BasicDataSource implements BasicJdb
private static final Logger LOG = LoggerFactory.getLogger(DatasourceConfiguration.class);
private final CalculatedSettings calculatedSettings;
private final String name;
private Map<String, ?> individualDsProperties = new HashMap<>();

/**
* Constructor.
Expand Down Expand Up @@ -165,6 +168,7 @@ public void setConnectionPropertiesString(@Property(name = "datasources.*.connec

@Override
public void setDataSourceProperties(@MapFormat(transformation = MapFormat.MapTransformation.FLAT, keyFormat = StringConvention.RAW) Map<String, ?> dsProperties) {
this.individualDsProperties = dsProperties;
if (dsProperties != null) {
dsProperties.forEach((s, o) -> {
if (o != null) {
Expand Down Expand Up @@ -194,4 +198,8 @@ void setEnabled(boolean enabled) {
throw new DisabledBeanException("The datasource \"" + name + "\" is disabled");
}
}

Map<String, ?> getIndividualDsProperties() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this exposed?

Copy link
Author

@jamesforwardnwboxed jamesforwardnwboxed Aug 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dstepanov The dbcp implementation needs to access the properties to work properly

return individualDsProperties;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2017-2020 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.configuration.jdbc.dbcp;

import io.micronaut.context.annotation.Requires;
import io.micronaut.context.event.BeanCreatedEvent;
import io.micronaut.context.event.BeanCreatedEventListener;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;

/**
* A bean created event listener that applies global datasource properties to
* {@link DatasourceConfiguration} beans when they are created. This modifier
* ensures that global properties defined under the "global.datasources.data-source-properties"
* configuration prefix are automatically applied to all datasource configurations,
* while preserving individual datasource-specific settings that take precedence.
*
* <p>The modifier only adds global properties that are not already present in the
* individual datasource configuration, ensuring that specific configurations always
* override global defaults. Properties with null values are ignored.</p>
*
* <p>This bean is only created when the "global.datasources.data-source-properties"
* configuration property is present.</p>
*
* @author James Forward
*/
@Requires(property = "global.datasources.data-source-properties")
@Singleton
public class GlobalDatasourceConfigModifier implements BeanCreatedEventListener<DatasourceConfiguration> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pls make final, package private and annotate @Internal


@Inject
GlobalDatasourceProperties globalDatasourceProperties;

@Override
public DatasourceConfiguration onCreated(BeanCreatedEvent<DatasourceConfiguration> event) {

DatasourceConfiguration configuration = event.getBean();
globalDatasourceProperties.getDataSourceProperties()
.forEach((key, value) -> {
if (value != null && !configuration.getIndividualDsProperties().containsKey(key)) {
configuration.addConnectionProperty(key, value);
}
}
);
return configuration;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2017-2020 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.configuration.jdbc.dbcp;

import io.micronaut.context.annotation.ConfigurationProperties;
import io.micronaut.context.annotation.Requires;
import io.micronaut.core.convert.format.MapFormat;
import io.micronaut.core.naming.conventions.StringConvention;

import java.util.HashMap;
import java.util.Map;

/**
* Configuration properties for global datasource settings that can be applied
* to all {@link DatasourceConfiguration} instances. This class binds configuration
* properties under the "global.datasources" prefix and provides a way to define
* common datasource properties that should be applied across all datasources
* in the application.
*
* <p>Properties defined here serve as defaults that can be overridden by
* individual datasource configurations. The primary use case is to avoid
* repetition when multiple datasources share common settings such as
* connection pool parameters, SSL settings, or application-specific properties.</p>
*
* <p>This bean is only created when the "global.datasources.data-source-properties"
* configuration property is present, ensuring it doesn't interfere with applications
* that don't use global datasource configuration.</p>
*
* @author James Forward
*/
@Requires(property = "global.datasources.data-source-properties")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think better name would be global-data-source-properties

Copy link
Author

@jamesforwardnwboxed jamesforwardnwboxed Aug 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dstepanov The idea was to have global at a higher level and then have the usual datasources properties underneath, which makes it extensible and more inline with the current setup, since these data source properties are already set at datasources.<datasourcename>.data-source-properties. For example, if we did want to set other properties that would usually be under datasources.<datasourcename>, such as a global username, it would be global.datasources.username

@ConfigurationProperties("global.datasources")
public class GlobalDatasourceProperties {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pls make final and add javadoc to methods

private Map<String, String> dataSourceProperties = new HashMap<>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this has to be mutatable?


public Map<String, String> getDataSourceProperties() {
return dataSourceProperties;
}

public void setDataSourceProperties(@MapFormat(transformation = MapFormat.MapTransformation.FLAT, keyFormat = StringConvention.RAW) Map<String, String> dataSourceProperties) {
this.dataSourceProperties.putAll(dataSourceProperties);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* Copyright 2017-2020 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//file:noinspection GroovyAccessibility
package io.micronaut.configuration.jdbc.dbcp

import io.micronaut.context.ApplicationContext
import io.micronaut.context.DefaultApplicationContext
import io.micronaut.context.env.MapPropertySource
import spock.lang.Specification

class GlobalDatasourcePropertiesSpec extends Specification {

void "test no global datasource configuration exists when no global properties are present"() {
given:
ApplicationContext applicationContext = new DefaultApplicationContext("test")
applicationContext.environment.addPropertySource(MapPropertySource.of(
'test',
['datasources.default.url': 'jdbc:h2:mem:default']
))
applicationContext.start()

when:
Optional<GlobalDatasourceProperties> properties = applicationContext.findBean(GlobalDatasourceProperties)
Optional<DatasourceConfiguration> datasourceConfig = applicationContext.findBean(DatasourceConfiguration)

then: "No global beans are created when no global configuration is present"
datasourceConfig.isPresent()
datasourceConfig.get().connectionProperties.isEmpty()
datasourceConfig.get().url == 'jdbc:h2:mem:default'
properties.isEmpty()

cleanup:
applicationContext.close()
}

void "test global datasource properties configuration creates correct beans"() {
given:
ApplicationContext applicationContext = new DefaultApplicationContext("test")
applicationContext.environment.addPropertySource(MapPropertySource.of(
'test',
['global.datasources.data-source-properties.ApplicationName': 'MyApp',
'global.datasources.data-source-properties.assumeMinServerVersion': '9.0',
'global.datasources.data-source-properties.reWriteBatchInserts': true]
))
applicationContext.start()

when:
GlobalDatasourceProperties properties = applicationContext.getBean(GlobalDatasourceProperties)

then: "GlobalDatasourceProperties bean is created with correct properties"
properties != null
properties.dataSourceProperties != null
properties.dataSourceProperties.size() == 3
properties.dataSourceProperties['ApplicationName'] == 'MyApp'
properties.dataSourceProperties['assumeMinServerVersion'] == '9.0'
properties.dataSourceProperties['reWriteBatchInserts'] == "true"

and: "GlobalDatasourceConfigModifier bean is also created"
applicationContext.containsBean(GlobalDatasourceConfigModifier)

cleanup:
applicationContext.close()
}

void "test global properties are applied to all datasource configurations"() {
given:
ApplicationContext applicationContext = new DefaultApplicationContext("test")
applicationContext.environment.addPropertySource(MapPropertySource.of(
'test',
['datasources.default.url': 'jdbc:h2:mem:default',
'datasources.secondary.url': 'jdbc:h2:mem:secondary',
'global.datasources.data-source-properties.ApplicationName': 'GlobalApp',
'global.datasources.data-source-properties.assumeMinServerVersion': '9.0']
))
applicationContext.start()

when:
def datasourceConfigs = applicationContext.getBeansOfType(DatasourceConfiguration)

then: "Global properties are applied to all DatasourceConfiguration beans"
datasourceConfigs.size() == 2
datasourceConfigs[0].connectionProperties['ApplicationName'] == 'GlobalApp'
datasourceConfigs[0].connectionProperties['assumeMinServerVersion'] == '9.0'
datasourceConfigs[1].connectionProperties['ApplicationName'] == 'GlobalApp'
datasourceConfigs[1].connectionProperties['assumeMinServerVersion'] == '9.0'

cleanup:
applicationContext.close()
}

void "test individual datasource properties override global properties"() {
given:
ApplicationContext applicationContext = new DefaultApplicationContext("test")
applicationContext.environment.addPropertySource(MapPropertySource.of(
'test',
['datasources.default.url': 'jdbc:h2:mem:default',
'datasources.default.data-source-properties.ApplicationName': 'SpecificApp',
'datasources.default.data-source-properties.specificProperty': 'specificValue',
'global.datasources.data-source-properties.ApplicationName': 'GlobalApp',
'global.datasources.data-source-properties.assumeMinServerVersion': '9.0',
'global.datasources.data-source-properties.globalProperty': 'globalValue']
))
applicationContext.start()

when:
DatasourceConfiguration datasourceConfig = applicationContext.getBean(DatasourceConfiguration)

then: "Individual properties override globals, but global properties are still added"
datasourceConfig != null
datasourceConfig.connectionProperties['ApplicationName'] == 'SpecificApp' // Overridden
datasourceConfig.connectionProperties['specificProperty'] == 'specificValue' // Individual property
datasourceConfig.connectionProperties['assumeMinServerVersion'] == '9.0' // Added from global
datasourceConfig.connectionProperties['globalProperty'] == 'globalValue' // Added from global

cleanup:
applicationContext.close()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2017-2020 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.configuration.jdbc.hikari;

import io.micronaut.context.annotation.Requires;
import io.micronaut.context.event.BeanCreatedEvent;
import io.micronaut.context.event.BeanCreatedEventListener;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;

/**
* A bean created event listener that applies global datasource properties to
* {@link DatasourceConfiguration} beans when they are created. This modifier
* ensures that global properties defined under the "global.datasources.data-source-properties"
* configuration prefix are automatically applied to all datasource configurations,
* while preserving individual datasource-specific settings that take precedence.
*
* <p>The modifier only adds global properties that are not already present in the
* individual datasource configuration, ensuring that specific configurations always
* override global defaults. Properties with null values are ignored.</p>
*
* <p>This bean is only created when the "global.datasources.data-source-properties"
* configuration property is present.</p>
*
* @author James Forward
*/
@Requires(property = "global.datasources.data-source-properties")
@Singleton
public class GlobalDatasourceConfigModifier implements BeanCreatedEventListener<DatasourceConfiguration> {

@Inject
GlobalDatasourceProperties globalDatasourceProperties;

@Override
public DatasourceConfiguration onCreated(BeanCreatedEvent<DatasourceConfiguration> event) {

DatasourceConfiguration configuration = event.getBean();
globalDatasourceProperties.getDataSourceProperties()
.forEach((key, value) -> {
if (value != null && !configuration.getDataSourceProperties().containsKey(key)) {
configuration.addDataSourceProperty(key, value);
}
}
);
return configuration;
}
}
Loading