Skip to content

Commit 6bf45cf

Browse files
committed
[quarkusio#31974] Add HibernateProcessorUtil class
The goal is to have the common configuration for Hibernate ORM and Hibernate Reactive so that initialization is consistent between the two
1 parent 533f4b8 commit 6bf45cf

File tree

1 file changed

+361
-0
lines changed

1 file changed

+361
-0
lines changed
Lines changed: 361 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,361 @@
1+
package io.quarkus.hibernate.orm.deployment.util;
2+
3+
import static io.quarkus.hibernate.orm.deployment.HibernateConfigUtil.firstPresent;
4+
5+
import java.nio.file.Files;
6+
import java.nio.file.Path;
7+
import java.util.Collections;
8+
import java.util.List;
9+
import java.util.Locale;
10+
import java.util.Map;
11+
import java.util.Optional;
12+
import java.util.OptionalInt;
13+
import java.util.Properties;
14+
import java.util.Set;
15+
import java.util.function.BiConsumer;
16+
import java.util.stream.Collectors;
17+
18+
import jakarta.persistence.SharedCacheMode;
19+
import jakarta.persistence.ValidationMode;
20+
21+
import org.hibernate.cfg.AvailableSettings;
22+
import org.hibernate.id.SequenceMismatchStrategy;
23+
import org.hibernate.jpa.boot.spi.JpaSettings;
24+
import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor;
25+
import org.hibernate.loader.BatchFetchStyle;
26+
import org.jboss.logging.Logger;
27+
28+
import io.quarkus.datasource.common.runtime.DatabaseKind;
29+
import io.quarkus.deployment.Capabilities;
30+
import io.quarkus.deployment.Capability;
31+
import io.quarkus.deployment.annotations.BuildProducer;
32+
import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem;
33+
import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem;
34+
import io.quarkus.deployment.builditem.SystemPropertyBuildItem;
35+
import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem;
36+
import io.quarkus.hibernate.orm.deployment.HibernateConfigUtil;
37+
import io.quarkus.hibernate.orm.deployment.HibernateOrmConfig;
38+
import io.quarkus.hibernate.orm.deployment.HibernateOrmConfigPersistenceUnit;
39+
import io.quarkus.hibernate.orm.deployment.JpaModelBuildItem;
40+
import io.quarkus.hibernate.orm.deployment.spi.DatabaseKindDialectBuildItem;
41+
import io.quarkus.hibernate.orm.runtime.HibernateOrmRuntimeConfig;
42+
import io.quarkus.hibernate.orm.runtime.boot.QuarkusPersistenceUnitDescriptor;
43+
import io.quarkus.hibernate.orm.runtime.customized.FormatMapperKind;
44+
import io.quarkus.runtime.LaunchMode;
45+
import io.quarkus.runtime.configuration.ConfigurationException;
46+
47+
/**
48+
* A set of utilities method to collect the common operations needed to configure the
49+
* Hibernate ORM and Hibernate Reactive extensions.
50+
*/
51+
public final class HibernateProcessorUtil {
52+
private static final Logger LOG = Logger.getLogger(HibernateProcessorUtil.class);
53+
private static final int DEFAULT_BATCH_SIZE = 16;
54+
public static final String NO_SQL_LOAD_SCRIPT_FILE = "no-file";
55+
56+
private HibernateProcessorUtil() {
57+
}
58+
59+
public static boolean hasEntities(JpaModelBuildItem jpaModel) {
60+
return !jpaModel.getEntityClassNames().isEmpty();
61+
}
62+
63+
public static Optional<FormatMapperKind> jsonMapperKind(Capabilities capabilities) {
64+
if (capabilities.isPresent(Capability.JACKSON)) {
65+
return Optional.of(FormatMapperKind.JACKSON);
66+
} else if (capabilities.isPresent(Capability.JSONB)) {
67+
return Optional.of(FormatMapperKind.JSONB);
68+
} else {
69+
return Optional.empty();
70+
}
71+
}
72+
73+
public static Optional<FormatMapperKind> xmlMapperKind(Capabilities capabilities) {
74+
return capabilities.isPresent(Capability.JAXB)
75+
? Optional.of(FormatMapperKind.JAXB)
76+
: Optional.empty();
77+
}
78+
79+
public static boolean isHibernateValidatorPresent(Capabilities capabilities) {
80+
return capabilities.isPresent(Capability.HIBERNATE_VALIDATOR);
81+
}
82+
83+
public static void setDialectAndStorageEngine(
84+
String persistenceUnitName,
85+
Optional<String> dbKind,
86+
Optional<String> explicitDialect,
87+
Optional<String> explicitDbMinVersion,
88+
List<DatabaseKindDialectBuildItem> dbKindDialectBuildItems,
89+
Optional<String> storageEngine,
90+
BuildProducer<SystemPropertyBuildItem> systemProperties,
91+
BiConsumer<String, String> puPropertiesCollector,
92+
Set<String> storageEngineCollector) {
93+
Optional<String> dialect = explicitDialect;
94+
Optional<String> dbProductName = Optional.empty();
95+
Optional<String> dbProductVersion = explicitDbMinVersion;
96+
97+
if (dbKind.isPresent() || explicitDialect.isPresent()) {
98+
for (DatabaseKindDialectBuildItem item : dbKindDialectBuildItems) {
99+
if (dbKind.isPresent() && DatabaseKind.is(dbKind.get(), item.getDbKind())
100+
|| explicitDialect.isPresent() && item.getMatchingDialects().contains(explicitDialect.get())) {
101+
dbProductName = item.getDatabaseProductName();
102+
if (dbProductName.isEmpty() && explicitDialect.isEmpty()) {
103+
dialect = item.getDialectOptional();
104+
}
105+
if (explicitDbMinVersion.isEmpty()) {
106+
dbProductVersion = item.getDefaultDatabaseProductVersion();
107+
}
108+
break;
109+
}
110+
}
111+
if (dialect.isEmpty() && dbProductName.isEmpty()) {
112+
throw new ConfigurationException(
113+
"Could not guess the dialect from the database kind '"
114+
+ dbKind.get()
115+
+ "'. Add an explicit '"
116+
+ HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "dialect")
117+
+ "' property.");
118+
}
119+
}
120+
121+
if (dialect.isPresent()) {
122+
puPropertiesCollector.accept(AvailableSettings.DIALECT, dialect.get());
123+
} else if (dbProductName.isPresent()) {
124+
puPropertiesCollector.accept(AvailableSettings.JAKARTA_HBM2DDL_DB_NAME, dbProductName.get());
125+
}
126+
127+
if (storageEngine.isPresent()) {
128+
if (isMySQLOrMariaDB(dbKind, dialect)) {
129+
// The storage engine has to be set as a system property.
130+
// We record it so that we can later run checks (because we can only set a single value)
131+
storageEngineCollector.add(storageEngine.get());
132+
systemProperties.produce(new SystemPropertyBuildItem(AvailableSettings.STORAGE_ENGINE, storageEngine.get()));
133+
} else {
134+
LOG.warnf(
135+
"The storage engine configuration is being ignored because the database is neither MySQL nor MariaDB.");
136+
}
137+
}
138+
139+
if (dbProductVersion.isPresent()) {
140+
puPropertiesCollector.accept(AvailableSettings.JAKARTA_HBM2DDL_DB_VERSION, dbProductVersion.get());
141+
}
142+
}
143+
144+
public static void configureProperties(QuarkusPersistenceUnitDescriptor desc, HibernateOrmConfigPersistenceUnit config,
145+
HibernateOrmConfig hibernateOrmConfig) {
146+
// Quoting strategy
147+
configureQuoting(desc, config);
148+
149+
// Physical Naming Strategy
150+
config.physicalNamingStrategy().ifPresent(namingStrategy -> desc.getProperties()
151+
.setProperty(AvailableSettings.PHYSICAL_NAMING_STRATEGY, namingStrategy));
152+
153+
// Implicit Naming Strategy
154+
config.implicitNamingStrategy().ifPresent(namingStrategy -> desc.getProperties()
155+
.setProperty(AvailableSettings.IMPLICIT_NAMING_STRATEGY, namingStrategy));
156+
157+
// Metadata builder contributor
158+
config.metadataBuilderContributor().ifPresent(className -> desc.getProperties()
159+
.setProperty(JpaSettings.METADATA_BUILDER_CONTRIBUTOR, className));
160+
161+
// Mapping
162+
if (config.mapping().timezone().timeZoneDefaultStorage().isPresent()) {
163+
desc.getProperties().setProperty(AvailableSettings.TIMEZONE_DEFAULT_STORAGE,
164+
config.mapping().timezone().timeZoneDefaultStorage().get().name());
165+
}
166+
desc.getProperties().setProperty(AvailableSettings.PREFERRED_POOLED_OPTIMIZER,
167+
config.mapping().id().optimizer().idOptimizerDefault()
168+
.orElse(HibernateOrmConfigPersistenceUnit.IdOptimizerType.POOLED_LO).configName);
169+
170+
//charset
171+
desc.getProperties()
172+
.setProperty(AvailableSettings.HBM2DDL_CHARSET_NAME, config.database().charset().name());
173+
174+
// Query
175+
int batchSize = firstPresent(config.fetch().batchSize(), config.batchFetchSize()).orElse(DEFAULT_BATCH_SIZE);
176+
if (batchSize > 0) {
177+
desc.getProperties().setProperty(AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, Integer.toString(batchSize));
178+
desc.getProperties().setProperty(AvailableSettings.BATCH_FETCH_STYLE, BatchFetchStyle.PADDED.toString());
179+
}
180+
181+
// Fetch
182+
if (config.fetch().maxDepth().isPresent()) {
183+
setMaxFetchDepth(desc, config.fetch().maxDepth());
184+
} else if (config.maxFetchDepth().isPresent()) {
185+
setMaxFetchDepth(desc, config.maxFetchDepth());
186+
}
187+
188+
desc.getProperties().setProperty(AvailableSettings.QUERY_PLAN_CACHE_MAX_SIZE, Integer.toString(
189+
config.query().queryPlanCacheMaxSize()));
190+
191+
desc.getProperties().setProperty(AvailableSettings.DEFAULT_NULL_ORDERING,
192+
config.query().defaultNullOrdering().name().toLowerCase(Locale.ROOT));
193+
194+
desc.getProperties().setProperty(AvailableSettings.IN_CLAUSE_PARAMETER_PADDING,
195+
String.valueOf(config.query().inClauseParameterPadding()));
196+
197+
// Disable sequence validations: they are reportedly slow, and people already get the same validation from normal schema validation
198+
desc.getProperties().put(AvailableSettings.SEQUENCE_INCREMENT_SIZE_MISMATCH_STRATEGY,
199+
SequenceMismatchStrategy.NONE);
200+
201+
// JDBC
202+
config.jdbc().timezone().ifPresent(
203+
timezone -> desc.getProperties().setProperty(AvailableSettings.JDBC_TIME_ZONE, timezone));
204+
205+
config.jdbc().statementFetchSize().ifPresent(
206+
fetchSize -> desc.getProperties().setProperty(AvailableSettings.STATEMENT_FETCH_SIZE,
207+
String.valueOf(fetchSize)));
208+
209+
config.jdbc().statementBatchSize().ifPresent(
210+
fetchSize -> desc.getProperties().setProperty(AvailableSettings.STATEMENT_BATCH_SIZE,
211+
String.valueOf(fetchSize)));
212+
213+
// Statistics
214+
if (hibernateOrmConfig.metrics().enabled()
215+
|| (hibernateOrmConfig.statistics().isPresent() && hibernateOrmConfig.statistics().get())) {
216+
desc.getProperties().setProperty(AvailableSettings.GENERATE_STATISTICS, "true");
217+
//When statistics are enabled, the default in Hibernate ORM is to also log them after each
218+
// session; turn that off by default as it's very noisy:
219+
desc.getProperties().setProperty(AvailableSettings.LOG_SESSION_METRICS,
220+
String.valueOf(hibernateOrmConfig.logSessionMetrics().orElse(false)));
221+
}
222+
223+
// Caching
224+
configureCaching(desc, config);
225+
226+
// Validation
227+
configureValidation(desc, config);
228+
229+
// Discriminator Column
230+
desc.getProperties().setProperty(AvailableSettings.IGNORE_EXPLICIT_DISCRIMINATOR_COLUMNS_FOR_JOINED_SUBCLASS,
231+
String.valueOf(config.discriminator().ignoreExplicitForJoined()));
232+
}
233+
234+
private static void setMaxFetchDepth(PersistenceUnitDescriptor descriptor, OptionalInt maxFetchDepth) {
235+
descriptor.getProperties().setProperty(AvailableSettings.MAX_FETCH_DEPTH, String.valueOf(maxFetchDepth.getAsInt()));
236+
}
237+
238+
private static List<String> getSqlLoadScript(Optional<List<String>> sqlLoadScript, LaunchMode launchMode) {
239+
if (sqlLoadScript.isPresent()) {
240+
return sqlLoadScript.get().stream()
241+
.filter(s -> !NO_SQL_LOAD_SCRIPT_FILE.equalsIgnoreCase(s))
242+
.collect(Collectors.toList());
243+
}
244+
if (launchMode == LaunchMode.NORMAL) {
245+
return Collections.emptyList();
246+
}
247+
return List.of("import.sql");
248+
}
249+
250+
private static boolean isMySQLOrMariaDB(Optional<String> dbKind, Optional<String> dialect) {
251+
if (dbKind.isPresent() && (DatabaseKind.isMySQL(dbKind.get()) || DatabaseKind.isMariaDB(dbKind.get()))) {
252+
return true;
253+
}
254+
if (dialect.isPresent()) {
255+
String lowercaseDialect = dialect.get().toLowerCase(Locale.ROOT);
256+
return lowercaseDialect.contains("mysql") || lowercaseDialect.contains("mariadb");
257+
}
258+
return false;
259+
}
260+
261+
private static void configureCaching(QuarkusPersistenceUnitDescriptor descriptor,
262+
HibernateOrmConfigPersistenceUnit config) {
263+
if (config.secondLevelCachingEnabled()) {
264+
Properties p = descriptor.getProperties();
265+
p.putIfAbsent(AvailableSettings.USE_DIRECT_REFERENCE_CACHE_ENTRIES, Boolean.TRUE);
266+
p.putIfAbsent(AvailableSettings.USE_SECOND_LEVEL_CACHE, Boolean.TRUE);
267+
p.putIfAbsent(AvailableSettings.USE_QUERY_CACHE, Boolean.TRUE);
268+
p.putIfAbsent(AvailableSettings.JAKARTA_SHARED_CACHE_MODE, SharedCacheMode.ENABLE_SELECTIVE);
269+
Map<String, String> cacheConfigEntries = HibernateConfigUtil.getCacheConfigEntries(config);
270+
for (Map.Entry<String, String> entry : cacheConfigEntries.entrySet()) {
271+
descriptor.getProperties().setProperty(entry.getKey(), entry.getValue());
272+
}
273+
} else {
274+
Properties p = descriptor.getProperties();
275+
p.put(AvailableSettings.USE_DIRECT_REFERENCE_CACHE_ENTRIES, Boolean.FALSE);
276+
p.put(AvailableSettings.USE_SECOND_LEVEL_CACHE, Boolean.FALSE);
277+
p.put(AvailableSettings.USE_QUERY_CACHE, Boolean.FALSE);
278+
p.put(AvailableSettings.JAKARTA_SHARED_CACHE_MODE, SharedCacheMode.NONE);
279+
}
280+
}
281+
282+
private static void configureValidation(QuarkusPersistenceUnitDescriptor descriptor,
283+
HibernateOrmConfigPersistenceUnit config) {
284+
if (!config.validation().enabled()) {
285+
descriptor.getProperties().setProperty(AvailableSettings.JAKARTA_VALIDATION_MODE, ValidationMode.NONE.name());
286+
} else {
287+
descriptor.getProperties().setProperty(
288+
AvailableSettings.JAKARTA_VALIDATION_MODE,
289+
config.validation().mode()
290+
.stream()
291+
.map(Enum::name)
292+
.collect(Collectors.joining(",")));
293+
}
294+
}
295+
296+
private static void configureQuoting(QuarkusPersistenceUnitDescriptor desc,
297+
HibernateOrmConfigPersistenceUnit persistenceUnitConfig) {
298+
if (persistenceUnitConfig.quoteIdentifiers()
299+
.strategy() == HibernateOrmConfigPersistenceUnit.IdentifierQuotingStrategy.ALL
300+
|| persistenceUnitConfig.quoteIdentifiers()
301+
.strategy() == HibernateOrmConfigPersistenceUnit.IdentifierQuotingStrategy.ALL_EXCEPT_COLUMN_DEFINITIONS
302+
|| persistenceUnitConfig.database().globallyQuotedIdentifiers()) {
303+
desc.getProperties().setProperty(AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, "true");
304+
}
305+
if (persistenceUnitConfig.quoteIdentifiers()
306+
.strategy() == HibernateOrmConfigPersistenceUnit.IdentifierQuotingStrategy.ALL_EXCEPT_COLUMN_DEFINITIONS) {
307+
desc.getProperties().setProperty(AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS_SKIP_COLUMN_DEFINITIONS, "true");
308+
} else if (persistenceUnitConfig.quoteIdentifiers()
309+
.strategy() == HibernateOrmConfigPersistenceUnit.IdentifierQuotingStrategy.ONLY_KEYWORDS) {
310+
desc.getProperties().setProperty(AvailableSettings.KEYWORD_AUTO_QUOTING_ENABLED, "true");
311+
}
312+
}
313+
314+
public static void configureSqlLoadScript(String persistenceUnitName,
315+
HibernateOrmConfigPersistenceUnit persistenceUnitConfig,
316+
ApplicationArchivesBuildItem applicationArchivesBuildItem, LaunchMode launchMode,
317+
BuildProducer<NativeImageResourceBuildItem> nativeImageResources,
318+
BuildProducer<HotDeploymentWatchedFileBuildItem> hotDeploymentWatchedFiles,
319+
QuarkusPersistenceUnitDescriptor descriptor) {
320+
// sql-load-scripts
321+
List<String> importFiles = getSqlLoadScript(persistenceUnitConfig.sqlLoadScript(), launchMode);
322+
if (!importFiles.isEmpty()) {
323+
for (String importFile : importFiles) {
324+
Path loadScriptPath;
325+
try {
326+
loadScriptPath = applicationArchivesBuildItem.getRootArchive().getChildPath(importFile);
327+
} catch (RuntimeException e) {
328+
throw new ConfigurationException(
329+
"Unable to interpret path referenced in '"
330+
+ HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "sql-load-script") + "="
331+
+ String.join(",", persistenceUnitConfig.sqlLoadScript().get())
332+
+ "': " + e.getMessage());
333+
}
334+
335+
if (loadScriptPath != null && !Files.isDirectory(loadScriptPath)) {
336+
// enlist resource if present
337+
nativeImageResources.produce(new NativeImageResourceBuildItem(importFile));
338+
} else if (persistenceUnitConfig.sqlLoadScript().isPresent()) {
339+
//raise exception if explicit file is not present (i.e. not the default)
340+
throw new ConfigurationException(
341+
"Unable to find file referenced in '"
342+
+ HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "sql-load-script") + "="
343+
+ String.join(",", persistenceUnitConfig.sqlLoadScript().get())
344+
+ "'. Remove property or add file to your path.");
345+
}
346+
// in dev mode we want to make sure that we watch for changes to file even if it doesn't currently exist
347+
// as a user could still add it after performing the initial configuration
348+
hotDeploymentWatchedFiles.produce(new HotDeploymentWatchedFileBuildItem(importFile));
349+
}
350+
351+
// only set the found import files if configured
352+
if (persistenceUnitConfig.sqlLoadScript().isPresent()) {
353+
descriptor.getProperties().setProperty(AvailableSettings.HBM2DDL_IMPORT_FILES, String.join(",", importFiles));
354+
}
355+
} else {
356+
//Disable implicit loading of the default import script (import.sql)
357+
descriptor.getProperties().setProperty(AvailableSettings.HBM2DDL_IMPORT_FILES, "");
358+
descriptor.getProperties().setProperty(AvailableSettings.HBM2DDL_SKIP_DEFAULT_IMPORT_FILE, "true");
359+
}
360+
}
361+
}

0 commit comments

Comments
 (0)