Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] QUARKUS-3363 - Offline startup for Hibernate ORM in Quarkus #43396

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
QUARKUS-3363 - Disable JDBC Metadata Defaults in quarkus
Handle Hibernate's `DialectSpecificSettings` as supported Quarkus settings
  • Loading branch information
sebersole committed Sep 25, 2024
commit bd12755c577670cf7422b5a55efebe919aef1774
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.quarkus.hibernate.orm.deployment;

import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.configuration.TrimmedStringConverter;
import io.smallrye.config.WithConverter;

/**
* Configuration specific to the Hibernate ORM {@linkplain org.hibernate.dialect.CockroachDialect}
*
* @author Steve Ebersole
*/
@ConfigGroup
public interface CockroachDialectConfig {
sebersole marked this conversation as resolved.
Show resolved Hide resolved
/**
* Specialized version string which can be passed into the {@linkplain org.hibernate.dialect.CockroachDialect}
*/
@WithConverter(TrimmedStringConverter.class)
Optional<String> versionString();

default boolean isAnyPropertySet() {
return versionString().isPresent();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.quarkus.hibernate.orm.deployment;

import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigDocDefault;
import io.quarkus.runtime.annotations.ConfigGroup;

/**
* Configuration specific to the Hibernate ORM {@linkplain org.hibernate.dialect.HANADialect}
*
* @author Steve Ebersole
*/
@ConfigGroup
public interface HANADialectConfig {
/**
* Specifies the LOB prefetch size.
*
* @see org.hibernate.cfg.DialectSpecificSettings#HANA_MAX_LOB_PREFETCH_SIZE
*/
@ConfigDocDefault("1024")
Optional<Integer> maxLobPrefetchSize();

default boolean isAnyPropertySet() {
return maxLobPrefetchSize().isPresent();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -310,13 +310,52 @@ interface HibernateOrmConfigPersistenceUnitDialect {
*
* E.g. `MyISAM` or `InnoDB` for MySQL.
*
* @deprecated Use {@code mysql.}{@linkplain MySQLDialectConfig#storageEngine storage-engine} instead
*
* @asciidoclet
sebersole marked this conversation as resolved.
Show resolved Hide resolved
*/
@WithConverter(TrimmedStringConverter.class)
@Deprecated
Optional<String> storageEngine();

/**
* Configuration specific to the Hibernate ORM {@linkplain org.hibernate.dialect.CockroachDialect}
*/
CockroachDialectConfig cockroach();

/**
* Configuration specific to the Hibernate ORM {@linkplain org.hibernate.dialect.HANADialect}
*/
HANADialectConfig hana();

/**
* Configuration specific to the Hibernate ORM {@linkplain org.hibernate.dialect.MySQLDialect}
*/
MySQLDialectConfig mysql();
Copy link
Member

Choose a reason for hiding this comment

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

Note we have use dedicated configuration for MariaDB in Quarkus, e.g. there is a db-kind named mariadb, distinct from mysql. It would probably make sense to do the same here?

FWIW you might be able to use inheritance with config groups if you want to avoid duplicating code... I think Guillaume added that support in his recent changes to the config annotation processor. We'll need to double checked by having a look at the resulting documentation, and of course through tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I could just add MySQLDialectConfig mariadb();, no?

Copy link
Member

Choose a reason for hiding this comment

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

True, for now this would work.


/**
* Configuration specific to the Hibernate ORM {@linkplain org.hibernate.dialect.OracleDialect}
*/
OracleDialectConfig oracle();

/**
* Configuration specific to the Hibernate ORM {@linkplain org.hibernate.dialect.SQLServerDialect}
*/
SqlServerDialectConfig sqlserver();

/**
* Configuration specific to the Hibernate ORM {@linkplain org.hibernate.dialect.SybaseDialect}
*/
SybaseDialectConfig sybase();

default boolean isAnyPropertySet() {
return dialect().isPresent() || storageEngine().isPresent();
return dialect().isPresent() || storageEngine().isPresent()
|| cockroach().isAnyPropertySet()
|| hana().isAnyPropertySet()
|| mysql().isAnyPropertySet()
|| oracle().isAnyPropertySet()
|| sqlserver().isAnyPropertySet()
|| sybase().isAnyPropertySet();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@
import static org.hibernate.cfg.AvailableSettings.USE_DIRECT_REFERENCE_CACHE_ENTRIES;
import static org.hibernate.cfg.AvailableSettings.USE_QUERY_CACHE;
import static org.hibernate.cfg.AvailableSettings.USE_SECOND_LEVEL_CACHE;
import static org.hibernate.cfg.DialectSpecificSettings.COCKROACH_VERSION_STRING;
import static org.hibernate.cfg.DialectSpecificSettings.HANA_MAX_LOB_PREFETCH_SIZE;
import static org.hibernate.cfg.DialectSpecificSettings.MYSQL_BYTES_PER_CHARACTER;
import static org.hibernate.cfg.DialectSpecificSettings.MYSQL_NO_BACKSLASH_ESCAPES;
import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_APPLICATION_CONTINUITY;
import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_AUTONOMOUS_DATABASE;
import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_EXTENDED_STRING_SIZE;
import static org.hibernate.cfg.DialectSpecificSettings.SQL_SERVER_COMPATIBILITY_LEVEL;
import static org.hibernate.cfg.DialectSpecificSettings.SYBASE_ANSI_NULL;

import java.io.IOException;
import java.net.URL;
Expand Down Expand Up @@ -1117,7 +1126,10 @@ private static void collectDialectConfig(String persistenceUnitName,
MultiTenancyStrategy multiTenancyStrategy,
BuildProducer<SystemPropertyBuildItem> systemProperties,
BiConsumer<String, String> puPropertiesCollector, Set<String> storageEngineCollector) {
Optional<String> explicitDialect = persistenceUnitConfig.dialect().dialect();
final HibernateOrmConfigPersistenceUnit.HibernateOrmConfigPersistenceUnitDialect dialectConfig = persistenceUnitConfig
.dialect();

Optional<String> explicitDialect = dialectConfig.dialect();
Optional<String> dbKind = jdbcDataSource.map(JdbcDataSourceBuildItem::getDbKind);
Optional<String> explicitDbMinVersion = jdbcDataSource.flatMap(JdbcDataSourceBuildItem::getDbVersion);
if (multiTenancyStrategy != MultiTenancyStrategy.DATABASE && jdbcDataSource.isEmpty()) {
Expand Down Expand Up @@ -1174,24 +1186,103 @@ private static void collectDialectConfig(String persistenceUnitName,
persistenceUnitName));
}

if (persistenceUnitConfig.dialect().storageEngine().isPresent()) {
// Only actually set the storage engines if MySQL or MariaDB
if (isMySQLOrMariaDB(dbKind, dialect)) {
// The storage engine has to be set as a system property.
// We record it so that we can later run checks (because we can only set a single value)
storageEngineCollector.add(persistenceUnitConfig.dialect().storageEngine().get());
systemProperties.produce(new SystemPropertyBuildItem(AvailableSettings.STORAGE_ENGINE,
persistenceUnitConfig.dialect().storageEngine().get()));
} else {
if (dbProductVersion.isPresent()) {
puPropertiesCollector.accept(AvailableSettings.JAKARTA_HBM2DDL_DB_VERSION, dbProductVersion.get());
}

handleDialectSpecificSettings(
persistenceUnitName,
systemProperties,
puPropertiesCollector,
storageEngineCollector,
dialectConfig,
dbKind,
dialect);
}

private static void handleDialectSpecificSettings(
String persistenceUnitName,
BuildProducer<SystemPropertyBuildItem> systemProperties,
BiConsumer<String, String> puPropertiesCollector,
Set<String> storageEngineCollector,
HibernateOrmConfigPersistenceUnit.HibernateOrmConfigPersistenceUnitDialect dialectConfig,
Optional<String> dbKind,
Optional<String> dialect) {

// todo : do we want dbKind/dialect aware handling for all of these settings (i.e. isMySQLOrMariaDB)?
// if so, we need to reorganize this code a bit
sebersole marked this conversation as resolved.
Show resolved Hide resolved

final Optional<String> storageEngine = dialectConfig.mysql().storageEngine()
.or(() -> dialectConfig.storageEngine().or(Optional::empty));
if (isMySQLOrMariaDB(dbKind, dialect)) {
if (storageEngine.isPresent()) {
storageEngineCollector.add(storageEngine.get());
systemProperties.produce(new SystemPropertyBuildItem(AvailableSettings.STORAGE_ENGINE, storageEngine.get()));
}
applyOptionalIntegerSetting(dialectConfig.mysql().bytesPerCharacter(), MYSQL_BYTES_PER_CHARACTER,
puPropertiesCollector);
applyOptionalBooleanSetting(dialectConfig.mysql().noBackslashEscapes(), MYSQL_NO_BACKSLASH_ESCAPES,
puPropertiesCollector);
} else {
if (storageEngine.isPresent()) {
LOG.warnf("The storage engine set through configuration property '%1$s' is being ignored"
+ " because the database is neither MySQL nor MariaDB.",
HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "dialect.storage-engine"));
}

applyOptionalStringSetting(dialectConfig.cockroach().versionString(), COCKROACH_VERSION_STRING,
puPropertiesCollector);

applyOptionalIntegerSetting(dialectConfig.hana().maxLobPrefetchSize(), HANA_MAX_LOB_PREFETCH_SIZE,
puPropertiesCollector);

applyOptionalIntegerSetting(dialectConfig.mysql().bytesPerCharacter(), MYSQL_BYTES_PER_CHARACTER,
puPropertiesCollector);
applyOptionalBooleanSetting(dialectConfig.mysql().noBackslashEscapes(), MYSQL_NO_BACKSLASH_ESCAPES,
puPropertiesCollector);

applyOptionalBooleanSetting(dialectConfig.oracle().applicationContinuity(), ORACLE_APPLICATION_CONTINUITY,
puPropertiesCollector);
applyOptionalBooleanSetting(dialectConfig.oracle().autonomous(), ORACLE_AUTONOMOUS_DATABASE,
puPropertiesCollector);
applyOptionalBooleanSetting(dialectConfig.oracle().extended(), ORACLE_EXTENDED_STRING_SIZE,
puPropertiesCollector);

applyOptionalStringSetting(dialectConfig.sqlserver().compatibilityLevel(), SQL_SERVER_COMPATIBILITY_LEVEL,
puPropertiesCollector);

applyOptionalBooleanSetting(dialectConfig.sybase().ansinull(), SYBASE_ANSI_NULL, puPropertiesCollector);
}
}

if (dbProductVersion.isPresent()) {
puPropertiesCollector.accept(AvailableSettings.JAKARTA_HBM2DDL_DB_VERSION, dbProductVersion.get());
private static void applyOptionalStringSetting(
Optional<String> setting,
String settingName,
BiConsumer<String, String> puPropertiesCollector) {
if (setting.isEmpty()) {
return;
}
puPropertiesCollector.accept(settingName, setting.get());
}

private static void applyOptionalIntegerSetting(
Optional<Integer> setting,
String settingName,
BiConsumer<String, String> puPropertiesCollector) {
if (setting.isEmpty()) {
return;
}
puPropertiesCollector.accept(settingName, Integer.toString(setting.get()));
}

private static void applyOptionalBooleanSetting(
Optional<Boolean> setting,
String settingName,
BiConsumer<String, String> puPropertiesCollector) {
if (setting.isEmpty()) {
return;
}
puPropertiesCollector.accept(settingName, Boolean.toString(setting.get()));
}

private static void collectDialectConfigForPersistenceXml(String persistenceUnitName,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.quarkus.hibernate.orm.deployment;

import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigDocDefault;
import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.configuration.TrimmedStringConverter;
import io.smallrye.config.WithConverter;

/**
* Configuration specific to the Hibernate ORM {@linkplain org.hibernate.dialect.MySQLDialect},
* though may also affect other dialects such as {@linkplain org.hibernate.dialect.MariaDBDialect}.
*
* @author Steve Ebersole
*/
@ConfigGroup
public interface MySQLDialectConfig {
/**
* Specifies the bytes per character to use based on the database's configured
* <a href="https://dev.mysql.com/doc/refman/8.0/en/charset-charsets.html">charset</a>.
*
* @see org.hibernate.cfg.DialectSpecificSettings#MYSQL_BYTES_PER_CHARACTER
Copy link
Member

Choose a reason for hiding this comment

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

Note:

  1. Such links won't appear in generated documentation.
  2. Application users are unlikely to reach this javadoc directly, they'll most likely read the rendered version included in the Quarkus documentation -- and in IDE plugins.

That's why we tend to do things like this:

* Refer to
* link:{hibernate-orm-docs-url}#basic-uuid[this section of the Hibernate ORM documentation]
* to see the possible UUID representations.

Reminder that Asciidoc syntax requires @asciidoctlet in the Javadoc comment.

hibernate-orm-docs-url is defined here, in case you want to add links to Hibernate ORM Javadocs -- but since nowadays you have a list of documentation properties in the Hibernate ORM user guide, I guess it could be more practical to link directly there?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reminder that Asciidoc syntax requires @asciidoctlet in the Javadoc comment.

+1, sure just have not been using asciidoc formatting yet. But clearly I will need to start :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

but since nowadays you have a list of documentation properties in the Hibernate ORM user guide, I guess it could be more practical to link directly there?

I think that's a good idea, Currently we don't generate url anchors for each setting as we'd need to, but https://hibernate.atlassian.net/browse/HHH-18654

*/
@ConfigDocDefault("4")
Optional<Integer> bytesPerCharacter();

/**
* Specifies whether the {@code NO_BACKSLASH_ESCAPES} sql mode is enabled.
*
* @see org.hibernate.cfg.DialectSpecificSettings#MYSQL_NO_BACKSLASH_ESCAPES
*/
@ConfigDocDefault("false")
Optional<Boolean> noBackslashEscapes();

/**
* The storage engine to use.
*/
@WithConverter(TrimmedStringConverter.class)
Optional<String> storageEngine();

default boolean isAnyPropertySet() {
return bytesPerCharacter().isPresent()
|| noBackslashEscapes().isPresent()
|| storageEngine().isPresent();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package io.quarkus.hibernate.orm.deployment;

import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigDocDefault;
import io.quarkus.runtime.annotations.ConfigGroup;

/**
* Configuration specific to the Hibernate ORM {@linkplain org.hibernate.dialect.OracleDialect}
*
* @author Steve Ebersole
sebersole marked this conversation as resolved.
Show resolved Hide resolved
*/
@ConfigGroup
public interface OracleDialectConfig {

/**
* Support for Oracle's MAX_STRING_SIZE = EXTENDED.
*
* @see org.hibernate.cfg.DialectSpecificSettings#ORACLE_EXTENDED_STRING_SIZE
*/
@ConfigDocDefault("false")
Optional<Boolean> extended();

/**
* Specifies whether this database is running on an Autonomous Database Cloud Service.
*
* @see org.hibernate.cfg.DialectSpecificSettings#ORACLE_AUTONOMOUS_DATABASE
*/
@ConfigDocDefault("false")
Optional<Boolean> autonomous();

/**
* Specifies whether this database is accessed using a database service protected by Application Continuity.
*
* @see org.hibernate.cfg.DialectSpecificSettings#ORACLE_APPLICATION_CONTINUITY
*/
@ConfigDocDefault("false")
Optional<Boolean> applicationContinuity();

default boolean isAnyPropertySet() {
return extended().isPresent()
|| autonomous().isPresent()
|| applicationContinuity().isPresent();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.quarkus.hibernate.orm.deployment;

import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.configuration.TrimmedStringConverter;
import io.smallrye.config.WithConverter;

/**
* Configuration specific to the Hibernate ORM {@linkplain org.hibernate.dialect.SQLServerDialect}
*
* @author Steve Ebersole
*/
@ConfigGroup
public interface SqlServerDialectConfig {
/**
* The {@code compatibility_level} as defined in {@code sys.databases}.
*
* @see org.hibernate.cfg.DialectSpecificSettings#SQL_SERVER_COMPATIBILITY_LEVEL
*/
@WithConverter(TrimmedStringConverter.class)
Optional<String> compatibilityLevel();

default boolean isAnyPropertySet() {
return compatibilityLevel().isPresent();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.quarkus.hibernate.orm.deployment;

import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigDocDefault;
import io.quarkus.runtime.annotations.ConfigGroup;

/**
* Configuration specific to the Hibernate ORM {@linkplain org.hibernate.dialect.SybaseDialect}
*
* @author Steve Ebersole
*/
@ConfigGroup
public interface SybaseDialectConfig {

/**
* Whether the database's {@code ansinull} setting is enabled
*
* @see org.hibernate.cfg.DialectSpecificSettings#SYBASE_ANSI_NULL
*/
@SuppressWarnings("SpellCheckingInspection")
@ConfigDocDefault("false")
Optional<Boolean> ansinull();

default boolean isAnyPropertySet() {
return ansinull().isPresent();
}
}