Skip to content

Commit d3fafc8

Browse files
authored
Merge pull request #50454 from NickBeginner/nbelcastro/issue-33328
Introduce support for `hibernate.type.preferred_*_jdbc_type` properties
2 parents 3552e62 + f64d9d7 commit d3fafc8

File tree

10 files changed

+297
-5
lines changed

10 files changed

+297
-5
lines changed

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

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,51 @@ interface HibernateOrmConfigPersistenceUnitMapping {
357357
*/
358358
Id id();
359359

360+
Duration duration();
361+
362+
/**
363+
* The preferred JDBC type to use for storing {@link java.time.Instant} values.
364+
* <p>
365+
* Can be overridden locally using `@JdbcType`, `@JdbcTypeCode`, and similar annotations.
366+
* <p>
367+
* Can also specify the name of the SqlTypes constant field,
368+
* for example, `quarkus.hibernate-orm.mapping.type.preferred_instant_jdbc_type=TIMESTAMP`
369+
* or `quarkus.hibernate-orm.mapping.type.preferred_instant_jdbc_type=INSTANT`.
370+
*
371+
* @asciidoclet
372+
*/
373+
@WithName("instant.preferred-jdbc-type")
374+
@ConfigDocDefault("TIMESTAMP")
375+
Optional<@WithConverter(TrimmedStringConverter.class) String> instantPreferredJdbcType();
376+
377+
/**
378+
* The preferred JDBC type to use for storing boolean values.
379+
* <p>
380+
* Can be overridden locally using `@JdbcType`, `@JdbcTypeCode`, and similar annotations.
381+
* <p>
382+
* Can also specify the name of the SqlTypes constant field,
383+
* for example, `quarkus.hibernate-orm.mapping.type.boolean_jdbc_type=BIT`.
384+
*
385+
* @asciidoclet
386+
*/
387+
@WithName("boolean.preferred-jdbc-type")
388+
@ConfigDocDefault("BOOLEAN")
389+
Optional<@WithConverter(TrimmedStringConverter.class) String> booleanPreferredJdbcType();
390+
391+
/**
392+
* The preferred JDBC type to use for storing {@link java.util.UUID} values.
393+
* <p>
394+
* Can be overridden locally using `@JdbcType`, `@JdbcTypeCode`, and similar annotations.
395+
* <p>
396+
* Can also specify the name of the SqlTypes constant field,
397+
* for example, `quarkus.hibernate-orm.mapping.type.uuid_jdbc_type=CHAR`.
398+
*
399+
* @asciidoclet
400+
*/
401+
@WithName("uuid.preferred-jdbc-type")
402+
@ConfigDocDefault("UUID")
403+
Optional<@WithConverter(TrimmedStringConverter.class) String> UUIDPreferredJdbcType();
404+
360405
@ConfigGroup
361406
interface Timezone {
362407
/**
@@ -436,9 +481,31 @@ interface Optimizer {
436481
}
437482
}
438483

484+
@ConfigGroup
485+
interface Duration {
486+
487+
/**
488+
* The preferred JDBC type to use for storing {@link java.time.Duration} values.
489+
* <p>
490+
* Can be overridden locally using `@JdbcType`, `@JdbcTypeCode`, and similar annotations.
491+
* <p>
492+
* Can also specify the name of the SqlTypes constant field,
493+
* for example, `quarkus.hibernate-orm.mapping.type.preferred_jdbc_type=INTERVAL_SECOND`.
494+
*
495+
* @asciidoclet
496+
*/
497+
@WithName("preferred-jdbc-type")
498+
@ConfigDocDefault("INTERVAL_SECOND")
499+
Optional<@WithConverter(TrimmedStringConverter.class) String> durationPreferredJdbcType();
500+
}
501+
439502
default boolean isAnyPropertySet() {
440-
return timezone().timeZoneDefaultStorage().isPresent()
441-
|| id().optimizer().idOptimizerDefault().isPresent();
503+
return timezone().timeZoneDefaultStorage().isPresent() ||
504+
id().optimizer().idOptimizerDefault().isPresent() ||
505+
duration().durationPreferredJdbcType().isPresent() ||
506+
instantPreferredJdbcType().isPresent() ||
507+
booleanPreferredJdbcType().isPresent() ||
508+
UUIDPreferredJdbcType().isPresent();
442509
}
443510

444511
}

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,28 @@ public static void configureProperties(QuarkusPersistenceUnitDescriptor desc, Hi
333333
config.mapping().id().optimizer().idOptimizerDefault()
334334
.orElse(HibernateOrmConfigPersistenceUnit.IdOptimizerType.POOLED_LO).configName);
335335

