Skip to content

Commit a1051b1

Browse files
committed
Warn on startup when setting a database/orm compatibility version
1 parent d4ebbe9 commit a1051b1

File tree

13 files changed

+223
-49
lines changed

13 files changed

+223
-49
lines changed

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
import java.util.Optional;
55
import java.util.TreeMap;
66

7-
import io.quarkus.hibernate.orm.deployment.config.DatabaseOrmCompatibilityVersion;
87
import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil;
8+
import io.quarkus.hibernate.orm.runtime.config.DatabaseOrmCompatibilityVersion;
99
import io.quarkus.runtime.annotations.ConfigDocMapKey;
1010
import io.quarkus.runtime.annotations.ConfigDocSection;
1111
import io.quarkus.runtime.annotations.ConfigGroup;
@@ -40,11 +40,14 @@ public class HibernateOrmConfig {
4040
* but it may still expect a different (but runtime-compatible) schema.
4141
* * robust test suites are still useful and recommended:
4242
* 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.
43+
* * this feature is inherently unstable:
44+
* some aspects of it may stop working in future versions of Quarkus,
45+
* and older versions will be dropped as Hibernate ORM changes pile up
46+
* and support for those older versions becomes too unreliable.
47+
* * you should still plan a migration of your schema to a newer version of Hibernate ORM.
48+
* For help with migration, refer to
49+
* link:https://github.com/quarkusio/quarkus/wiki/Migration-Guide-3.0:-Hibernate-ORM-5-to-6-migration[the Quarkus 3
50+
* migration guide from Hibernate ORM 5 to 6].
4851
*
4952
* @asciidoclet
5053
*/

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -389,13 +389,13 @@ public void configurationDescriptorBuilding(
389389
.produce(new PersistenceUnitDescriptorBuildItem(xmlDescriptor,
390390
xmlDescriptor.getName(),
391391
Optional.of(DataSourceUtil.DEFAULT_DATASOURCE_NAME),
392+
jdbcDataSource.map(JdbcDataSourceBuildItem::getDbKind),
392393
getMultiTenancyStrategy(Optional.ofNullable(persistenceXmlDescriptorBuildItem.getDescriptor()
393394
.getProperties().getProperty("hibernate.multiTenancy"))), //FIXME this property is meaningless in Hibernate ORM 6
394395
null,
395396
jpaModel.getXmlMappings(persistenceXmlDescriptorBuildItem.getDescriptor().getName()),
396397
Collections.emptyMap(),
397-
hibernateOrmConfig.databaseOrmCompatibilityVersion
398-
.settings(jdbcDataSource.map(JdbcDataSourceBuildItem::getDbKind).orElse(null)),
398+
hibernateOrmConfig.databaseOrmCompatibilityVersion,
399399
false, true));
400400
}
401401

@@ -1200,12 +1200,12 @@ private static void producePersistenceUnitDescriptorFromConfig(
12001200
persistenceUnitDescriptors.produce(
12011201
new PersistenceUnitDescriptorBuildItem(descriptor, descriptor.getName(),
12021202
jdbcDataSource.map(JdbcDataSourceBuildItem::getName),
1203+
jdbcDataSource.map(JdbcDataSourceBuildItem::getDbKind),
12031204
multiTenancyStrategy,
12041205
persistenceUnitConfig.multitenantSchemaDatasource.orElse(null),
12051206
xmlMappings,
12061207
persistenceUnitConfig.unsupportedProperties,
1207-
hibernateOrmConfig.databaseOrmCompatibilityVersion
1208-
.settings(jdbcDataSource.map(JdbcDataSourceBuildItem::getDbKind).orElse(null)),
1208+
hibernateOrmConfig.databaseOrmCompatibilityVersion,
12091209
false, false));
12101210
}
12111211

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

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import io.quarkus.datasource.common.runtime.DataSourceUtil;
1212
import io.quarkus.hibernate.orm.runtime.boot.QuarkusPersistenceUnitDefinition;
1313
import io.quarkus.hibernate.orm.runtime.boot.xml.RecordableXmlMapping;
14+
import io.quarkus.hibernate.orm.runtime.config.DatabaseOrmCompatibilityVersion;
1415
import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationStaticDescriptor;
1516
import io.quarkus.hibernate.orm.runtime.migration.MultiTenancyStrategy;
1617

