Skip to content

Commit

Permalink
Merge pull request quarkusio#18574 from yrodiere/i18130-multitenancy
Browse files Browse the repository at this point in the history
Make the datasource optional for persistence units with DATABASE multi-tenancy
  • Loading branch information
gsmet authored Jul 19, 2021
2 parents 378d5db + 7a914fd commit 408a29d
Show file tree
Hide file tree
Showing 57 changed files with 1,478 additions and 127 deletions.
2 changes: 1 addition & 1 deletion .github/native-tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
{
"category": "Data1",
"timeout": 65,
"test-modules": "jpa-h2, jpa-mariadb, jpa-mssql, jpa-derby, jpa-without-entity, hibernate-tenancy",
"test-modules": "jpa-h2, jpa-mariadb, jpa-mssql, jpa-derby, jpa-without-entity, hibernate-orm-tenancy/datasource, hibernate-orm-tenancy/connection-resolver",
"os-name": "ubuntu-latest"
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package io.quarkus.hibernate.orm.deployment;

import java.util.Optional;

import io.quarkus.datasource.common.runtime.DatabaseKind;
import io.quarkus.deployment.configuration.ConfigurationError;

Expand All @@ -11,37 +9,37 @@ private Dialects() {
//utility
}

public static Optional<String> guessDialect(String resolvedDbKind) {
public static String guessDialect(String persistenceUnitName, String resolvedDbKind) {
// For now select the latest dialect from the driver
// later, we can keep doing that but also avoid DCE
// of all the dialects we want in so that people can override them
if (DatabaseKind.isDB2(resolvedDbKind)) {
return Optional.of("org.hibernate.dialect.DB297Dialect");
return "org.hibernate.dialect.DB297Dialect";
}
if (DatabaseKind.isPostgreSQL(resolvedDbKind)) {
return Optional.of("io.quarkus.hibernate.orm.runtime.dialect.QuarkusPostgreSQL10Dialect");
return "io.quarkus.hibernate.orm.runtime.dialect.QuarkusPostgreSQL10Dialect";
}
if (DatabaseKind.isH2(resolvedDbKind)) {
return Optional.of("io.quarkus.hibernate.orm.runtime.dialect.QuarkusH2Dialect");
return "io.quarkus.hibernate.orm.runtime.dialect.QuarkusH2Dialect";
}
if (DatabaseKind.isMariaDB(resolvedDbKind)) {
return Optional.of("org.hibernate.dialect.MariaDB103Dialect");
return "org.hibernate.dialect.MariaDB103Dialect";
}
if (DatabaseKind.isMySQL(resolvedDbKind)) {
return Optional.of("org.hibernate.dialect.MySQL8Dialect");
return "org.hibernate.dialect.MySQL8Dialect";
}
if (DatabaseKind.isOracle(resolvedDbKind)) {
return Optional.of("org.hibernate.dialect.Oracle12cDialect");
return "org.hibernate.dialect.Oracle12cDialect";
}
if (DatabaseKind.isDerby(resolvedDbKind)) {
return Optional.of("org.hibernate.dialect.DerbyTenSevenDialect");
return "org.hibernate.dialect.DerbyTenSevenDialect";
}
if (DatabaseKind.isMsSQL(resolvedDbKind)) {
return Optional.of("org.hibernate.dialect.SQLServer2012Dialect");
return "org.hibernate.dialect.SQLServer2012Dialect";
}

String error = "Hibernate extension could not guess the dialect from the database kind '" + resolvedDbKind
+ "'. Add an explicit '" + HibernateOrmProcessor.HIBERNATE_ORM_CONFIG_PREFIX + "dialect' property.";
+ "'. Add an explicit '" + HibernateOrmConfig.puPropertyKey(persistenceUnitName, "dialect") + "' property.";
throw new ConfigurationError(error);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.Map;
import java.util.Optional;

import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil;
import io.quarkus.runtime.annotations.ConfigDocMapKey;
import io.quarkus.runtime.annotations.ConfigDocSection;
import io.quarkus.runtime.annotations.ConfigGroup;
Expand Down Expand Up @@ -54,6 +55,13 @@ public boolean isAnyPropertySet() {
metricsEnabled;
}

public static String puPropertyKey(String puName, String radical) {
String prefix = PersistenceUnitUtil.isDefaultPersistenceUnit(puName)
? "quarkus.hibernate-orm."
: "quarkus.hibernate-orm.\"" + puName + "\".";
return prefix + radical;
}

@ConfigGroup
public static class HibernateOrmConfigLog {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ public void configurationDescriptorBuilding(
for (PersistenceXmlDescriptorBuildItem persistenceXmlDescriptorBuildItem : persistenceXmlDescriptors) {
persistenceUnitDescriptors
.produce(new PersistenceUnitDescriptorBuildItem(persistenceXmlDescriptorBuildItem.getDescriptor(),
DataSourceUtil.DEFAULT_DATASOURCE_NAME,
Optional.of(DataSourceUtil.DEFAULT_DATASOURCE_NAME),
getMultiTenancyStrategy(Optional.ofNullable(persistenceXmlDescriptorBuildItem.getDescriptor()
.getProperties().getProperty(AvailableSettings.MULTI_TENANT))),
null,
Expand Down Expand Up @@ -818,47 +818,40 @@ private static void producePersistenceUnitDescriptorFromConfig(
BuildProducer<HotDeploymentWatchedFileBuildItem> hotDeploymentWatchedFiles,
BuildProducer<PersistenceUnitDescriptorBuildItem> persistenceUnitDescriptors,
Set<String> storageEngineCollector) {
// Find the associated datasource
JdbcDataSourceBuildItem jdbcDataSource;
String dataSource;
if (persistenceUnitConfig.datasource.isPresent()) {
jdbcDataSource = jdbcDataSources.stream()
.filter(i -> persistenceUnitConfig.datasource.get().equals(i.getName()))
.findFirst()
.orElseThrow(() -> new ConfigurationException(
String.format(Locale.ROOT,
"The datasource '%1$s' is not configured but the persistence unit '%2$s' uses it."
+ " To solve this, configure datasource '%1$s'."
+ " Refer to https://quarkus.io/guides/datasource for guidance.",
persistenceUnitConfig.datasource.get(), persistenceUnitName)));
dataSource = persistenceUnitConfig.datasource.get();
Optional<JdbcDataSourceBuildItem> jdbcDataSource = findJdbcDataSource(persistenceUnitName, persistenceUnitConfig,
jdbcDataSources);

Optional<String> explicitDialect = persistenceUnitConfig.dialect.dialect;
String dialect;
MultiTenancyStrategy multiTenancyStrategy = getMultiTenancyStrategy(persistenceUnitConfig.multitenant);
if (multiTenancyStrategy == MultiTenancyStrategy.DATABASE) {
// The datasource is optional for the DATABASE multi-tenancy strategy,
// since the datasource will be resolved separately for each tenant.
if (explicitDialect.isPresent()) {
dialect = explicitDialect.get();
} else if (jdbcDataSource.isPresent()) {
dialect = Dialects.guessDialect(persistenceUnitName, jdbcDataSource.get().getDbKind());
} else {
throw new ConfigurationException(String.format(Locale.ROOT,
"The Hibernate ORM extension could not infer the dialect for persistence unit '%s'."
+ " When using database multi-tenancy, you must either configure a datasource for that persistence unit"
+ " (refer to https://quarkus.io/guides/datasource for guidance),"
+ " or set the dialect explicitly through property '"
+ HibernateOrmConfig.puPropertyKey(persistenceUnitName, "dialect") + "'.",
persistenceUnitName));
}
} else {
if (!PersistenceUnitUtil.isDefaultPersistenceUnit(persistenceUnitName)) {
// if it's not the default persistence unit, we mandate a datasource to prevent common errors
throw new ConfigurationException(
String.format(Locale.ROOT, "Datasource must be defined for persistence unit '%s'.",
persistenceUnitName));
if (!jdbcDataSource.isPresent()) {
throw new ConfigurationException(String.format(Locale.ROOT,
"Datasource must be defined for persistence unit '%s'."
+ " Refer to https://quarkus.io/guides/datasource for guidance.",
persistenceUnitName));
}
if (explicitDialect.isPresent()) {
dialect = explicitDialect.get();
} else {
dialect = Dialects.guessDialect(persistenceUnitName, jdbcDataSource.get().getDbKind());
}

jdbcDataSource = jdbcDataSources.stream()
.filter(i -> i.isDefault())
.findFirst()
.orElseThrow(() -> new ConfigurationException(
String.format(Locale.ROOT,
"The default datasource is not configured but the persistence unit '%s' uses it."
+ " To solve this, configure the default datasource."
+ " Refer to https://quarkus.io/guides/datasource for guidance.",
persistenceUnitName)));
dataSource = DataSourceUtil.DEFAULT_DATASOURCE_NAME;
}

Optional<String> dialect = persistenceUnitConfig.dialect.dialect;
if (!dialect.isPresent()) {
dialect = Dialects.guessDialect(jdbcDataSource.getDbKind());
}

if (!dialect.isPresent()) {
return;
}

// we found one
Expand All @@ -883,7 +876,7 @@ private static void producePersistenceUnitDescriptorFromConfig(
}

descriptor.setTransactionType(PersistenceUnitTransactionType.JTA);
descriptor.getProperties().setProperty(AvailableSettings.DIALECT, dialect.get());
descriptor.getProperties().setProperty(AvailableSettings.DIALECT, dialect);

// The storage engine has to be set as a system property.
if (persistenceUnitConfig.dialect.storageEngine.isPresent()) {
Expand Down Expand Up @@ -975,7 +968,8 @@ private static void producePersistenceUnitDescriptorFromConfig(
} else if (persistenceUnitConfig.sqlLoadScript.isPresent()) {
//raise exception if explicit file is not present (i.e. not the default)
throw new ConfigurationError(
"Unable to find file referenced in '" + HIBERNATE_ORM_CONFIG_PREFIX + "sql-load-script="
"Unable to find file referenced in '"
+ HibernateOrmConfig.puPropertyKey(persistenceUnitName, "sql-load-script") + "="
+ persistenceUnitConfig.sqlLoadScript.get() + "'. Remove property or add file to your path.");
}
// in dev mode we want to make sure that we watch for changes to file even if it doesn't currently exist
Expand Down Expand Up @@ -1014,7 +1008,7 @@ private static void producePersistenceUnitDescriptorFromConfig(
}

// Collect the storage engines if MySQL or MariaDB
if (isMySQLOrMariaDB(dialect.get()) && persistenceUnitConfig.dialect.storageEngine.isPresent()) {
if (isMySQLOrMariaDB(dialect) && persistenceUnitConfig.dialect.storageEngine.isPresent()) {
storageEngineCollector.add(persistenceUnitConfig.dialect.storageEngine.get());
}

Expand All @@ -1023,13 +1017,34 @@ private static void producePersistenceUnitDescriptorFromConfig(
String.valueOf(persistenceUnitConfig.discriminator.ignoreExplicitForJoined));

persistenceUnitDescriptors.produce(
new PersistenceUnitDescriptorBuildItem(descriptor, dataSource,
getMultiTenancyStrategy(persistenceUnitConfig.multitenant),
new PersistenceUnitDescriptorBuildItem(descriptor, jdbcDataSource.map(JdbcDataSourceBuildItem::getName),
multiTenancyStrategy,
persistenceUnitConfig.multitenantSchemaDatasource.orElse(null),
xmlMappings,
false, false));
}

private static Optional<JdbcDataSourceBuildItem> findJdbcDataSource(String persistenceUnitName,
HibernateOrmConfigPersistenceUnit persistenceUnitConfig, List<JdbcDataSourceBuildItem> jdbcDataSources) {
if (persistenceUnitConfig.datasource.isPresent()) {
return Optional.of(jdbcDataSources.stream()
.filter(i -> persistenceUnitConfig.datasource.get().equals(i.getName()))
.findFirst()
.orElseThrow(() -> new ConfigurationException(String.format(Locale.ROOT,
"The datasource '%1$s' is not configured but the persistence unit '%2$s' uses it."
+ " To solve this, configure datasource '%1$s'."
+ " Refer to https://quarkus.io/guides/datasource for guidance.",
persistenceUnitConfig.datasource.get(), persistenceUnitName))));
} else if (PersistenceUnitUtil.isDefaultPersistenceUnit(persistenceUnitName)) {
return jdbcDataSources.stream()
.filter(i -> i.isDefault())
.findFirst();
} else {
// if it's not the default persistence unit, we mandate an explicit datasource to prevent common errors
return Optional.empty();
}
}

private static void setMaxFetchDepth(ParsedPersistenceXmlDescriptor descriptor, OptionalInt maxFetchDepth) {
descriptor.getProperties().setProperty(AvailableSettings.MAX_FETCH_DEPTH, String.valueOf(maxFetchDepth.getAsInt()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.Collection;
import java.util.List;
import java.util.Optional;

import org.hibernate.MultiTenancyStrategy;
import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
Expand All @@ -21,7 +22,7 @@
public final class PersistenceUnitDescriptorBuildItem extends MultiBuildItem {

private final ParsedPersistenceXmlDescriptor descriptor;
private final String dataSource;
private final Optional<String> dataSource;
private final MultiTenancyStrategy multiTenancyStrategy;
private final String multiTenancySchemaDataSource;
private final List<RecordableXmlMapping> xmlMappings;
Expand All @@ -30,11 +31,11 @@ public final class PersistenceUnitDescriptorBuildItem extends MultiBuildItem {

public PersistenceUnitDescriptorBuildItem(ParsedPersistenceXmlDescriptor descriptor,
List<RecordableXmlMapping> xmlMappings, boolean isReactive, boolean fromPersistenceXml) {
this(descriptor, DataSourceUtil.DEFAULT_DATASOURCE_NAME, MultiTenancyStrategy.NONE, null,
this(descriptor, Optional.of(DataSourceUtil.DEFAULT_DATASOURCE_NAME), MultiTenancyStrategy.NONE, null,
xmlMappings, isReactive, fromPersistenceXml);
}

public PersistenceUnitDescriptorBuildItem(ParsedPersistenceXmlDescriptor descriptor, String dataSource,
public PersistenceUnitDescriptorBuildItem(ParsedPersistenceXmlDescriptor descriptor, Optional<String> dataSource,
MultiTenancyStrategy multiTenancyStrategy, String multiTenancySchemaDataSource,
List<RecordableXmlMapping> xmlMappings,
boolean isReactive, boolean fromPersistenceXml) {
Expand All @@ -59,7 +60,7 @@ public String getPersistenceUnitName() {
return descriptor.getName();
}

public String getDataSource() {
public Optional<String> getDataSource() {
return dataSource;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

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

import java.util.Optional;
import java.util.Set;

import org.hibernate.dialect.DB297Dialect;
Expand All @@ -18,6 +17,7 @@
import org.junit.jupiter.params.provider.MethodSource;

import io.quarkus.datasource.common.runtime.DatabaseKind;
import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil;
import io.quarkus.hibernate.orm.runtime.dialect.QuarkusH2Dialect;
import io.quarkus.hibernate.orm.runtime.dialect.QuarkusPostgreSQL10Dialect;

Expand Down Expand Up @@ -46,9 +46,8 @@ public void testDialectNames() {
}

private void assertDialectMatch(String dbName, Class<?> dialectClass) {
final Optional<String> guessDialect = Dialects.guessDialect(dbName);
Assertions.assertTrue(guessDialect.isPresent());
Assertions.assertEquals(dialectClass.getName(), guessDialect.get());
final String guessDialect = Dialects.guessDialect(PersistenceUnitUtil.DEFAULT_PERSISTENCE_UNIT_NAME, dbName);
Assertions.assertEquals(dialectClass.getName(), guessDialect);
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.hibernate.orm;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PACKAGE;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;
Expand Down Expand Up @@ -28,7 +29,7 @@
* <p>
* It is also used to mark packages as part of a given persistence unit.
*/
@Target({ TYPE, FIELD, PARAMETER, PACKAGE })
@Target({ TYPE, FIELD, METHOD, PARAMETER, PACKAGE })
@Retention(RUNTIME)
@Documented
@Qualifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,11 @@ private EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull(String
RuntimeSettings.Builder runtimeSettingsBuilder = new RuntimeSettings.Builder(buildTimeSettings,
integrationSettings);

// Inject the datasource
injectDataSource(persistenceUnitName, recordedState.getDataSource(), runtimeSettingsBuilder);
Optional<String> dataSourceName = recordedState.getDataSource();
if (dataSourceName.isPresent()) {
// Inject the datasource
injectDataSource(persistenceUnitName, dataSourceName.get(), runtimeSettingsBuilder);
}

// Inject runtime configuration if the persistence unit was defined by Quarkus configuration
if (!recordedState.isFromPersistenceXml()) {
Expand Down Expand Up @@ -204,7 +207,7 @@ private EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull(String
persistenceUnitName,
standardServiceRegistry /* Mostly ignored! (yet needs to match) */,
runtimeSettings,
validatorFactory, cdiBeanManager, recordedState.getMultiTenancyStrategy());
validatorFactory, cdiBeanManager);
}

log.debug("Found no matching persistence units");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;

Expand Down Expand Up @@ -73,7 +74,7 @@ public JPAConfigSupport get() {
}

public Supplier<DataSourceTenantConnectionResolver> dataSourceTenantConnectionResolver(String persistenceUnitName,
String dataSourceName,
Optional<String> dataSourceName,
MultiTenancyStrategy multiTenancyStrategy, String multiTenancySchemaDataSourceName) {
return new Supplier<DataSourceTenantConnectionResolver>() {
@Override
Expand Down
Loading

0 comments on commit 408a29d

Please sign in to comment.