Skip to content

Commit

Permalink
Improve documentation for multiple-datasource-alternative setups
Browse files Browse the repository at this point in the history
Mention the "getActive()" solution in particular.
  • Loading branch information
yrodiere committed Oct 17, 2024
1 parent 9fbf191 commit 093b837
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 17 deletions.
1 change: 1 addition & 0 deletions docs/src/main/asciidoc/cdi-integration.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@ public class TestRecorder {
----
<1> Pass a contextual reference of `Bar` to the constructor of `Foo`.

[[inactive-synthetic-beans]]
=== Inactive Synthetic Beans

In the case when one needs to register multiple synthetic beans at build time but only wants a subset of them active at runtime, it is useful to be able to mark a synthetic bean as _inactive_.
Expand Down
43 changes: 35 additions & 8 deletions docs/src/main/asciidoc/datasource.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -501,24 +501,41 @@ xref:config-reference.adoc#multiple-profiles[setting `quarkus.profile`]:
----
====

[TIP]
====
It can also be useful to define a xref:cdi.adoc#ok-you-said-that-there-are-several-kinds-of-beans[CDI bean producer] redirecting to the currently active datasource, like this:
With such a setup, you will need to take care to only ever access the _active_ datasource.
To do so, you can inject an `InjectableInstance<DataSource>` or `InjectableInstance<Pool>` with an `@Any` qualifier, and call xref:cdi-integration.adoc#inactive-synthetic-beans[`getActive()`]:

[source,java]
----
import io.quarkus.arc.InjectableInstance;
@ApplicationScoped
public class MyConsumer {
@Inject
@Any
InjectableInstance<DataSource> dataSource;
public void doSomething() {
DataSource activeDataSource = dataSource.getActive();
// ...
}
}
----

Alternatively, you may define a xref:cdi.adoc#ok-you-said-that-there-are-several-kinds-of-beans[CDI bean producer] for the default datasource redirecting to the currently active named datasource, so that it can be injected directly, like this:

[source,java,indent=0]
----
public class MyProducer {
@Inject
@DataSource("pg")
InjectableInstance<AgroalDataSource> pgDataSourceBean; // <1>
InjectableInstance<DataSource> pgDataSourceBean; // <1>
@Inject
@DataSource("oracle")
InjectableInstance<AgroalDataSource> oracleDataSourceBean;
InjectableInstance<DataSource> oracleDataSourceBean;
@Produces // <2>
@ApplicationScoped
public AgroalDataSource dataSource() {
public DataSource dataSource() {
if (pgDataSourceBean.getHandle().getBean().isActive()) { // <3>
return pgDataSourceBean.get();
} else if (oracleDataSourceBean.getHandle().getBean().isActive()) { // <3>
Expand All @@ -528,14 +545,24 @@ public class MyProducer {
}
}
}
@ApplicationScoped
public class MyConsumer {
@Inject
DataSource dataSource; // <4>
public void doSomething() {
// .. just use the injected datasource ...
}
}
----
<1> Don't inject a `DataSource` or `AgroalDatasource` directly,
because that would lead to a failure on startup (can't inject inactive beans).
Instead, inject `InjectableInstance<DataSource>` or `InjectableInstance<AgroalDataSource>`
Instead, inject `InjectableInstance<DataSource>` or `InjectableInstance<AgroalDataSource>`.
<2> Declare a CDI producer method that will define the default datasource
as either PostgreSQL or Oracle, depending on what is active.
<3> Check whether beans are active before retrieving them.
====
<4> This will get injected with the (only) active datasource.

[[datasource-multiple-single-transaction]]
=== Use multiple datasources in a single transaction
Expand Down
32 changes: 23 additions & 9 deletions docs/src/main/asciidoc/hibernate-orm.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -560,17 +560,15 @@ xref:config-reference.adoc#multiple-profiles[setting `quarkus.profile`]:
----
====

[TIP]
====
It can also be useful to define a xref:cdi.adoc#ok-you-said-that-there-are-several-kinds-of-beans[CDI bean producer] redirecting to the currently active persistence unit,
like this:
With such a setup, you will need to take care to only ever access the _active_ persistence unit.
To do so, you may define a xref:cdi.adoc#ok-you-said-that-there-are-several-kinds-of-beans[CDI bean producer] for the default `Session` redirecting to the currently active named `Session`, so that it can be injected directly, like this:

[source,java,indent=0]
----
public class MyProducer {
@Inject
@DataSource("pg")
InjectableInstance<AgroalDataSource> pgDataSourceBean;
InjectableInstance<AgroalDataSource> pgDataSourceBean; // <1>
@Inject
@DataSource("oracle")
Expand All @@ -584,20 +582,36 @@ public class MyProducer {
@PersistenceUnit("oracle")
Session oracleSessionBean;
@Produces
@Produces // <2>
@ApplicationScoped
public Session session() {
if (pgDataSourceBean.getHandle().getBean().isActive()) {
if (pgDataSourceBean.getHandle().getBean().isActive()) { // <3>
return pgSessionBean;
} else if (oracleDataSourceBean.getHandle().getBean().isActive()) {
} else if (oracleDataSourceBean.getHandle().getBean().isActive()) { // <3>
return oracleSessionBean;
} else {
throw new RuntimeException("No active datasource!");
}
}
}
@ApplicationScoped
public class MyConsumer {
@Inject
Session session; // <4>
public void doSomething() {
// .. just use the injected session ...
}
}
----
====
<1> Don't inject a `DataSource` or `AgroalDatasource` directly,
because that would lead to a failure on startup (can't inject inactive beans).
Instead, inject `InjectableInstance<DataSource>` or `InjectableInstance<AgroalDataSource>`.
<2> Declare a CDI producer method that will define the default session
as either PostgreSQL or Oracle, depending on what is active.
<3> Check whether datasource beans are active before retrieving the corresponding session.
<4> This will get injected with the (only) active session.

[[persistence-xml]]
== Setting up and configuring Hibernate ORM with a `persistence.xml`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package io.quarkus.agroal.test;

import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Any;
import jakarta.enterprise.inject.Produces;
import jakarta.inject.Inject;

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

import io.agroal.api.AgroalDataSource;
import io.quarkus.agroal.DataSource;
import io.quarkus.arc.Arc;
import io.quarkus.arc.InjectableInstance;
import io.quarkus.test.QuarkusUnitTest;

/**
* Tests a use case where multiple datasources are defined at build time,
* but only one is used at runtime,
* and Arc's {@code getActive()} method is used to retrieve the active datasource.
* <p>
* This is mostly useful when each datasource has a distinct db-kind, but in theory that shouldn't matter,
* so we use the h2 db-kind everywhere here to keep test dependencies simpler.
*/
public abstract class MultipleDataSourcesAsAlternativesWithGetActiveTest {

public static class Ds1ActiveTest extends MultipleDataSourcesAsAlternativesWithBeanProducerTest {
@RegisterExtension
static QuarkusUnitTest runner = runner("ds-1");

public Ds1ActiveTest() {
super("ds-1", "ds-2");
}
}

public static class Ds2ActiveTest extends MultipleDataSourcesAsAlternativesWithBeanProducerTest {
@RegisterExtension
static QuarkusUnitTest runner = runner("ds-2");

public Ds2ActiveTest() {
super("ds-2", "ds-1");
}
}

static QuarkusUnitTest runner(String activeDsName) {
return new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClass(MyProducer.class))
.overrideConfigKey("quarkus.datasource.ds-1.db-kind", "h2")
.overrideConfigKey("quarkus.datasource.ds-1.active", "false")
.overrideConfigKey("quarkus.datasource.ds-2.db-kind", "h2")
.overrideConfigKey("quarkus.datasource.ds-2.active", "false")
// This is where we select the active datasource
.overrideRuntimeConfigKey("quarkus.datasource." + activeDsName + ".active", "true")
.overrideRuntimeConfigKey("quarkus.datasource." + activeDsName + ".jdbc.url", "jdbc:h2:mem:testds1");
}

private final String activeDsName;
private final String inactiveDsName;

protected MultipleDataSourcesAsAlternativesWithGetActiveTest(String activeDsName, String inactiveDsName) {
this.activeDsName = activeDsName;
this.inactiveDsName = inactiveDsName;
}

@Inject
@Any
InjectableInstance<AgroalDataSource> datasourceInjectableInstance;

@Test
public void testExplicitDatasourceBeanUsable() {
doTestDatasource(Arc.container()
.select(AgroalDataSource.class, new DataSource.DataSourceLiteral(activeDsName)).get());
}

@Test
public void testInjectableInstanceGetActiveBeanUsable() {
doTestDatasource(datasourceInjectableInstance.getActive());
}

@Test
public void testInactiveDatasourceBeanUnusable() {
assertThatThrownBy(
() -> Arc.container().select(AgroalDataSource.class, new DataSource.DataSourceLiteral(inactiveDsName)).get()
.getConnection())
.hasMessageContaining(
"Datasource '" + inactiveDsName + "' was deactivated through configuration properties.");
}

private static void doTestDatasource(AgroalDataSource dataSource) {
assertThatCode(() -> {
try (var connection = dataSource.getConnection()) {
}
})
.doesNotThrowAnyException();
}

private static class MyProducer {
@Inject
@DataSource("ds-1")
InjectableInstance<AgroalDataSource> dataSource1Bean;

@Inject
@DataSource("ds-2")
InjectableInstance<AgroalDataSource> dataSource2Bean;

@Produces
@ApplicationScoped
public AgroalDataSource dataSource() {
if (dataSource1Bean.getHandle().getBean().isActive()) {
return dataSource1Bean.get();
} else if (dataSource2Bean.getHandle().getBean().isActive()) {
return dataSource2Bean.get();
} else {
throw new RuntimeException("No active datasource!");
}
}
}
}

0 comments on commit 093b837

Please sign in to comment.