@@ -29,40 +30,43 @@ public final class PersistenceUnitDescriptorBuildItem extends MultiBuildItem {
2930
// use the name "<default>", so we need to convert between those.
3031
private final String configurationName;
3132
private final Optional<String> dataSource;
33+
private final Optional<String> dbKind;
3234
private final MultiTenancyStrategy multiTenancyStrategy;
3335
private final String multiTenancySchemaDataSource;
3436
private final List<RecordableXmlMapping> xmlMappings;
3537
private final Map<String, String> quarkusConfigUnsupportedProperties;
36-
private final Map<String, String> databaseOrmCompatibilitySettings;
38+
private final DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion;
3739
private final boolean isReactive;
3840
private final boolean fromPersistenceXml;
3941

4042
public PersistenceUnitDescriptorBuildItem(ParsedPersistenceXmlDescriptor descriptor, String configurationName,
43+
Optional<String> dbKind,
4144
List<RecordableXmlMapping> xmlMappings,
4245
Map<String, String> quarkusConfigUnsupportedProperties,
43-
Map<String, String> databaseOrmCompatibilitySettings,
46+
DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion,
4447
boolean isReactive, boolean fromPersistenceXml) {
4548
this(descriptor, configurationName,
46-
Optional.of(DataSourceUtil.DEFAULT_DATASOURCE_NAME), MultiTenancyStrategy.NONE, null,
47-
xmlMappings, quarkusConfigUnsupportedProperties, databaseOrmCompatibilitySettings,
49+
Optional.of(DataSourceUtil.DEFAULT_DATASOURCE_NAME), dbKind, MultiTenancyStrategy.NONE, null,
50+
xmlMappings, quarkusConfigUnsupportedProperties, databaseOrmCompatibilityVersion,
4851
isReactive, fromPersistenceXml);
4952
}
5053

5154
public PersistenceUnitDescriptorBuildItem(ParsedPersistenceXmlDescriptor descriptor, String configurationName,
52-
Optional<String> dataSource,
55+
Optional<String> dataSource, Optional<String> dbKind,
5356
MultiTenancyStrategy multiTenancyStrategy, String multiTenancySchemaDataSource,
5457
List<RecordableXmlMapping> xmlMappings,
5558
Map<String, String> quarkusConfigUnsupportedProperties,
56-
Map<String, String> databaseOrmCompatibilitySettings,
59+
DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion,
5760
boolean isReactive, boolean fromPersistenceXml) {
5861
this.descriptor = descriptor;
5962
this.configurationName = configurationName;
6063
this.dataSource = dataSource;
64+
this.dbKind = dbKind;
6165
this.multiTenancyStrategy = multiTenancyStrategy;
6266
this.multiTenancySchemaDataSource = multiTenancySchemaDataSource;
6367
this.xmlMappings = xmlMappings;
6468
this.quarkusConfigUnsupportedProperties = quarkusConfigUnsupportedProperties;
65-
this.databaseOrmCompatibilitySettings = databaseOrmCompatibilitySettings;
69+
this.databaseOrmCompatibilityVersion = databaseOrmCompatibilityVersion;
6670
this.isReactive = isReactive;
6771
this.fromPersistenceXml = fromPersistenceXml;
6872
}
@@ -105,9 +109,9 @@ public boolean isFromPersistenceXml() {
105109

106110
public QuarkusPersistenceUnitDefinition asOutputPersistenceUnitDefinition(
107111
List<HibernateOrmIntegrationStaticDescriptor> integrationStaticDescriptors) {
108-
return new QuarkusPersistenceUnitDefinition(descriptor, configurationName, dataSource, multiTenancyStrategy,
109-
xmlMappings,
110-
quarkusConfigUnsupportedProperties, databaseOrmCompatibilitySettings,
112+
return new QuarkusPersistenceUnitDefinition(descriptor, configurationName, dataSource, dbKind,
113+
multiTenancyStrategy, xmlMappings,
114+
quarkusConfigUnsupportedProperties, databaseOrmCompatibilityVersion,
111115
isReactive, fromPersistenceXml, integrationStaticDescriptors);
112116
}
113117
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package io.quarkus.hibernate.orm.config.unsupportedproperties;
1+
package io.quarkus.hibernate.orm.config;
22

33
import java.io.Serializable;
44
import java.util.ArrayList;
@@ -20,7 +20,7 @@
2020
* Feel free to use some other solution if you find one.
2121
*/
2222
public class SettingsSpyingIdentifierGenerator implements IdentifierGenerator {
23-
static final List<Map<String, Object>> collectedSettings = new ArrayList<>();
23+
public static final List<Map<String, Object>> collectedSettings = new ArrayList<>();
2424

2525
@Override
2626
@SuppressWarnings({ "unchecked", "rawtypes" })
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package io.quarkus.hibernate.orm.config.databaseormcompatibility;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import java.util.Map;
6+
import java.util.logging.Formatter;
7+
import java.util.logging.Level;
8+
9+
import jakarta.inject.Inject;
10+
import jakarta.persistence.Entity;
11+
import jakarta.persistence.EntityManager;
12+
import jakarta.persistence.EntityManagerFactory;
13+
import jakarta.persistence.GeneratedValue;
14+
import jakarta.persistence.Id;
15+
16+
import org.hibernate.annotations.GenericGenerator;
17+
import org.hibernate.cfg.AvailableSettings;
18+
import org.jboss.logmanager.formatters.PatternFormatter;
19+
import org.junit.jupiter.api.Test;
20+
import org.junit.jupiter.api.extension.RegisterExtension;
21+
22+
import io.quarkus.hibernate.orm.config.SettingsSpyingIdentifierGenerator;
23+
import io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider;
24+
import io.quarkus.test.QuarkusUnitTest;
25+
26+
public class DatabaseOrmCompatibilityVersionTest {
27+
28+
private static final Formatter LOG_FORMATTER = new PatternFormatter("%s");
29+
30+
@RegisterExtension
31+
static QuarkusUnitTest runner = new QuarkusUnitTest()
32+
.withApplicationRoot((jar) -> jar
33+
.addClass(SpyingIdentifierGeneratorEntity.class)
34+
.addClass(SettingsSpyingIdentifierGenerator.class))
35+
.withConfigurationResource("application.properties")
36+
.overrideConfigKey("quarkus.hibernate-orm.database.orm-compatibility.version", "5.6")
37+
// We allow overriding database/orm compatibility settings with .unsupported-properties,
38+
// to enable step-by-step migration
39+
.overrideConfigKey(
40+
"quarkus.hibernate-orm.unsupported-properties.\"" + AvailableSettings.PREFERRED_INSTANT_JDBC_TYPE + "\"",
41+
"TIMESTAMP_UTC")
42+
// Expect warnings on startup
43+
.setLogRecordPredicate(record -> FastBootHibernatePersistenceProvider.class.getName().equals(record.getLoggerName())
44+
&& record.getLevel().intValue() >= Level.WARNING.intValue())
45+
.assertLogRecords(records -> {
46+
var assertion = assertThat(records)
47+
.as("Warnings on startup")
48+
.hasSizeGreaterThanOrEqualTo(3);
49+
assertion.element(0).satisfies(record -> assertThat(LOG_FORMATTER.formatMessage(record))
50+
.contains("Persistence-unit [<default>] sets unsupported properties")
51+
// We should not log property values, that could be a security breach for some properties.
52+
.doesNotContain("some-value"));
53+
assertion.element(1).satisfies(record -> assertThat(LOG_FORMATTER.formatMessage(record))
54+
.contains("Persistence-unit [<default>]:"
55+
+ " enabling best-effort backwards compatibility with 'quarkus.hibernate-orm.database.orm-compatibility.version=5.6'.",
56+
"Quarkus will attempt to change the behavior and expected schema of Hibernate ORM"
57+
+ " to match those of Hibernate ORM 5.6.",
58+
"This is an inherently best-effort feature",
59+
"may stop working in future versions of Quarkus",
60+
"Consider migrating your application",
61+
"https://github.com/quarkusio/quarkus/wiki/Migration-Guide-3.0:-Hibernate-ORM-5-to-6-migration"));
62+
assertion.anySatisfy(record -> assertThat(LOG_FORMATTER.formatMessage(record))
63+
.contains(
64+
"Persistence-unit [<default>] - 5.6 compatibility: setting 'hibernate.timezone.default_storage=NORMALIZE'.",
65+
"affects Hibernate ORM's behavior and schema compatibility",
66+
"may stop working in future versions of Quarkus"));
67+
});
68+
69+
@Inject
70+
EntityManagerFactory emf;
71+
72+
@Inject
73+
EntityManager em;
74+
75+
@Test
76+
public void testPropertiesPropagatedToStaticInit() {
77+
assertThat(SettingsSpyingIdentifierGenerator.collectedSettings).hasSize(1);
78+
Map<String, Object> settings = SettingsSpyingIdentifierGenerator.collectedSettings.get(0);
79+
assertThat(settings).containsAllEntriesOf(Map.of(
80+
AvailableSettings.TIMEZONE_DEFAULT_STORAGE, "NORMALIZE",
81+
// We allow overriding database/orm compatibility settings with .unsupported-properties,
82+
// to enable step-by-step migration
83+
AvailableSettings.PREFERRED_INSTANT_JDBC_TYPE, "TIMESTAMP_UTC"));
84+
}
85+
86+
@Test
87+
public void testPropertiesPropagatedToRuntimeInit() {
88+
assertThat(emf.getProperties()).containsAllEntriesOf(Map.of(
89+
AvailableSettings.TIMEZONE_DEFAULT_STORAGE, "NORMALIZE",
90+
// We allow overriding database/orm compatibility settings with .unsupported-properties,
91+
// to enable step-by-step migration
92+
AvailableSettings.PREFERRED_INSTANT_JDBC_TYPE, "TIMESTAMP_UTC"));
93+
}
94+
95+
@Entity
96+
public static class SpyingIdentifierGeneratorEntity {
97+
@Id
98+
@GeneratedValue(generator = "spying-generator")
99+
@GenericGenerator(name = "spying-generator", strategy = "io.quarkus.hibernate.orm.config.SettingsSpyingIdentifierGenerator")
100+
private Long id;
101+
102+
public SpyingIdentifierGeneratorEntity() {
103+
}
104+
105+
public Long getId() {
106+
return id;
107+
}
108+
109+
public void setId(Long id) {
110+
this.id = id;
111+
}
112+
}
113+
}

extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/unsupportedproperties/UnsupportedPropertiesTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.junit.jupiter.api.Test;
2828
import org.junit.jupiter.api.extension.RegisterExtension;
2929

30+
import io.quarkus.hibernate.orm.config.SettingsSpyingIdentifierGenerator;
3031
import io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider;
3132
import io.quarkus.narayana.jta.QuarkusTransaction;
3233
import io.quarkus.test.QuarkusUnitTest;
@@ -211,7 +212,7 @@ public void setParent(ParentEntity parent) {
211212
public static class SpyingIdentifierGeneratorEntity {
212213
@Id
213214
@GeneratedValue(generator = "spying-generator")
214-
@GenericGenerator(name = "spying-generator", strategy = "io.quarkus.hibernate.orm.config.unsupportedproperties.SettingsSpyingIdentifierGenerator")
215+
@GenericGenerator(name = "spying-generator", strategy = "io.quarkus.hibernate.orm.config.SettingsSpyingIdentifierGenerator")
215216
private Long id;
216217

217218
public SpyingIdentifierGeneratorEntity() {

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,21 @@
22

33
import java.util.Map;
44

5+
import io.quarkus.hibernate.orm.runtime.config.DatabaseOrmCompatibilityVersion;
6+
57
public class BuildTimeSettings {
68

79
private Map<String, Object> quarkusConfigSettings;
10+
private DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion;
811
private Map<String, String> databaseOrmCompatibilitySettings;
912
private Map<String, Object> allSettings;
1013

1114
public BuildTimeSettings(Map<String, Object> quarkusConfigSettings,
15+
DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion,
1216
Map<String, String> databaseOrmCompatibilitySettings,
1317
Map<String, Object> allSettings) {
1418
this.quarkusConfigSettings = Map.copyOf(quarkusConfigSettings);
19+
this.databaseOrmCompatibilityVersion = databaseOrmCompatibilityVersion;
1520
this.databaseOrmCompatibilitySettings = Map.copyOf(databaseOrmCompatibilitySettings);
1621
this.allSettings = Map.copyOf(allSettings);
1722
}
@@ -33,6 +38,10 @@ public Map<String, Object> getQuarkusConfigSettings() {
3338
return quarkusConfigSettings;
3439
}
3540

41+
public DatabaseOrmCompatibilityVersion getDatabaseOrmCompatibilityVersion() {
42+
return databaseOrmCompatibilityVersion;
43+
}
44+
3645
public Map<String, String> getDatabaseOrmCompatibilitySettings() {
3746
return databaseOrmCompatibilitySettings;
3847
}

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

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import java.util.HashSet;
55
import java.util.List;
66
import java.util.Map;
7-
import java.util.Objects;
87
import java.util.Optional;
98
import java.util.Set;
109

@@ -34,6 +33,7 @@
3433
import io.quarkus.hibernate.orm.runtime.boot.FastBootEntityManagerFactoryBuilder;
3534
import io.quarkus.hibernate.orm.runtime.boot.RuntimePersistenceUnitDescriptor;
3635
import io.quarkus.hibernate.orm.runtime.boot.registry.PreconfiguredServiceRegistryBuilder;
36+
import io.quarkus.hibernate.orm.runtime.config.DatabaseOrmCompatibilityVersion;
3737
import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationRuntimeDescriptor;
3838
import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationRuntimeInitListener;
3939
import io.quarkus.hibernate.orm.runtime.recording.PrevalidatedQuarkusMetadata;
@@ -254,7 +254,35 @@ private RuntimeSettings buildRuntimeSettings(String persistenceUnitName, Recorde
254254
runtimeSettingsBuilder.put(entry.getKey(), entry.getValue());
255255
}
256256

257-
buildTimeSettings.getDatabaseOrmCompatibilitySettings().forEach(runtimeSettingsBuilder::putIfAbsent);
257+
var databaseOrmCompatibilityVersion = buildTimeSettings.getDatabaseOrmCompatibilityVersion();
258+
var databaseOrmCompatibilitySettings = buildTimeSettings.getDatabaseOrmCompatibilitySettings();
259+
if (databaseOrmCompatibilityVersion != DatabaseOrmCompatibilityVersion.LATEST) {
260+
log.warnf("Persistence-unit [%1$s]: enabling best-effort backwards compatibility with '%2$s=%3$s'."
261+
+ " Quarkus will attempt to change the behavior and expected schema of Hibernate ORM"
262+
+ " to match those of Hibernate ORM %3$s."
263+
+ " This is an inherently best-effort feature that cannot address all "
264+
+ " backwards-incompatible changes of Hibernate ORM 6."
265+
+ " It is also inherently unstable and may stop working in future versions of Quarkus."
266+
+ " Consider migrating your application to native Hibernate ORM 6 behavior;"
267+
+ " see https://github.com/quarkusio/quarkus/wiki/Migration-Guide-3.0:-Hibernate-ORM-5-to-6-migration for more information.",
268+
persistenceUnitName,
269+
HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "database.orm-compatibility.version"),
270+
databaseOrmCompatibilityVersion.externalRepresentation,
271+
persistenceUnitConfig.unsupportedProperties.keySet());
272+
}
273+
for (Map.Entry<String, String> entry : databaseOrmCompatibilitySettings.entrySet()) {
274+
var key = entry.getKey();
275+
var value = entry.getValue();
276+
if (!runtimeSettingsBuilder.isConfigured(key)) {
277+
log.warnf("Persistence-unit [%1$s] - %2$s compatibility: setting '%3$s=%4$s'."
278+
+ " This affects Hibernate ORM's behavior and schema compatibility"
279+
+ " and may stop working in future versions of Quarkus.",
280+
persistenceUnitName,
281+
databaseOrmCompatibilityVersion.externalRepresentation,
282+
key, value);
283+
runtimeSettingsBuilder.put(key, value);
284+
}
285+
}
258286

259287
return runtimeSettingsBuilder.build();
260288
}

0 commit comments

Comments
 (0)