336+
// Duration
337+
config.mapping().duration().durationPreferredJdbcType().ifPresent(duration -> desc.getProperties().setProperty(
338+
AvailableSettings.PREFERRED_DURATION_JDBC_TYPE,
339+
duration));
340+
341+
// Instant
342+
config.mapping().instantPreferredJdbcType().ifPresent(instant -> desc.getProperties().setProperty(
343+
AvailableSettings.PREFERRED_INSTANT_JDBC_TYPE,
344+
instant));
345+
346+
// Boolean
347+
config.mapping().booleanPreferredJdbcType().ifPresent(
348+
bool -> desc.getProperties().setProperty(
349+
AvailableSettings.PREFERRED_BOOLEAN_JDBC_TYPE,
350+
bool));
351+
352+
// UUID
353+
config.mapping().UUIDPreferredJdbcType().ifPresent(
354+
uuid -> desc.getProperties().setProperty(
355+
AvailableSettings.PREFERRED_UUID_JDBC_TYPE,
356+
uuid));
357+
336358
//charset
337359
desc.getProperties()
338360
.setProperty(AvailableSettings.HBM2DDL_CHARSET_NAME, config.database().charset().name());

extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/properties/ConfigPropertiesTest.java

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ public class ConfigPropertiesTest {
3535
// Overrides to test that Quarkus configuration properties are taken into account
3636
.overrideConfigKey("quarkus.hibernate-orm.\"overrides\".flush.mode", "always")
3737
.overrideConfigKey("quarkus.hibernate-orm.\"overrides\".schema-management.extra-physical-table-types",
38-
"MATERIALIZED VIEW,FOREIGN TABLE");
38+
"MATERIALIZED VIEW,FOREIGN TABLE")
39+
.overrideConfigKey("quarkus.hibernate-orm.\"overrides\".mapping.duration.preferred-jdbc-type", "INTERVAL_SECOND")
40+
.overrideConfigKey("quarkus.hibernate-orm.\"overrides\".mapping.instant.preferred-jdbc-type", "INSTANT")
41+
.overrideConfigKey("quarkus.hibernate-orm.\"overrides\".mapping.boolean.preferred-jdbc-type", "BIT")
42+
.overrideConfigKey("quarkus.hibernate-orm.\"overrides\".mapping.uuid.preferred-jdbc-type", "CHAR");
3943

4044
@Inject
4145
Session sessionForDefaultPU;
@@ -67,4 +71,37 @@ public void extraPhysicalTableTypes() {
6771
assertThat(tableTypes).containsExactly("MATERIALIZED VIEW", "FOREIGN TABLE");
6872
}
6973

74+
@Test
75+
@Transactional
76+
void shouldMapHibernateOrmConfigPersistenceUnitMappingDurationProperties() {
77+
// given
78+
var preferredJdbcType = sessionForOverridesPU.getSessionFactory()
79+
.getProperties()
80+
.get(AvailableSettings.PREFERRED_DURATION_JDBC_TYPE);
81+
82+
// when - then
83+
assertThat(preferredJdbcType).isEqualTo("INTERVAL_SECOND");
84+
}
85+
86+
@Test
87+
@Transactional
88+
void shouldMapHibernateOrmConfigPersistenceUnitMappingPreferredTypesProperties() {
89+
// given
90+
var instantPreferredJdbcType = sessionForOverridesPU.getSessionFactory()
91+
.getProperties()
92+
.get(AvailableSettings.PREFERRED_INSTANT_JDBC_TYPE);
93+
94+
var booleanPreferredJdbcType = sessionForOverridesPU.getSessionFactory()
95+
.getProperties()
96+
.get(AvailableSettings.PREFERRED_BOOLEAN_JDBC_TYPE);
97+
98+
var UUIDPreferredJdbcType = sessionForOverridesPU.getSessionFactory()
99+
.getProperties()
100+
.get(AvailableSettings.PREFERRED_UUID_JDBC_TYPE);
101+
102+
// when - then
103+
assertThat(instantPreferredJdbcType).isEqualTo("INSTANT");
104+
assertThat(booleanPreferredJdbcType).isEqualTo("BIT");
105+
assertThat(UUIDPreferredJdbcType).isEqualTo("CHAR");
106+
}
70107
}

