Skip to content

Commit

Permalink
Make DataSource beans application-scoped
Browse files Browse the repository at this point in the history
So that we'll be able to postpone initialization to first access
in some cases, instead of doing it on startup.

This could be useful in particular for deactivated datasources:
we don't want to initialize those on startup, but we do want them
to fail on first use.

An alternative would have been to represent deactivated datasources
with a custom implementation of AgroalDataSource, like we currently do
with UnconfiguredDataSource, but that solution has serious problems,
in particular when we "forget" to implement some methods:
see quarkusio#36666
  • Loading branch information
yrodiere committed Dec 8, 2023
1 parent d86f0b1 commit c3597f3
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@

import javax.sql.XADataSource;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Default;
import jakarta.inject.Singleton;

import org.jboss.jandex.ClassType;
import org.jboss.jandex.DotName;
Expand Down Expand Up @@ -72,6 +72,7 @@ class AgroalProcessor {

private static final String OPEN_TELEMETRY_DRIVER = "io.opentelemetry.instrumentation.jdbc.OpenTelemetryDriver";
private static final DotName DATA_SOURCE = DotName.createSimple(javax.sql.DataSource.class.getName());
private static final DotName AGROAL_DATA_SOURCE = DotName.createSimple(AgroalDataSource.class.getName());

@BuildStep
void agroal(BuildProducer<FeatureBuildItem> feature) {
Expand Down Expand Up @@ -277,7 +278,8 @@ void generateDataSourceBeans(AgroalRecorder recorder,
SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem
.configure(AgroalDataSource.class)
.addType(DATA_SOURCE)
.scope(Singleton.class)
.addType(AGROAL_DATA_SOURCE)
.scope(ApplicationScoped.class)
.setRuntimeInit()
.unremovable()
.addInjectionPoint(ClassType.create(DotName.createSimple(DataSources.class)))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.quarkus.agroal.test;

import static org.assertj.core.api.Assertions.assertThat;

import jakarta.inject.Singleton;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.agroal.runtime.DataSources;
import io.quarkus.arc.Arc;
import io.quarkus.datasource.common.runtime.DataSourceUtil;
import io.quarkus.test.QuarkusUnitTest;

/**
* Check that datasources are created eagerly on application startup.
* <p>
* This has always been the case historically, so we want to keep it that way.
*/
public class EagerStartupTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withConfigurationResource("base.properties");

@Test
public void shouldStartEagerly() {
var container = Arc.container();
var instanceHandle = container.instance(DataSources.class);
// Check that the following call won't trigger a lazy initialization:
// the DataSources bean must be eagerly initialized.
assertThat(container.getActiveContext(Singleton.class).getState()
.getContextualInstances().get(instanceHandle.getBean()))
.as("Eagerly instantiated DataSources bean")
.isNotNull();
// Check that the datasource has already been eagerly created.
assertThat(instanceHandle.get().isDataSourceCreated(DataSourceUtil.DEFAULT_DATASOURCE_NAME))
.isTrue();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.function.Function;
import java.util.stream.Collectors;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.enterprise.inject.Any;
import jakarta.enterprise.inject.Default;
Expand Down Expand Up @@ -122,6 +123,10 @@ public static AgroalDataSource fromName(String dataSourceName) {
.getDataSource(dataSourceName);
}

public boolean isDataSourceCreated(String dataSourceName) {
return dataSources.containsKey(dataSourceName);
}

public AgroalDataSource getDataSource(String dataSourceName) {
return dataSources.computeIfAbsent(dataSourceName, new Function<String, AgroalDataSource>() {
@Override
Expand All @@ -131,6 +136,13 @@ public AgroalDataSource apply(String s) {
});
}

@PostConstruct
public void start() {
for (String dataSourceName : dataSourceSupport.entries.keySet()) {
getDataSource(dataSourceName);
}
}

@SuppressWarnings("resource")
public AgroalDataSource doCreateDataSource(String dataSourceName) {
if (!dataSourceSupport.entries.containsKey(dataSourceName)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class EntitiesInDefaultPUWithImplicitUnconfiguredDatasourceTest {
.assertException(t -> assertThat(t)
.isInstanceOf(RuntimeException.class)
.hasMessageContainingAll(
"Model classes are defined for the default persistence unit <default> but configured datasource <default> not found",
"Model classes are defined for persistence unit <default> but configured datasource <default> not found",
"To solve this, configure the default datasource.",
"Refer to https://quarkus.io/guides/datasource for guidance."));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,9 @@
import org.hibernate.service.internal.ProvidedService;
import org.jboss.logging.Logger;

import io.quarkus.agroal.DataSource.DataSourceLiteral;
import io.quarkus.agroal.runtime.DataSources;
import io.quarkus.agroal.runtime.UnconfiguredDataSource;
import io.quarkus.arc.Arc;
import io.quarkus.arc.InstanceHandle;
import io.quarkus.datasource.common.runtime.DataSourceUtil;
import io.quarkus.hibernate.orm.runtime.RuntimeSettings.Builder;
import io.quarkus.hibernate.orm.runtime.boot.FastBootEntityManagerFactoryBuilder;
import io.quarkus.hibernate.orm.runtime.boot.RuntimePersistenceUnitDescriptor;
Expand All @@ -38,7 +36,6 @@
import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationRuntimeInitListener;
import io.quarkus.hibernate.orm.runtime.recording.PrevalidatedQuarkusMetadata;
import io.quarkus.hibernate.orm.runtime.recording.RecordedState;
import io.quarkus.runtime.configuration.ConfigurationException;

/**
* This can not inherit from HibernatePersistenceProvider as that would force
Expand Down Expand Up @@ -375,7 +372,7 @@ private void verifyProperties(Map properties) {
}
}

private static void injectDataSource(String persistenceUnitName, String dataSource,
private static void injectDataSource(String persistenceUnitName, String dataSourceName,
RuntimeSettings.Builder runtimeSettingsBuilder) {
// first convert

Expand All @@ -389,26 +386,21 @@ private static void injectDataSource(String persistenceUnitName, String dataSour
return;
}

InstanceHandle<DataSource> dataSourceHandle;
if (DataSourceUtil.isDefault(dataSource)) {
dataSourceHandle = Arc.container().instance(DataSource.class);
} else {
dataSourceHandle = Arc.container().instance(DataSource.class, new DataSourceLiteral(dataSource));
}

if (!dataSourceHandle.isAvailable()) {
DataSource dataSource;
try {
dataSource = Arc.container().instance(DataSources.class).get().getDataSource(dataSourceName);
} catch (IllegalArgumentException e) {
throw new IllegalStateException(
"No datasource " + dataSource + " has been defined for persistence unit " + persistenceUnitName);
"No datasource " + dataSourceName + " has been defined for persistence unit " + persistenceUnitName);
}

DataSource ds = dataSourceHandle.get();
if (ds instanceof UnconfiguredDataSource) {
throw new ConfigurationException(
"Model classes are defined for the default persistence unit " + persistenceUnitName
+ " but configured datasource " + dataSource
if (dataSource instanceof UnconfiguredDataSource) {
throw new IllegalStateException(
"Model classes are defined for persistence unit " + persistenceUnitName
+ " but configured datasource " + dataSourceName
+ " not found: the default EntityManagerFactory will not be created. To solve this, configure the default datasource. Refer to https://quarkus.io/guides/datasource for guidance.");
}
runtimeSettingsBuilder.put(AvailableSettings.DATASOURCE, ds);
runtimeSettingsBuilder.put(AvailableSettings.DATASOURCE, dataSource);
}

private static void injectRuntimeConfiguration(HibernateOrmRuntimeConfigPersistenceUnit persistenceUnitConfig,
Expand Down

0 comments on commit c3597f3

Please sign in to comment.