Skip to content

Commit 9285df2

Browse files
authored
Merge pull request #31540 from yrodiere/i31475-schema-compat
Configuration property to work with database schemas generated for Hibernate ORM 5.6
2 parents 603c3a7 + 47f418b commit 9285df2

File tree

46 files changed

+2235
-46
lines changed

Some content is hidden

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

46 files changed

+2235
-46
lines changed

build-parent/pom.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@
170170
<enforce-test-deps-scope.skip>${enforcer.skip}</enforce-test-deps-scope.skip>
171171

172172
<surefire.argLine.additional></surefire.argLine.additional>
173+
<failsafe.argLine.additional>${surefire.argLine.additional}</failsafe.argLine.additional>
173174
<os-maven-plugin.version>1.7.0</os-maven-plugin.version>
174175

175176
<!-- google cloud functions invoker-->
@@ -460,7 +461,7 @@
460461
<project.groupId>${project.groupId}</project.groupId>
461462
</systemPropertyVariables>
462463
<!-- set tmpdir as early as possible because failsafe sets it too late for JDK16 -->
463-
<argLine>-Djava.io.tmpdir="${project.build.directory}"</argLine>
464+
<argLine>-Djava.io.tmpdir="${project.build.directory}" ${failsafe.argLine.additional}</argLine>
464465
<excludedEnvironmentVariables>MAVEN_OPTS</excludedEnvironmentVariables>
465466
</configuration>
466467
</plugin>

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
import java.util.TreeMap;
66

77
import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil;
8+
import io.quarkus.hibernate.orm.runtime.config.DatabaseOrmCompatibilityVersion;
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,33 @@ 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+
* * 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].
51+
*
52+
* @asciidoclet
53+
*/
54+
@ConfigItem(name = "database.orm-compatibility.version", defaultValue = "LATEST")
55+
@ConvertWith(DatabaseOrmCompatibilityVersion.Converter.class)
56+
public DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion;
57+
2958
/**
3059
* Configuration for the default persistence unit.
3160
*/

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,17 +382,21 @@ 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(),
388391
Optional.of(DataSourceUtil.DEFAULT_DATASOURCE_NAME),
392+
jdbcDataSource.map(JdbcDataSourceBuildItem::getDbKind),
389393
getMultiTenancyStrategy(Optional.ofNullable(persistenceXmlDescriptorBuildItem.getDescriptor()
390394
.getProperties().getProperty("hibernate.multiTenancy"))), //FIXME this property is meaningless in Hibernate ORM 6
391395
null,
392396
jpaModel.getXmlMappings(persistenceXmlDescriptorBuildItem.getDescriptor().getName()),
393397
Collections.emptyMap(),
394-
false,
395-
true));
398+
hibernateOrmConfig.databaseOrmCompatibilityVersion,
399+
false, true));
396400
}
397401

398402
if (impliedPU.shouldGenerateImpliedBlockingPersistenceUnit()) {
@@ -1196,10 +1200,12 @@ private static void producePersistenceUnitDescriptorFromConfig(
11961200
persistenceUnitDescriptors.produce(
11971201
new PersistenceUnitDescriptorBuildItem(descriptor, descriptor.getName(),
11981202
jdbcDataSource.map(JdbcDataSourceBuildItem::getName),
1203+
jdbcDataSource.map(JdbcDataSourceBuildItem::getDbKind),
11991204
multiTenancyStrategy,
12001205
persistenceUnitConfig.multitenantSchemaDatasource.orElse(null),
12011206
xmlMappings,
12021207
persistenceUnitConfig.unsupportedProperties,
1208+
hibernateOrmConfig.databaseOrmCompatibilityVersion,
12031209
false, false));
12041210
}
12051211

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

Lines changed: 16 additions & 6 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,35 +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;
38+
private final DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion;
3639
private final boolean isReactive;
3740
private final boolean fromPersistenceXml;
3841

3942
public PersistenceUnitDescriptorBuildItem(ParsedPersistenceXmlDescriptor descriptor, String configurationName,
43+
Optional<String> dbKind,
4044
List<RecordableXmlMapping> xmlMappings,
4145
Map<String, String> quarkusConfigUnsupportedProperties,
46+
DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion,
4247
boolean isReactive, boolean fromPersistenceXml) {
4348
this(descriptor, configurationName,
44-
Optional.of(DataSourceUtil.DEFAULT_DATASOURCE_NAME), MultiTenancyStrategy.NONE, null,
45-
xmlMappings, quarkusConfigUnsupportedProperties, isReactive, fromPersistenceXml);
49+
Optional.of(DataSourceUtil.DEFAULT_DATASOURCE_NAME), dbKind, MultiTenancyStrategy.NONE, null,
50+
xmlMappings, quarkusConfigUnsupportedProperties, databaseOrmCompatibilityVersion,
51+
isReactive, fromPersistenceXml);
4652
}
4753