extensions/hibernate-orm/deployment/src/test/resources/application.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ quarkus.datasource.db-kind=h2
22
quarkus.datasource.jdbc.url=jdbc:h2:mem:test
33

44
#quarkus.hibernate-orm.log.sql=true
5-
quarkus.hibernate-orm.schema-management.strategy=drop-and-create
5+
quarkus.hibernate-orm.schema-management.strategy=drop-and-create
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package io.quarkus.it.jpa.preferredhibernatetypesoverride;
2+
3+
import java.time.Duration;
4+
import java.time.Instant;
5+
import java.util.UUID;
6+
7+
import jakarta.persistence.Entity;
8+
import jakarta.persistence.GeneratedValue;
9+
import jakarta.persistence.Id;
10+
11+
@Entity(name = EntityWithOverridablePreferredTypes.NAME)
12+
public class EntityWithOverridablePreferredTypes {
13+
public static final String NAME = "ent_with_preferred_types";
14+
15+
@Id
16+
@GeneratedValue
17+
public UUID id;
18+
19+
public Instant createdAt;
20+
21+
public boolean isPersisted;
22+
23+
public Duration overridenDuration;
24+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package io.quarkus.it.jpa.preferredhibernatetypesoverride;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import java.time.Duration;
6+
import java.time.Instant;
7+
import java.util.List;
8+
import java.util.UUID;
9+
10+
import jakarta.enterprise.context.ApplicationScoped;
11+
import jakarta.inject.Inject;
12+
import jakarta.transaction.Transactional;
13+
import jakarta.ws.rs.GET;
14+
import jakarta.ws.rs.Path;
15+
import jakarta.ws.rs.Produces;
16+
import jakarta.ws.rs.core.MediaType;
17+
18+
import org.hibernate.Session;
19+
import org.hibernate.engine.spi.SessionFactoryImplementor;
20+
import org.hibernate.type.SqlTypes;
21+
import org.hibernate.type.StandardBasicTypes;
22+
23+
import io.quarkus.hibernate.orm.PersistenceUnit;
24+
25+
@Path("/overridden-preferred-types")
26+
@ApplicationScoped
27+
public class OverriddenPreferredTypesResource {
28+
29+
@Inject
30+
@PersistenceUnit("overridden-types")
31+
Session session;
32+
33+
@GET
34+
@Path("/test-successful-persistence")
35+
@Produces(MediaType.TEXT_PLAIN)
36+
@Transactional
37+
public String testSuccessfulPersistence() {
38+
var entity = persistEntity();
39+
40+
assertThat(findUsingNativeQuery()).contains(entity.id);
41+
42+
return "OK";
43+
}
44+
45+
@GET
46+
@Path("/test-successful-override")
47+
@Produces(MediaType.TEXT_PLAIN)
48+
@Transactional
49+
public String testSuccessfulPreferredTypesOverride() {
50+
persistEntity();
51+
52+
var metamodel = session.getFactory()
53+
.unwrap(SessionFactoryImplementor.class)
54+
.getMappingMetamodel()
55+
.findEntityDescriptor(EntityWithOverridablePreferredTypes.class);
56+
57+
assertThat(metamodel.getIdentifierMapping().getSingleJdbcMapping().getJdbcType()
58+
.getDefaultSqlTypeCode())
59+
.isEqualTo(SqlTypes.CHAR);
60+
assertThat(metamodel.getAttributeMapping(metamodel.getPropertyIndex("createdAt")).getSingleJdbcMapping().getJdbcType()
61+
.getDefaultSqlTypeCode())
62+
.isEqualTo(SqlTypes.INSTANT);
63+
64+
assertThat(metamodel.getAttributeMapping(metamodel.getPropertyIndex("overridenDuration")).getSingleJdbcMapping()
65+
.getJdbcType()
66+
.getDefaultSqlTypeCode())
67+
.isEqualTo(SqlTypes.INTERVAL_SECOND);
68+
69+
// Cannot detect BIT from the metamodel because it's handled as Boolean at runtime
70+
// See https://github.com/hibernate/hibernate-orm/blob/018b8eeda3627e114ec25bd48407ccb9c47564ce/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/BooleanJdbcType.java#L61-L66
71+
assertThat(metamodel.getAttributeMapping(metamodel.getPropertyIndex("isPersisted")).getSingleJdbcMapping().getJdbcType()
72+
.getDefaultSqlTypeCode())
73+
.isEqualTo(SqlTypes.BOOLEAN);
74+
75+
return "OK";
76+
}
77+
78+
private EntityWithOverridablePreferredTypes persistEntity() {
79+
var entity = new EntityWithOverridablePreferredTypes();
80+
entity.isPersisted = true;
81+
entity.createdAt = Instant.now();
82+
entity.overridenDuration = Duration.ofDays(1);
83+
84+
session.persist(entity);
85+
session.flush();
86+
session.clear();
87+
88+
return entity;
89+
}
90+
91+
private List<UUID> findUsingNativeQuery() {
92+
return session.createNativeQuery(
93+
"""
94+
SELECT id FROM %s WHERE isPersisted = :isPersisted
95+
""".formatted(EntityWithOverridablePreferredTypes.NAME))
96+
.addScalar("id", StandardBasicTypes.UUID_CHAR)
97+
.setParameter("isPersisted", true)
98+
.getResultList();
99+
}
100+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
quarkus.datasource.db-kind=h2
22
quarkus.datasource.jdbc.url=jdbc:h2:tcp://localhost/mem:test
3+
quarkus.hibernate-orm.packages=io.quarkus.it.jpa
34

45
quarkus.hibernate-orm.schema-management.strategy=drop-and-create
56

67
quarkus.hibernate-orm.metadata-builder-contributor=io.quarkus.it.jpa.defaultcatalogandschema.Schema1MetadataBuilderContributor
78

89
quarkus.hibernate-orm.multitenant=DISCRIMINATOR
910

11+
quarkus.datasource."overridden-types".db-kind=h2
12+
quarkus.hibernate-orm."overridden-types".datasource=overridden-types
13+
quarkus.hibernate-orm."overridden-types".packages=io.quarkus.it.jpa.preferredhibernatetypesoverride
14+
quarkus.hibernate-orm."overridden-types".mapping.duration.preferred-jdbc-type=INTERVAL_SECOND
15+
quarkus.hibernate-orm."overridden-types".mapping.instant.preferred-jdbc-type=INSTANT
16+
quarkus.hibernate-orm."overridden-types".mapping.boolean.preferred-jdbc-type=BIT
17+
quarkus.hibernate-orm."overridden-types".mapping.uuid.preferred-jdbc-type=CHAR
18+
1019
dummy.transaction.timeout=30

integration-tests/jpa/src/test/java/io/quarkus/it/jpa/integrator/JPAIntegratorTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class JPAIntegratorTest {
1414
public void testInjection() {
1515
when().get("/jpa-test/integrator").then()
1616
.statusCode(200)
17-
.body(is("1"));
17+
.body(is("2")); // Once per persistence unit
1818
}
1919

2020
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package io.quarkus.it.jpa.preferredhibernatetypesoverride;
2+
3+
import io.quarkus.test.junit.QuarkusIntegrationTest;
4+
5+
@QuarkusIntegrationTest
6+
public class OverriddenPreferredTypesInGraalITCase extends OverriddenPreferredTypesTest {
7+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.quarkus.it.jpa.preferredhibernatetypesoverride;
2+
3+
import static io.restassured.RestAssured.given;
4+
import static org.hamcrest.core.Is.is;
5+
6+
import org.junit.jupiter.api.Test;
7+
8+
import io.quarkus.test.junit.QuarkusTest;
9+
10+
@QuarkusTest
11+
class OverriddenPreferredTypesTest {
12+
13+
@Test
14+
void shouldSaveEntityWithOverriddenTypes() {
15+
given().when().get("/jpa-test/overridden-preferred-types/test-successful-persistence").then()
16+
.body(is("OK"))
17+
.statusCode(200);
18+
}
19+
20+
@Test
21+
void shouldOverrideTypes() {
22+
given().when().get("/jpa-test/overridden-preferred-types/test-successful-override").then()
23+
.body(is("OK"))
24+
.statusCode(200);
25+
}
26+
}

0 commit comments

Comments
 (0)