Skip to content

Commit e86c940

Browse files
committed
Add quarkus.hibernate-orm.database.orm-compatibility.version
1 parent 5bc4371 commit e86c940

File tree

10 files changed

+196
-35
lines changed

10 files changed

+196
-35
lines changed

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
import java.util.Optional;
55
import java.util.TreeMap;
66

7+
import io.quarkus.hibernate.orm.deployment.config.DatabaseOrmCompatibilityVersion;
78
import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil;
89
import io.quarkus.runtime.annotations.ConfigDocMapKey;
910
import io.quarkus.runtime.annotations.ConfigDocSection;
1011
import io.quarkus.runtime.annotations.ConfigGroup;
1112
import io.quarkus.runtime.annotations.ConfigItem;
1213
import io.quarkus.runtime.annotations.ConfigRoot;
14+
import io.quarkus.runtime.annotations.ConvertWith;
1315

1416
@ConfigRoot
1517
public class HibernateOrmConfig {
@@ -26,6 +28,30 @@ public class HibernateOrmConfig {
2628
@ConfigItem(defaultValue = "true")
2729
public boolean enabled;
2830

31+
/**
32+
* When set, attempts to exchange data with the database
33+
* as the given version of Hibernate ORM would have,
34+
* *on a best-effort basis*.
35+
*
36+
* Please note:
37+
*
38+
* * schema validation may still fail in some cases:
39+
* this attempts to make Hibernate ORM 6+ behave correctly at runtime,
40+
* but it may still expect a different (but runtime-compatible) schema.
41+
* * robust test suites are still useful and recommended:
42+
* you should still check that your application behaves as intended with your legacy schema.
43+
* * older versions will be dropped as Hibernate ORM changes pile up
44+
* and support for those older versions becomes too unreliable:
45+
* you should still plan a migration of your schema to a newer version of Hibernate ORM.
46+
* To help with migration, have a look at the source code of {@link DatabaseOrmCompatibilityVersion}:
47+
* it lists relevant settings and includes links to the relevant sections of migration guides.
48+
*
49+
* @asciidoclet
50+
*/
51+
@ConfigItem(name = "database.orm-compatibility.version", defaultValue = "LATEST")
52+
@ConvertWith(DatabaseOrmCompatibilityVersion.Converter.class)
53+
public DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion;
54+
2955
/**
3056
* Configuration for the default persistence unit.
3157
*/

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,9 @@ public void configurationDescriptorBuilding(
382382
// First produce the PUs having a persistence.xml: these are not reactive, as we don't allow using a persistence.xml for them.
383383
for (PersistenceXmlDescriptorBuildItem persistenceXmlDescriptorBuildItem : persistenceXmlDescriptors) {
384384
ParsedPersistenceXmlDescriptor xmlDescriptor = persistenceXmlDescriptorBuildItem.getDescriptor();
385+
Optional<JdbcDataSourceBuildItem> jdbcDataSource = jdbcDataSources.stream()
386+
.filter(i -> i.isDefault())
387+
.findFirst();
385388
persistenceUnitDescriptors
386389
.produce(new PersistenceUnitDescriptorBuildItem(xmlDescriptor,
387390
xmlDescriptor.getName(),
@@ -391,8 +394,9 @@ public void configurationDescriptorBuilding(
391394
null,
392395
jpaModel.getXmlMappings(persistenceXmlDescriptorBuildItem.getDescriptor().getName()),
393396
Collections.emptyMap(),
394-
false,
395-
true));
397+
hibernateOrmConfig.databaseOrmCompatibilityVersion
398+
.settings(jdbcDataSource.map(JdbcDataSourceBuildItem::getDbKind).orElse(null)),
399+
false, true));
396400
}
397401

398402
if (impliedPU.shouldGenerateImpliedBlockingPersistenceUnit()) {
@@ -1200,6 +1204,8 @@ private static void producePersistenceUnitDescriptorFromConfig(
12001204
persistenceUnitConfig.multitenantSchemaDatasource.orElse(null),
12011205
xmlMappings,
12021206
persistenceUnitConfig.unsupportedProperties,
1207+
hibernateOrmConfig.databaseOrmCompatibilityVersion
1208+
.settings(jdbcDataSource.map(JdbcDataSourceBuildItem::getDbKind).orElse(null)),
12031209
false, false));
12041210
}
12051211

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,27 @@ public final class PersistenceUnitDescriptorBuildItem extends MultiBuildItem {
3333
private final String multiTenancySchemaDataSource;
3434
private final List<RecordableXmlMapping> xmlMappings;
3535
private final Map<String, String> quarkusConfigUnsupportedProperties;
36+
private final Map<String, String> databaseOrmCompatibilitySettings;
3637
private final boolean isReactive;
3738
private final boolean fromPersistenceXml;
3839

3940
public PersistenceUnitDescriptorBuildItem(ParsedPersistenceXmlDescriptor descriptor, String configurationName,
4041
List<RecordableXmlMapping> xmlMappings,
4142
Map<String, String> quarkusConfigUnsupportedProperties,
43+
Map<String, String> databaseOrmCompatibilitySettings,
4244
boolean isReactive, boolean fromPersistenceXml) {
4345
this(descriptor, configurationName,
4446
Optional.of(DataSourceUtil.DEFAULT_DATASOURCE_NAME), MultiTenancyStrategy.NONE, null,
45-
xmlMappings, quarkusConfigUnsupportedProperties, isReactive, fromPersistenceXml);
47+
xmlMappings, quarkusConfigUnsupportedProperties, databaseOrmCompatibilitySettings,
48+
isReactive, fromPersistenceXml);
4649
}
4750

4851
public PersistenceUnitDescriptorBuildItem(ParsedPersistenceXmlDescriptor descriptor, String configurationName,
4952
Optional<String> dataSource,
5053
MultiTenancyStrategy multiTenancyStrategy, String multiTenancySchemaDataSource,
5154
List<RecordableXmlMapping> xmlMappings,
5255
Map<String, String> quarkusConfigUnsupportedProperties,
56+
Map<String, String> databaseOrmCompatibilitySettings,
5357
boolean isReactive, boolean fromPersistenceXml) {
5458
this.descriptor = descriptor;
5559
this.configurationName = configurationName;
@@ -58,6 +62,7 @@ public PersistenceUnitDescriptorBuildItem(ParsedPersistenceXmlDescriptor descrip
5862
this.multiTenancySchemaDataSource = multiTenancySchemaDataSource;
5963
this.xmlMappings = xmlMappings;
6064
this.quarkusConfigUnsupportedProperties = quarkusConfigUnsupportedProperties;
65+
this.databaseOrmCompatibilitySettings = databaseOrmCompatibilitySettings;
6166
this.isReactive = isReactive;
6267
this.fromPersistenceXml = fromPersistenceXml;
6368
}
@@ -102,6 +107,7 @@ public QuarkusPersistenceUnitDefinition asOutputPersistenceUnitDefinition(
102107
List<HibernateOrmIntegrationStaticDescriptor> integrationStaticDescriptors) {
103108
return new QuarkusPersistenceUnitDefinition(descriptor, configurationName, dataSource, multiTenancyStrategy,
104109
xmlMappings,
105-
quarkusConfigUnsupportedProperties, isReactive, fromPersistenceXml, integrationStaticDescriptors);
110+
quarkusConfigUnsupportedProperties, databaseOrmCompatibilitySettings,
111+
isReactive, fromPersistenceXml, integrationStaticDescriptors);
106112
}
107113
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package io.quarkus.hibernate.orm.deployment.config;
2+
3+
import java.util.Arrays;
4+
import java.util.HashMap;
5+
import java.util.Locale;
6+
import java.util.Map;
7+
import java.util.stream.Collectors;
8+
9+
import org.hibernate.cfg.AvailableSettings;
10+
11+
import io.quarkus.datasource.common.runtime.DatabaseKind;
12+
13+
public enum DatabaseOrmCompatibilityVersion {
14+
V5_6("5.6") {
15+
@Override
16+
public Map<String, String> settings(String dbKind) {
17+
Map<String, String> result = new HashMap<>(Map.of(
18+
// https://github.com/hibernate/hibernate-orm/blob/6.0/migration-guide.adoc#implicit-identifier-sequence-and-table-name
19+
AvailableSettings.ID_DB_STRUCTURE_NAMING_STRATEGY, "legacy",
20+
// https://github.com/hibernate/hibernate-orm/blob/6.0/migration-guide.adoc#duration-mapping-changes
21+
AvailableSettings.PREFERRED_DURATION_JDBC_TYPE, "BIGINT",
22+
// https://github.com/hibernate/hibernate-orm/blob/6.0/migration-guide.adoc#instant-mapping-changes
23+
AvailableSettings.PREFERRED_INSTANT_JDBC_TYPE, "TIMESTAMP",
24+
// https://github.com/hibernate/hibernate-orm/blob/6.1/migration-guide.adoc#basic-arraycollection-mapping
25+
// Not changing this for now as there's no setting and affected users should be rare, and they can fix their code rather easily.
26+
// https://github.com/hibernate/hibernate-orm/blob/6.1/migration-guide.adoc#enum-mapping-changes
27+
// https://github.com/hibernate/hibernate-orm/blob/6.2/migration-guide.adoc#datatype-for-enums
28+
// Not changing this because we cannot:
29+
// there is no setting for this, so the schema will be incompatible.
30+
// Runtime (queries, persisting) should continue to work, though.
31+
// https://github.com/hibernate/hibernate-orm/blob/6.2/migration-guide.adoc#timezone-and-offset-storage
32+
AvailableSettings.TIMEZONE_DEFAULT_STORAGE, "NORMALIZE"));
33+
34+
if (!usedToSupportUuid(dbKind)) {
35+
// https://github.com/hibernate/hibernate-orm/blob/6.0/migration-guide.adoc#uuid-mapping-changes
36+
// https://github.com/hibernate/hibernate-orm/blob/6.2/migration-guide.adoc#uuid-mapping-changes-on-mariadb
37+
// https://github.com/hibernate/hibernate-orm/blob/6.2/migration-guide.adoc#uuid-mapping-changes-on-sql-server
38+
result.put(AvailableSettings.PREFERRED_UUID_JDBC_TYPE, "BINARY");
39+
}
40+
41+
return result;
42+
}
43+
},
44+
LATEST("latest") {
45+
@Override
46+
public Map<String, String> settings(String dbKind) {
47+
// Nothing to add
48+
return Map.of();
49+
}
50+
};
51+
52+
private static boolean usedToSupportUuid(String dbKind) {
53+
// As far as I can tell, only the PostgreSQL dialect used to support a native UUID type in ORM 5.x.
54+
return DatabaseKind.isPostgreSQL(dbKind);
55+
}
56+
57+
private final String externalRepresentation;
58+
59+
DatabaseOrmCompatibilityVersion(String externalRepresentation) {
60+
this.externalRepresentation = externalRepresentation;
61+
}
62+
63+
public abstract Map<String, String> settings(String dbKind);
64+
65+
public static class Converter
66+
implements org.eclipse.microprofile.config.spi.Converter<DatabaseOrmCompatibilityVersion> {
67+
@Override
68+
public DatabaseOrmCompatibilityVersion convert(String value) {
69+
final String normalizedValue = value.trim().toLowerCase(Locale.ROOT);
70+
for (DatabaseOrmCompatibilityVersion candidate : values()) {
71+
if (candidate.externalRepresentation.equals(normalizedValue)) {
72+
return candidate;
73+
}
74+
}
75+
throw new IllegalArgumentException(String.format(Locale.ROOT,
76+
"Invalid ORM compatibility version: %1$s. Valid versions are: %2$s.",
77+
value,
78+
Arrays.stream(values())
79+
.map(v -> v.externalRepresentation)
80+
.collect(Collectors.toList())));
81+
}
82+
}
83+
}
Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,48 @@
11
package io.quarkus.hibernate.orm.runtime;
22

3-
import java.util.Collections;
4-
import java.util.HashMap;
53
import java.util.Map;
64

75
public class BuildTimeSettings {
86

9-
private Map<String, Object> settings;
7+
private Map<String, Object> quarkusConfigSettings;
8+
private Map<String, String> databaseOrmCompatibilitySettings;
9+
private Map<String, Object> allSettings;
1010

11-
public BuildTimeSettings(Map<String, Object> settings) {
12-
this.settings = Collections.unmodifiableMap(new HashMap<>(settings));
11+
public BuildTimeSettings(Map<String, Object> quarkusConfigSettings,
12+
Map<String, String> databaseOrmCompatibilitySettings,
13+
Map<String, Object> allSettings) {
14+
this.quarkusConfigSettings = Map.copyOf(quarkusConfigSettings);
15+
this.databaseOrmCompatibilitySettings = Map.copyOf(databaseOrmCompatibilitySettings);
16+
this.allSettings = Map.copyOf(allSettings);
1317
}
1418

1519
public Object get(String key) {
16-
return settings.get(key);
20+
return allSettings.get(key);
1721
}
1822

1923
public boolean getBoolean(String key) {
20-
Object propertyValue = settings.get(key);
24+
Object propertyValue = allSettings.get(key);
2125
return propertyValue != null && Boolean.parseBoolean(propertyValue.toString());
2226
}
2327

2428
public boolean isConfigured(String key) {
25-
return settings.containsKey(key);
29+
return allSettings.containsKey(key);
2630
}
2731

28-
public Map<String, Object> getSettings() {
29-
return settings;
32+
public Map<String, Object> getQuarkusConfigSettings() {
33+
return quarkusConfigSettings;
34+
}
35+
36+
public Map<String, String> getDatabaseOrmCompatibilitySettings() {
37+
return databaseOrmCompatibilitySettings;
38+
}
39+
40+
public Map<String, Object> getAllSettings() {
41+
return allSettings;
3042
}
3143

3244
@Override
3345
public String toString() {
34-
return this.getClass().getSimpleName() + " {" + settings.toString() + "}";
46+
return this.getClass().getSimpleName() + " {" + allSettings.toString() + "}";
3547
}
3648
}

extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.util.HashSet;
55
import java.util.List;
66
import java.util.Map;
7+
import java.util.Objects;
78
import java.util.Optional;
89
import java.util.Set;
910

@@ -242,7 +243,12 @@ private RuntimeSettings buildRuntimeSettings(String persistenceUnitName, Recorde
242243
}
243244
for (Map.Entry<String, String> entry : persistenceUnitConfig.unsupportedProperties.entrySet()) {
244245
var key = entry.getKey();
245-
if (runtimeSettingsBuilder.get(key) != null) {
246+
var value = entry.getValue();
247+
var existingValue = runtimeSettingsBuilder.get(key);
248+
if (existingValue != null) {
249+
if (Objects.equals(existingValue, value)) {
250+
continue;
251+
}
246252
log.warnf("Persistence-unit [%s] sets property '%s' to a custom value through '%s',"
247253
+ " but Quarkus already set that property independently."
248254
+ " The custom value will be ignored.",
@@ -253,8 +259,9 @@ private RuntimeSettings buildRuntimeSettings(String persistenceUnitName, Recorde
253259
runtimeSettingsBuilder.put(entry.getKey(), entry.getValue());
254260
}
255261

256-
RuntimeSettings runtimeSettings = runtimeSettingsBuilder.build();
257-
return runtimeSettings;
262+
buildTimeSettings.getDatabaseOrmCompatibilitySettings().forEach(runtimeSettingsBuilder::putIfAbsent);
263+
264+
return runtimeSettingsBuilder.build();
258265
}
259266

260267
private StandardServiceRegistry rewireMetadataAndExtractServiceRegistry(RuntimeSettings runtimeSettings, RecordedState rs,

extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/RuntimeSettings.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,18 @@ public static class Builder {
3939
private final Map<String, Object> settings;
4040

4141
public Builder(BuildTimeSettings buildTimeSettings, IntegrationSettings integrationSettings) {
42-
this.settings = new HashMap<>(buildTimeSettings.getSettings());
42+
this.settings = new HashMap<>(buildTimeSettings.getQuarkusConfigSettings());
4343
this.settings.putAll(integrationSettings.getSettings());
4444
}
4545

4646
public void put(String key, Object value) {
4747
settings.put(key, value);
4848
}
4949

50+
public void putIfAbsent(String key, Object value) {
51+
settings.putIfAbsent(key, value);
52+
}
53+
5054
public Object get(String key) {
5155
return settings.get(key);
5256
}

extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.lang.reflect.InvocationTargetException;
2020
import java.util.ArrayList;
2121
import java.util.Collection;
22+
import java.util.HashMap;
2223
import java.util.Iterator;
2324
import java.util.LinkedHashSet;
2425
import java.util.List;
@@ -137,24 +138,11 @@ public FastBootMetadataBuilder(final QuarkusPersistenceUnitDefinition puDefiniti
137138
final RecordableBootstrap ssrBuilder = RecordableBootstrapFactory.createRecordableBootstrapBuilder(puDefinition);
138139

139140
final MergedSettings mergedSettings = mergeSettings(puDefinition);
140-
this.buildTimeSettings = new BuildTimeSettings(mergedSettings.getConfigurationValues());
141+
this.buildTimeSettings = createBuildTimeSettings(puDefinition, mergedSettings.getConfigurationValues());
141142

142143
// Build the "standard" service registry
143-
ssrBuilder.applySettings(buildTimeSettings.getSettings());
144-
// We don't add unsupported properties to mergedSettings/buildTimeSettings,
145-
// so that we can more easily differentiate between
146-
// properties coming from Quarkus and "unsupported" properties
147-
// on startup (see io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider.buildRuntimeSettings)
148-
for (Map.Entry<String, String> entry : puDefinition.getQuarkusConfigUnsupportedProperties().entrySet()) {
149-
var key = entry.getKey();
150-
if (buildTimeSettings.get(key) != null) {
151-
// Ignore properties that were already set by Quarkus;
152-
// we'll log a warning about those on startup.
153-
// (see io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider.buildRuntimeSettings)
154-
continue;
155-
}
156-
ssrBuilder.applySetting(key, entry.getValue());
157-
}
144+
ssrBuilder.applySettings(buildTimeSettings.getAllSettings());
145+
158146
// We need to initialize the multi tenancy strategy before building the service registry as it is used to
159147
// create metadata builder. Adding services afterwards would lead to unpredicted behavior.
160148
final MultiTenancyStrategy multiTenancyStrategy = puDefinition.getMultitenancyStrategy();
@@ -220,6 +208,26 @@ public FastBootMetadataBuilder(final QuarkusPersistenceUnitDefinition puDefiniti
220208
metamodelBuilder.applyTempClassLoader(null);
221209
}
222210

211+
private BuildTimeSettings createBuildTimeSettings(QuarkusPersistenceUnitDefinition puDefinition,
212+
Map<String, Object> quarkusConfigSettings) {
213+
Map<String, String> quarkusConfigUnsupportedProperties = puDefinition.getQuarkusConfigUnsupportedProperties();
214+
Map<String, Object> allSettings = new HashMap<>(quarkusConfigSettings);
215+
216+
// Ignore properties that were already set by Quarkus;
217+
// we'll log a warning about those on startup.
218+
// (see io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider.buildRuntimeSettings)
219+
quarkusConfigUnsupportedProperties.forEach(allSettings::putIfAbsent);
220+
221+
var databaseOrmCompatibilitySettings = puDefinition.getDatabaseOrmCompatibilitySettings();
222+
databaseOrmCompatibilitySettings.forEach(allSettings::putIfAbsent);
223+
224+
// We keep a separate copy of settings coming from Quarkus config,
225+
// so that we can more easily differentiate between
226+
// properties coming from Quarkus and "unsupported" properties
227+
// on startup (see io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider.buildRuntimeSettings)
228+
return new BuildTimeSettings(quarkusConfigSettings, databaseOrmCompatibilitySettings, allSettings);
229+
}
230+
223231
/**
224232
* Simplified copy of
225233
* org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl#mergeSettings(org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor,

0 commit comments

Comments
 (0)