4854
public PersistenceUnitDescriptorBuildItem(ParsedPersistenceXmlDescriptor descriptor, String configurationName,
49-
Optional<String> dataSource,
55+
Optional<String> dataSource, Optional<String> dbKind,
5056
MultiTenancyStrategy multiTenancyStrategy, String multiTenancySchemaDataSource,
5157
List<RecordableXmlMapping> xmlMappings,
5258
Map<String, String> quarkusConfigUnsupportedProperties,
59+
DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion,
5360
boolean isReactive, boolean fromPersistenceXml) {
5461
this.descriptor = descriptor;
5562
this.configurationName = configurationName;
5663
this.dataSource = dataSource;
64+
this.dbKind = dbKind;
5765
this.multiTenancyStrategy = multiTenancyStrategy;
5866
this.multiTenancySchemaDataSource = multiTenancySchemaDataSource;
5967
this.xmlMappings = xmlMappings;
6068
this.quarkusConfigUnsupportedProperties = quarkusConfigUnsupportedProperties;
69+
this.databaseOrmCompatibilityVersion = databaseOrmCompatibilityVersion;
6170
this.isReactive = isReactive;
6271
this.fromPersistenceXml = fromPersistenceXml;
6372
}
@@ -100,8 +109,9 @@ public boolean isFromPersistenceXml() {
100109

101110
public QuarkusPersistenceUnitDefinition asOutputPersistenceUnitDefinition(
102111
List<HibernateOrmIntegrationStaticDescriptor> integrationStaticDescriptors) {
103-
return new QuarkusPersistenceUnitDefinition(descriptor, configurationName, dataSource, multiTenancyStrategy,
104-
xmlMappings,
105-
quarkusConfigUnsupportedProperties, isReactive, fromPersistenceXml, integrationStaticDescriptors);
112+
return new QuarkusPersistenceUnitDefinition(descriptor, configurationName, dataSource, dbKind,
113+
multiTenancyStrategy, xmlMappings,
114+
quarkusConfigUnsupportedProperties, databaseOrmCompatibilityVersion,
115+
isReactive, fromPersistenceXml, integrationStaticDescriptors);
106116
}
107117
}
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" })
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() {
Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,57 @@
11
package io.quarkus.hibernate.orm.runtime;
22

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

7-
public class BuildTimeSettings {
5+
import io.quarkus.hibernate.orm.runtime.config.DatabaseOrmCompatibilityVersion;
86

9-
private Map<String, Object> settings;
7+
public class BuildTimeSettings {
108

11-
public BuildTimeSettings(Map<String, Object> settings) {
12-
this.settings = Collections.unmodifiableMap(new HashMap<>(settings));
9+
private Map<String, Object> quarkusConfigSettings;
10+
private DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion;
11+
private Map<String, String> databaseOrmCompatibilitySettings;
12+
private Map<String, Object> allSettings;
13+
14+
public BuildTimeSettings(Map<String, Object> quarkusConfigSettings,
15+
DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion,
16+
Map<String, String> databaseOrmCompatibilitySettings,
17+
Map<String, Object> allSettings) {
18+
this.quarkusConfigSettings = Map.copyOf(quarkusConfigSettings);
19+
this.databaseOrmCompatibilityVersion = databaseOrmCompatibilityVersion;
20+
this.databaseOrmCompatibilitySettings = Map.copyOf(databaseOrmCompatibilitySettings);
21+
this.allSettings = Map.copyOf(allSettings);
1322
}
1423

1524
public Object get(String key) {
16-
return settings.get(key);
25+
return allSettings.get(key);
1726
}
1827

1928
public boolean getBoolean(String key) {
20-
Object propertyValue = settings.get(key);
29+
Object propertyValue = allSettings.get(key);
2130
return propertyValue != null && Boolean.parseBoolean(propertyValue.toString());
2231
}
2332

2433
public boolean isConfigured(String key) {
25-
return settings.containsKey(key);
34+
return allSettings.containsKey(key);
35+
}
36+
37+
public Map<String, Object> getQuarkusConfigSettings() {
38+
return quarkusConfigSettings;
39+
}
40+
41+
public DatabaseOrmCompatibilityVersion getDatabaseOrmCompatibilityVersion() {
42+
return databaseOrmCompatibilityVersion;
43+
}
44+
45+
public Map<String, String> getDatabaseOrmCompatibilitySettings() {
46+
return databaseOrmCompatibilitySettings;
2647
}
2748

28-
public Map<String, Object> getSettings() {
29-
return settings;
49+
public Map<String, Object> getAllSettings() {
50+
return allSettings;
3051
}
3152

3253
@Override
3354
public String toString() {
34-
return this.getClass().getSimpleName() + " {" + settings.toString() + "}";
55+
return this.getClass().getSimpleName() + " {" + allSettings.toString() + "}";
3556
}
3657
}

0 commit comments

Comments
 (0)