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 6, 2023
1 parent 125bc15 commit 3df41c7
Show file tree
Hide file tree
Showing 5 changed files with 60 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,31 @@
package io.quarkus.agroal.test;

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

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() {
assertThat(Arc.container().instance(DataSources.class).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 3df41c7

Please sign in to comment.