Skip to content

Commit 1489f71

Browse files
lucamoltenisebersoleyrodiere
committed
Offline startup mode and dialect configuration for Hibernate ORM and Hibernate Reactive quarkusio#48130
Added a `quarkus.hibernate-orm.database.start-offline` to avoid connecting to the database * Disable schema validation * Disable temporary table creation at startup, gives precedence to local temporary tables using Hibernate 7.1 local mutation strategies * Disable schema management in offline mode for DevServices as well New way to handle storage engine from both mysql and mariadb Added tests for specific override of Dialect Settings Removed unused hack MultiplePersistenceUnitsInconsistentStorageEnginesTest$H2DialectWithMySQLInTheName Included initial draft by Steve Ebersole <[email protected]> quarkusio#43396 Co-authored-by: Steve Ebersole <[email protected]> Co-authored-by: Yoann Rodière <[email protected]>
1 parent f7d8602 commit 1489f71

File tree

51 files changed

+1706
-65
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1706
-65
lines changed

docs/src/main/asciidoc/_attributes.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
:quickstarts-tree-url: ${quickstarts-base-url}/tree/main
5555
// .
5656
:hibernate-orm-docs-url: https://docs.jboss.org/hibernate/orm/{hibernate-orm-version-major-minor}/userguide/html_single/Hibernate_User_Guide.html
57+
:hibernate-orm-javadocs-url: https://docs.jboss.org/hibernate/orm/{hibernate-orm-version-major-minor}/javadocs/
5758
:hibernate-orm-dialect-docs-url: https://docs.jboss.org/hibernate/orm/{hibernate-orm-version-major-minor}/dialect/dialect.html
5859
:hibernate-search-docs-url: https://docs.jboss.org/hibernate/search/{hibernate-search-version-major-minor}/reference/en-US/html_single/
5960
:hibernate-validator-docs-url: https://docs.jboss.org/hibernate/validator/{hibernate-validator-version-major-minor}/reference/en-US/html_single/

docs/src/main/asciidoc/hibernate-orm.adoc

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,46 @@ link in the Flyway pane. Hit the `Create Initial Migration` button and the follo
864864
WARNING: This button is simply a convenience to quickly get you started with Flyway, it is up to you to determine how you want to
865865
manage your database schemas in production. In particular the `migrate-at-start` setting may not be right for all environments.
866866

867+
[[offline]]
868+
== Offline startup
869+
870+
By default, Hibernate attempts to connect to the database at startup to fetch metadata. This is useful for example to validate the schema or create some temporary tables, making the startup process smoother and more user-friendly.
871+
872+
However, in certain environments, such as when running a Quarkus application in a container within a Kubernetes
873+
cluster, this connection might not be possible. For example, if the application runs in one pod and the database
874+
in another, the database may not be reachable at startup time.
875+
To address this, Quarkus provides an _offline startup_ mode, which allows Hibernate to skip connecting to the database
876+
during application startup.
877+
878+
When using offline startup, it's important to ensure that the database schema has already been created correctly before
879+
the application starts.
880+
881+
You can rely on xref:flyway.adoc[Flyway] or custom setups to create/migrate your database schema,
882+
though obviously at a time where the database _is_ accessible --
883+
Flyway's `migrate-at-start` option in particular will just fail at application startup
884+
if the database is not reachable.
885+
886+
To enable offline startup, set the following configuration property:
887+
888+
[source,properties]
889+
.application.properties
890+
----
891+
quarkus.hibernate-orm.database.start-offline=true
892+
----
893+
894+
895+
You can also fine-tune dialect behavior for specific databases using additional properties, such as:
896+
897+
[source,properties]
898+
.application.properties
899+
----
900+
quarkus.hibernate-orm."offline".dialect.mariadb.bytes-per-character=1
901+
quarkus.hibernate-orm."offline".dialect.mariadb.no-backslash-escapes=true
902+
----
903+
904+
Refer to the <<hibernate-configuration-properties,Hibernate ORM configuration properties>> section for more details on the available properties.
905+
906+
867907
[[caching]]
868908
== Caching
869909

extensions/datasource/common/src/main/java/io/quarkus/datasource/common/runtime/DatabaseKind.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.HashSet;
77
import java.util.Locale;
88
import java.util.Map;
9+
import java.util.Optional;
910
import java.util.Set;
1011

1112
/**
@@ -97,7 +98,7 @@ public static boolean is(String value, String mainName) {
9798
private DatabaseKind() {
9899
}
99100

100-
private enum SupportedDatabaseKind {
101+
public enum SupportedDatabaseKind {
101102
DB2(DatabaseKind.DB2),
102103
DERBY(DatabaseKind.DERBY),
103104
H2(DatabaseKind.H2),
@@ -110,16 +111,30 @@ private enum SupportedDatabaseKind {
110111
private final String mainName;
111112
private final Set<String> aliases;
112113

113-
private SupportedDatabaseKind(String mainName) {
114+
SupportedDatabaseKind(String mainName) {
114115
this.mainName = mainName;
115116
this.aliases = Collections.singleton(mainName);
116117
}
117118

118-
private SupportedDatabaseKind(String mainName, String... aliases) {
119+
SupportedDatabaseKind(String mainName, String... aliases) {
119120
this.mainName = mainName;
120121
this.aliases = new HashSet<>();
121122
this.aliases.add(mainName);
122123
this.aliases.addAll(Arrays.asList(aliases));
123124
}
125+
126+
public String getMainName() {
127+
return mainName;
128+
}
129+
130+
public static Optional<SupportedDatabaseKind> from(String name) {
131+
String normalizedName = normalize(name);
132+
for (SupportedDatabaseKind kind : values()) {
133+
if (kind.getMainName().equals(normalizedName)) {
134+
return Optional.of(kind);
135+
}
136+
}
137+
return Optional.empty();
138+
}
124139
}
125140
}

extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,12 +304,41 @@ interface HibernateOrmConfigPersistenceUnitDialect {
304304
*
305305
* E.g. `MyISAM` or `InnoDB` for MySQL.
306306
*
307+
* @deprecated Use {@code mysql.}{@linkplain MySQLDialectConfig#storageEngine storage-engine}
308+
* or {@code mariadb.}{@linkplain MySQLDialectConfig#storageEngine storage-engine} instead
309+
*
307310
* @asciidoclet
308311
*/
309-
Optional<@WithConverter(TrimmedStringConverter.class) String> storageEngine();
312+
@WithConverter(TrimmedStringConverter.class)
313+
@Deprecated
314+
Optional<String> storageEngine();
315+
316+
/**
317+
* Configuration specific to Hibernate's Dialect for MariaDB
318+
*/
319+
MySQLDialectConfig mariadb();
320+
321+
/**
322+
* Configuration specific to Hibernate's Dialect for MySQL
323+
*/
324+
MySQLDialectConfig mysql();
325+
326+
/**
327+
* Configuration specific to Hibernate's Dialect for Oracle
328+
*/
329+
OracleDialectConfig oracle();
330+
331+
/**
332+
* Configuration specific to Hibernate's Dialect for Microsoft SQLServer
333+
*/
334+
SqlServerDialectConfig mssql();
310335

311336
default boolean isAnyPropertySet() {
312-
return dialect().isPresent() || storageEngine().isPresent();
337+
return dialect().isPresent() || storageEngine().isPresent()
338+
|| mysql().isAnyPropertySet()
339+
|| oracle().isAnyPropertySet()
340+
|| mssql().isAnyPropertySet()
341+
|| mariadb().isAnyPropertySet();
313342
}
314343
}
315344

extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ public void configurationDescriptorBuilding(
340340
new RecordedConfig(
341341
Optional.of(DataSourceUtil.DEFAULT_DATASOURCE_NAME),
342342
jdbcDataSource.map(JdbcDataSourceBuildItem::getDbKind),
343+
Optional.empty(),
343344
jdbcDataSource.flatMap(JdbcDataSourceBuildItem::getDbVersion),
344345
Optional.ofNullable(xmlDescriptor.getProperties().getProperty(AvailableSettings.DIALECT)),
345346
getMultiTenancyStrategy(
@@ -950,7 +951,8 @@ private static void producePersistenceUnitDescriptorFromConfig(
950951

951952
MultiTenancyStrategy multiTenancyStrategy = getMultiTenancyStrategy(persistenceUnitConfig.multitenant());
952953

953-
collectDialectConfig(persistenceUnitName, persistenceUnitConfig,
954+
Optional<DatabaseKind.SupportedDatabaseKind> supportedDatabaseKind = collectDialectConfig(persistenceUnitName,
955+
persistenceUnitConfig,
954956
dbKindMetadataBuildItems, jdbcDataSource, multiTenancyStrategy,
955957
systemProperties, reflectiveMethods, descriptor.getProperties()::setProperty, storageEngineCollector);
956958

@@ -972,6 +974,7 @@ private static void producePersistenceUnitDescriptorFromConfig(
972974
new RecordedConfig(
973975
jdbcDataSource.map(JdbcDataSourceBuildItem::getName),
974976
jdbcDataSource.map(JdbcDataSourceBuildItem::getDbKind),
977+
supportedDatabaseKind.map(DatabaseKind.SupportedDatabaseKind::getMainName),
975978
jdbcDataSource.flatMap(JdbcDataSourceBuildItem::getDbVersion),
976979
persistenceUnitConfig.dialect().dialect(),
977980
multiTenancyStrategy,
@@ -985,7 +988,7 @@ private static void producePersistenceUnitDescriptorFromConfig(
985988
isHibernateValidatorPresent(capabilities), jsonMapper, xmlMapper));
986989
}
987990

988-
private static void collectDialectConfig(String persistenceUnitName,
991+
private static Optional<DatabaseKind.SupportedDatabaseKind> collectDialectConfig(String persistenceUnitName,
989992
HibernateOrmConfigPersistenceUnit persistenceUnitConfig,
990993
List<DatabaseKindDialectBuildItem> dbKindMetadataBuildItems,
991994
Optional<JdbcDataSourceBuildItem> jdbcDataSource,
@@ -994,7 +997,10 @@ private static void collectDialectConfig(String persistenceUnitName,
994997
BuildProducer<ReflectiveMethodBuildItem> reflectiveMethods,
995998
BiConsumer<String, String> puPropertiesCollector,
996999
Set<String> storageEngineCollector) {
997-
Optional<String> dialect = persistenceUnitConfig.dialect().dialect();
1000+
final HibernateOrmConfigPersistenceUnit.HibernateOrmConfigPersistenceUnitDialect dialectConfig = persistenceUnitConfig
1001+
.dialect();
1002+
1003+
Optional<String> dialect = dialectConfig.dialect();
9981004
Optional<String> dbKind = jdbcDataSource.map(JdbcDataSourceBuildItem::getDbKind);
9991005
Optional<String> explicitDbMinVersion = jdbcDataSource.flatMap(JdbcDataSourceBuildItem::getDbVersion);
10001006
if (multiTenancyStrategy != MultiTenancyStrategy.DATABASE && jdbcDataSource.isEmpty()) {
@@ -1007,11 +1013,12 @@ private static void collectDialectConfig(String persistenceUnitName,
10071013
"quarkus.datasource.password", "quarkus.datasource.jdbc.url")));
10081014
}
10091015

1010-
setDialectAndStorageEngine(
1016+
Optional<DatabaseKind.SupportedDatabaseKind> supportedDatabaseKind = setDialectAndStorageEngine(
10111017
persistenceUnitName,
10121018
dbKind,
10131019
dialect,
10141020
explicitDbMinVersion,
1021+
dialectConfig,
10151022
dbKindMetadataBuildItems,
10161023
persistenceUnitConfig.dialect().storageEngine(),
10171024
systemProperties,
@@ -1025,6 +1032,8 @@ private static void collectDialectConfig(String persistenceUnitName,
10251032
"Accessed in org.hibernate.engine.jdbc.env.internal.DefaultSchemaNameResolver.determineAppropriateResolverDelegate",
10261033
true, "org.postgresql.jdbc.PgConnection", "getSchema"));
10271034
}
1035+
1036+
return supportedDatabaseKind;
10281037
}
10291038

10301039
private static void collectDialectConfigForPersistenceXml(String persistenceUnitName,
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package io.quarkus.hibernate.orm.deployment;
2+
3+
import java.util.Optional;
4+
5+
import io.quarkus.runtime.annotations.ConfigDocDefault;
6+
import io.quarkus.runtime.annotations.ConfigGroup;
7+
import io.quarkus.runtime.configuration.TrimmedStringConverter;
8+
import io.smallrye.config.WithConverter;
9+
10+
/**
11+
* Configuration specific to the Hibernate ORM {@linkplain org.hibernate.dialect.MySQLDialect},
12+
* though may also affect other dialects such as {@linkplain org.hibernate.dialect.MariaDBDialect}.
13+
*
14+
* @author Steve Ebersole
15+
*/
16+
@ConfigGroup
17+
public interface MySQLDialectConfig {
18+
/**
19+
* Specifies the bytes per character to use based on the database's configured
20+
* <a href="https://dev.mysql.com/doc/refman/8.0/en/charset-charsets.html">charset</a>.
21+
*
22+
* @see org.hibernate.cfg.DialectSpecificSettings#MYSQL_BYTES_PER_CHARACTER
23+
*/
24+
@ConfigDocDefault("4")
25+
Optional<Integer> bytesPerCharacter();
26+
27+
/**
28+
* Specifies whether the {@code NO_BACKSLASH_ESCAPES} sql mode is enabled.
29+
*
30+
* @see org.hibernate.cfg.DialectSpecificSettings#MYSQL_NO_BACKSLASH_ESCAPES
31+
*/
32+
@ConfigDocDefault("false")
33+
Optional<Boolean> noBackslashEscapes();
34+
35+
/**
36+
* The storage engine to use.
37+
*/
38+
@WithConverter(TrimmedStringConverter.class)
39+
Optional<String> storageEngine();
40+
41+
default boolean isAnyPropertySet() {
42+
return bytesPerCharacter().isPresent()
43+
|| noBackslashEscapes().isPresent()
44+
|| storageEngine().isPresent();
45+
}
46+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package io.quarkus.hibernate.orm.deployment;
2+
3+
import java.util.Optional;
4+
5+
import io.quarkus.runtime.annotations.ConfigDocDefault;
6+
import io.quarkus.runtime.annotations.ConfigGroup;
7+
8+
/**
9+
* Configuration specific to the Hibernate ORM {@linkplain org.hibernate.dialect.OracleDialect}
10+
*
11+
* @author Steve Ebersole
12+
*/
13+
@ConfigGroup
14+
public interface OracleDialectConfig {
15+
16+
/**
17+
* Support for Oracle's MAX_STRING_SIZE = EXTENDED.
18+
*
19+
* @see org.hibernate.cfg.DialectSpecificSettings#ORACLE_EXTENDED_STRING_SIZE
20+
*/
21+
@ConfigDocDefault("false")
22+
Optional<Boolean> extended();
23+
24+
/**
25+
* Specifies whether this database is running on an Autonomous Database Cloud Service.
26+
*
27+
* @see org.hibernate.cfg.DialectSpecificSettings#ORACLE_AUTONOMOUS_DATABASE
28+
*/
29+
@ConfigDocDefault("false")
30+
Optional<Boolean> autonomous();
31+
32+
/**
33+
* Specifies whether this database is accessed using a database service protected by Application Continuity.
34+
*
35+
* @see org.hibernate.cfg.DialectSpecificSettings#ORACLE_APPLICATION_CONTINUITY
36+
*/
37+
@ConfigDocDefault("false")
38+
Optional<Boolean> applicationContinuity();
39+
40+
default boolean isAnyPropertySet() {
41+
return extended().isPresent()
42+
|| autonomous().isPresent()
43+
|| applicationContinuity().isPresent();
44+
}
45+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package io.quarkus.hibernate.orm.deployment;
2+
3+
import java.util.Optional;
4+
5+
import io.quarkus.runtime.annotations.ConfigGroup;
6+
import io.quarkus.runtime.configuration.TrimmedStringConverter;
7+
import io.smallrye.config.WithConverter;
8+
9+
/**
10+
* Configuration specific to the Hibernate ORM {@linkplain org.hibernate.dialect.SQLServerDialect}
11+
*
12+
* @author Steve Ebersole
13+
*/
14+
@ConfigGroup
15+
public interface SqlServerDialectConfig {
16+
/**
17+
* The {@code compatibility_level} as defined in {@code sys.databases}.
18+
*
19+
* @see org.hibernate.cfg.DialectSpecificSettings#SQL_SERVER_COMPATIBILITY_LEVEL
20+
*/
21+
@WithConverter(TrimmedStringConverter.class)
22+
Optional<String> compatibilityLevel();
23+
24+
default boolean isAnyPropertySet() {
25+
return compatibilityLevel().isPresent();
26+
}
27+
}

extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/dev/HibernateOrmDevServicesProcessor.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.quarkus.hibernate.orm.deployment.dev;
22

33
import java.util.Collection;
4+
import java.util.Collections;
45
import java.util.HashSet;
56
import java.util.List;
67
import java.util.Map;
@@ -55,10 +56,21 @@ void devServicesAutoGenerateByDefault(List<JdbcDataSourceSchemaReadyBuildItem> s
5556
// Only force DB generation if the datasource is configured through dev services
5657
if (propertyKeysIndicatingDataSourceConfigured.stream()
5758
.anyMatch(devServicesConfig::containsKey)) {
58-
String forcedValue = "drop-and-create";
59-
LOG.infof("Setting %s=%s to initialize Dev Services managed database",
60-
schemaManagementStrategyPropertyKey, forcedValue);
61-
return Map.of(schemaManagementStrategyPropertyKey, forcedValue);
59+
String offlineStartKey = HibernateOrmRuntimeConfig.puPropertyKey(entry.getKey(),
60+
"database.start-offline");
61+
Optional<Boolean> offlineStart = ConfigUtils
62+
.getFirstOptionalValue(Collections.singletonList(offlineStartKey), Boolean.class);
63+
64+
String forcedValue;
65+
if (offlineStart.isEmpty() || !offlineStart.get()) {
66+
forcedValue = "drop-and-create";
67+
LOG.infof("Setting %s=%s to initialize Dev Services managed database",
68+
schemaManagementStrategyPropertyKey, forcedValue);
69+
return Map.of(schemaManagementStrategyPropertyKey, forcedValue);
70+
} else {
71+
return Map.of();
72+
}
73+
6274
} else {
6375
return Map.of();
6476
}

0 commit comments

Comments
 (0)