diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java index 7462c4b1308c9..e0c61b871bb98 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java @@ -340,6 +340,12 @@ public boolean isAnyPropertySet() { */ @ConfigGroup public static class HibernateOrmConfigPersistenceUnitMapping { + /** + * JSON mapping configuration. + */ + @ConfigItem + public Json json; + /** * Timezone configuration. */ @@ -352,6 +358,18 @@ public static class HibernateOrmConfigPersistenceUnitMapping { @ConfigItem public Id id; + @ConfigGroup + public static class Json { + /** + * Class name of a custom + * https://docs.jboss.org/hibernate/stable/orm/javadocs/org/hibernate/type/format/FormatMapper.html[`FormatMapper`] + * implementation. + * This mapper will be used for JSON serialization and deserialization. + */ + @ConfigItem + public Optional mapperFormatter; + } + @ConfigGroup public static class Timezone { /** diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java index ed82729ff903b..faf072d819d68 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java @@ -295,6 +295,7 @@ public void configurationDescriptorBuilding( Capabilities capabilities, BuildProducer systemProperties, BuildProducer nativeImageResources, + BuildProducer reflectiveClasses, BuildProducer hotDeploymentWatchedFiles, BuildProducer persistenceUnitDescriptors, List dbKindMetadataBuildItems) { @@ -325,14 +326,14 @@ public void configurationDescriptorBuilding( hibernateOrmConfig.database.ormCompatibilityVersion, Collections.emptyMap()), null, jpaModel.getXmlMappings(persistenceXmlDescriptorBuildItem.getDescriptor().getName()), - false, true)); + false, true, capabilities.isPresent(Capability.JACKSON))); } if (impliedPU.shouldGenerateImpliedBlockingPersistenceUnit()) { handleHibernateORMWithNoPersistenceXml(hibernateOrmConfig, index, persistenceXmlDescriptors, jdbcDataSources, applicationArchivesBuildItem, launchMode.getLaunchMode(), jpaModel, capabilities, - systemProperties, nativeImageResources, hotDeploymentWatchedFiles, persistenceUnitDescriptors, - dbKindMetadataBuildItems); + systemProperties, nativeImageResources, reflectiveClasses, hotDeploymentWatchedFiles, + persistenceUnitDescriptors, dbKindMetadataBuildItems); } } @@ -809,6 +810,7 @@ private void handleHibernateORMWithNoPersistenceXml( Capabilities capabilities, BuildProducer systemProperties, BuildProducer nativeImageResources, + BuildProducer reflectiveClasses, BuildProducer hotDeploymentWatchedFiles, BuildProducer persistenceUnitDescriptors, List dbKindMetadataBuildItems) { @@ -858,8 +860,8 @@ private void handleHibernateORMWithNoPersistenceXml( modelClassesAndPackagesForDefaultPersistenceUnit, jpaModel.getXmlMappings(PersistenceUnitUtil.DEFAULT_PERSISTENCE_UNIT_NAME), jdbcDataSources, applicationArchivesBuildItem, launchMode, capabilities, - systemProperties, nativeImageResources, hotDeploymentWatchedFiles, persistenceUnitDescriptors, - storageEngineCollector, dbKindMetadataBuildItems); + systemProperties, nativeImageResources, reflectiveClasses, hotDeploymentWatchedFiles, + persistenceUnitDescriptors, storageEngineCollector, dbKindMetadataBuildItems); } else if (!modelClassesAndPackagesForDefaultPersistenceUnit.isEmpty() && (!hibernateOrmConfig.defaultPersistenceUnit.datasource.isPresent() || DataSourceUtil.isDefault(hibernateOrmConfig.defaultPersistenceUnit.datasource.get())) @@ -881,8 +883,8 @@ private void handleHibernateORMWithNoPersistenceXml( Collections.emptySet()), jpaModel.getXmlMappings(persistenceUnitEntry.getKey()), jdbcDataSources, applicationArchivesBuildItem, launchMode, capabilities, - systemProperties, nativeImageResources, hotDeploymentWatchedFiles, persistenceUnitDescriptors, - storageEngineCollector, dbKindMetadataBuildItems); + systemProperties, nativeImageResources, reflectiveClasses, hotDeploymentWatchedFiles, + persistenceUnitDescriptors, storageEngineCollector, dbKindMetadataBuildItems); } if (storageEngineCollector.size() > 1) { @@ -903,6 +905,7 @@ private static void producePersistenceUnitDescriptorFromConfig( Capabilities capabilities, BuildProducer systemProperties, BuildProducer nativeImageResources, + BuildProducer reflectiveClasses, BuildProducer hotDeploymentWatchedFiles, BuildProducer persistenceUnitDescriptors, Set storageEngineCollector, @@ -960,7 +963,11 @@ private static void producePersistenceUnitDescriptorFromConfig( descriptor.getProperties().setProperty(AvailableSettings.PREFERRED_POOLED_OPTIMIZER, persistenceUnitConfig.mapping.id.optimizer.idOptimizerDefault .orElse(HibernateOrmConfigPersistenceUnit.IdOptimizerType.POOLED_LO).configName); - + if (persistenceUnitConfig.mapping.json.mapperFormatter.isPresent()) { + String mapper = persistenceUnitConfig.mapping.json.mapperFormatter.get(); + reflectiveClasses.produce(ReflectiveClassBuildItem.builder(mapper).build()); + descriptor.getProperties().setProperty(AvailableSettings.JSON_FORMAT_MAPPER, mapper); + } //charset descriptor.getProperties().setProperty(AvailableSettings.HBM2DDL_CHARSET_NAME, persistenceUnitConfig.database.charset.name()); @@ -1117,7 +1124,7 @@ private static void producePersistenceUnitDescriptorFromConfig( persistenceUnitConfig.unsupportedProperties), persistenceUnitConfig.multitenantSchemaDatasource.orElse(null), xmlMappings, - false, false)); + false, false, capabilities.isPresent(Capability.JACKSON))); } private static void collectDialectConfig(String persistenceUnitName, diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/PersistenceUnitDescriptorBuildItem.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/PersistenceUnitDescriptorBuildItem.java index 9400a67468d6c..9d6ed1ea88239 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/PersistenceUnitDescriptorBuildItem.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/PersistenceUnitDescriptorBuildItem.java @@ -30,12 +30,13 @@ public final class PersistenceUnitDescriptorBuildItem extends MultiBuildItem { private final List xmlMappings; private final boolean isReactive; private final boolean fromPersistenceXml; + private final boolean jacksonPresent; public PersistenceUnitDescriptorBuildItem(ParsedPersistenceXmlDescriptor descriptor, String configurationName, RecordedConfig config, String multiTenancySchemaDataSource, List xmlMappings, - boolean isReactive, boolean fromPersistenceXml) { + boolean isReactive, boolean fromPersistenceXml, boolean jacksonPresent) { this.descriptor = descriptor; this.configurationName = configurationName; this.config = config; @@ -43,6 +44,7 @@ public PersistenceUnitDescriptorBuildItem(ParsedPersistenceXmlDescriptor descrip this.xmlMappings = xmlMappings; this.isReactive = isReactive; this.fromPersistenceXml = fromPersistenceXml; + this.jacksonPresent = jacksonPresent; } public Collection getManagedClassNames() { @@ -80,6 +82,6 @@ public boolean isFromPersistenceXml() { public QuarkusPersistenceUnitDefinition asOutputPersistenceUnitDefinition( List integrationStaticDescriptors) { return new QuarkusPersistenceUnitDefinition(descriptor, configurationName, config, - xmlMappings, isReactive, fromPersistenceXml, integrationStaticDescriptors); + xmlMappings, isReactive, fromPersistenceXml, jacksonPresent, integrationStaticDescriptors); } } diff --git a/extensions/hibernate-orm/runtime/pom.xml b/extensions/hibernate-orm/runtime/pom.xml index 264d3f851db53..f5489d67da837 100644 --- a/extensions/hibernate-orm/runtime/pom.xml +++ b/extensions/hibernate-orm/runtime/pom.xml @@ -143,6 +143,11 @@ io.quarkus quarkus-caffeine + + io.quarkus + quarkus-jackson + true + diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java index 3eabdd8e7bc79..08d003a3c6bb7 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java @@ -72,6 +72,7 @@ import io.quarkus.hibernate.orm.runtime.BuildTimeSettings; import io.quarkus.hibernate.orm.runtime.IntegrationSettings; import io.quarkus.hibernate.orm.runtime.boot.xml.RecordableXmlMapping; +import io.quarkus.hibernate.orm.runtime.customized.QuarkusJsonFormatMapper; import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationStaticDescriptor; import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationStaticInitListener; import io.quarkus.hibernate.orm.runtime.migration.MultiTenancyStrategy; @@ -399,6 +400,12 @@ private MergedSettings mergeSettings(QuarkusPersistenceUnitDefinition puDefiniti } } + // If there's no user provided mapper but we have Jackson available, + // we want to add a "default" Quarkus mapper that delegates to the Quarkus-configured ObjectMapper: + if (!cfg.containsKey(AvailableSettings.JSON_FORMAT_MAPPER) && puDefinition.isJacksonPresent()) { + cfg.put(AvailableSettings.JSON_FORMAT_MAPPER, new QuarkusJsonFormatMapper()); + } + return mergedSettings; } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/QuarkusPersistenceUnitDefinition.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/QuarkusPersistenceUnitDefinition.java index a59c32123a15b..ac0918fce40da 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/QuarkusPersistenceUnitDefinition.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/QuarkusPersistenceUnitDefinition.java @@ -21,12 +21,13 @@ public final class QuarkusPersistenceUnitDefinition { private final List xmlMappings; private final boolean isReactive; private final boolean fromPersistenceXml; + private final boolean jacksonPresent; private final List integrationStaticDescriptors; public QuarkusPersistenceUnitDefinition(PersistenceUnitDescriptor persistenceUnitDescriptor, String configurationName, RecordedConfig config, List xmlMappings, - boolean isReactive, boolean fromPersistenceXml, + boolean isReactive, boolean fromPersistenceXml, boolean jacksonPresent, List integrationStaticDescriptors) { Objects.requireNonNull(persistenceUnitDescriptor); Objects.requireNonNull(config); @@ -36,6 +37,7 @@ public QuarkusPersistenceUnitDefinition(PersistenceUnitDescriptor persistenceUni this.xmlMappings = xmlMappings; this.isReactive = isReactive; this.fromPersistenceXml = fromPersistenceXml; + this.jacksonPresent = jacksonPresent; this.integrationStaticDescriptors = integrationStaticDescriptors; } @@ -45,6 +47,7 @@ public QuarkusPersistenceUnitDefinition(RuntimePersistenceUnitDescriptor actualH List xmlMappings, boolean reactive, boolean fromPersistenceXml, + boolean jacksonPresent, List integrationStaticDescriptors) { Objects.requireNonNull(actualHibernateDescriptor); Objects.requireNonNull(config); @@ -53,6 +56,7 @@ public QuarkusPersistenceUnitDefinition(RuntimePersistenceUnitDescriptor actualH this.xmlMappings = xmlMappings; this.isReactive = reactive; this.fromPersistenceXml = fromPersistenceXml; + this.jacksonPresent = jacksonPresent; this.integrationStaticDescriptors = integrationStaticDescriptors; } @@ -81,6 +85,10 @@ public boolean isFromPersistenceXml() { return fromPersistenceXml; } + public boolean isJacksonPresent() { + return jacksonPresent; + } + public List getIntegrationStaticDescriptors() { return integrationStaticDescriptors; } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/customized/QuarkusJsonFormatMapper.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/customized/QuarkusJsonFormatMapper.java new file mode 100644 index 0000000000000..860c22451c0a3 --- /dev/null +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/customized/QuarkusJsonFormatMapper.java @@ -0,0 +1,22 @@ +package io.quarkus.hibernate.orm.runtime.customized; + +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.format.FormatMapper; +import org.hibernate.type.format.jackson.JacksonJsonFormatMapper; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.quarkus.arc.Arc; + +public class QuarkusJsonFormatMapper implements FormatMapper { + private final FormatMapper delegate = new JacksonJsonFormatMapper(Arc.container().instance(ObjectMapper.class).get()); + + public T fromString(CharSequence charSequence, JavaType javaType, WrapperOptions wrapperOptions) { + return delegate.fromString(charSequence, javaType, wrapperOptions); + } + + public String toString(T value, JavaType javaType, WrapperOptions wrapperOptions) { + return delegate.toString(value, javaType, wrapperOptions); + } +} diff --git a/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java b/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java index 72f7ce71a52ac..bed87c2e0c6d5 100644 --- a/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java +++ b/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java @@ -25,6 +25,8 @@ import java.util.Set; import java.util.stream.Collectors; +import io.quarkus.deployment.Capabilities; +import io.quarkus.deployment.Capability; import jakarta.persistence.SharedCacheMode; import jakarta.persistence.spi.PersistenceUnitTransactionType; @@ -130,6 +132,7 @@ public void buildReactivePersistenceUnit( ApplicationArchivesBuildItem applicationArchivesBuildItem, LaunchModeBuildItem launchMode, JpaModelBuildItem jpaModel, + Capabilities capabilities, BuildProducer systemProperties, BuildProducer nativeImageResources, BuildProducer hotDeploymentWatchedFiles, @@ -187,7 +190,7 @@ public void buildReactivePersistenceUnit( persistenceUnitConfig.unsupportedProperties), null, jpaModel.getXmlMappings(reactivePU.getName()), - true, false)); + true, false, capabilities.isPresent(Capability.JACKSON))); } } diff --git a/integration-tests/jpa-postgresql/pom.xml b/integration-tests/jpa-postgresql/pom.xml index 980645fc1e6b5..15ce75578bbb9 100644 --- a/integration-tests/jpa-postgresql/pom.xml +++ b/integration-tests/jpa-postgresql/pom.xml @@ -26,6 +26,10 @@ io.quarkus quarkus-jdbc-postgresql + + io.quarkus + quarkus-jackson + @@ -89,6 +93,19 @@ + + io.quarkus + quarkus-jackson-deployment + ${project.version} + pom + test + + + * + * + + + diff --git a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/EntityWithJson.java b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/EntityWithJson.java new file mode 100644 index 0000000000000..c224b7b5777a5 --- /dev/null +++ b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/EntityWithJson.java @@ -0,0 +1,59 @@ +package io.quarkus.it.jpa.postgresql; + +import java.time.LocalDate; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.quarkus.runtime.annotations.RegisterForReflection; + +@Entity +public class EntityWithJson { + @Id + @GeneratedValue + Long id; + + @JdbcTypeCode(SqlTypes.JSON) + ToBeJsonWithDateTime json; + + public EntityWithJson() { + } + + public EntityWithJson(ToBeJsonWithDateTime json) { + this.json = json; + } + + @Override + public String toString() { + return "EntityWithJson{" + + "id=" + id + + ", json=" + json + + '}'; + } + + @RegisterForReflection + public static class ToBeJsonWithDateTime { + @JsonProperty + LocalDate date; + + public ToBeJsonWithDateTime() { + } + + public ToBeJsonWithDateTime(LocalDate date) { + this.date = date; + } + + @Override + public String toString() { + return "ToBeJsonWithDateTime{" + + "date=" + date + + '}'; + } + } +} diff --git a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java index 0bc9b7d02b6e7..4ac750a1e6001 100644 --- a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java +++ b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.time.Duration; +import java.time.LocalDate; import java.util.List; import java.util.UUID; @@ -10,6 +11,7 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManagerFactory; import jakarta.persistence.EntityTransaction; +import jakarta.persistence.PersistenceUnit; import jakarta.persistence.TypedQuery; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaQuery; @@ -19,6 +21,8 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import io.quarkus.it.jpa.postgresql.otherpu.EntityWithJsonOtherPU; + /** * Various tests covering JPA functionality. All tests should work in both standard JVM and in native mode. */ @@ -27,11 +31,14 @@ public class JPAFunctionalityTestEndpoint extends HttpServlet { @Inject EntityManagerFactory entityManagerFactory; + @Inject + @PersistenceUnit(unitName = "other") + EntityManagerFactory otherEntityManagerFactory; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { try { - doStuffWithHibernate(entityManagerFactory); + doStuffWithHibernate(entityManagerFactory, otherEntityManagerFactory); } catch (Exception e) { reportException("An error occurred while performing Hibernate operations", e, resp); } @@ -41,7 +48,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO /** * Lists the various operations we want to test for: */ - private static void doStuffWithHibernate(EntityManagerFactory entityManagerFactory) { + private static void doStuffWithHibernate(EntityManagerFactory entityManagerFactory, + EntityManagerFactory otherEntityManagerFactory) { //Cleanup any existing data: deleteAllPerson(entityManagerFactory); @@ -57,6 +65,8 @@ private static void doStuffWithHibernate(EntityManagerFactory entityManagerFacto deleteAllPerson(entityManagerFactory); + doJsonStuff(entityManagerFactory, otherEntityManagerFactory); + } private static void verifyJPANamedQuery(final EntityManagerFactory emf) { @@ -144,6 +154,42 @@ private static String randomName() { return UUID.randomUUID().toString(); } + private static void doJsonStuff(EntityManagerFactory emf, EntityManagerFactory otherEmf) { + try (EntityManager em = emf.createEntityManager()) { + EntityTransaction transaction = em.getTransaction(); + transaction.begin(); + + EntityWithJson entity = new EntityWithJson(new EntityWithJson.ToBeJsonWithDateTime(LocalDate.of(2023, 7, 28))); + em.persist(entity); + transaction.commit(); + + transaction.begin(); + List entities = em.createQuery("select e from EntityWithJson e", EntityWithJson.class) + .getResultList(); + if (entities.isEmpty()) { + throw new AssertionError("No entities with json were found"); + } + transaction.commit(); + + transaction.begin(); + em.createQuery("delete from EntityWithJson").executeUpdate(); + transaction.commit(); + } + + try (EntityManager em = otherEmf.createEntityManager()) { + EntityTransaction transaction = em.getTransaction(); + transaction.begin(); + EntityWithJsonOtherPU otherPU = new EntityWithJsonOtherPU( + new EntityWithJsonOtherPU.ToBeJsonWithDateTime(LocalDate.of(2023, 7, 28))); + em.persist(otherPU); + transaction.commit(); + throw new AssertionError( + "Default mapper cannot process date/time properties. So we were expecting commit to fail, but it did not!"); + } catch (Exception e) { + // just ignore + } + } + private void reportException(String errorMessage, final Exception e, final HttpServletResponse resp) throws IOException { final PrintWriter writer = resp.getWriter(); if (errorMessage != null) { diff --git a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/otherpu/EntityWithJsonOtherPU.java b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/otherpu/EntityWithJsonOtherPU.java new file mode 100644 index 0000000000000..100e0648b3e38 --- /dev/null +++ b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/otherpu/EntityWithJsonOtherPU.java @@ -0,0 +1,60 @@ + +package io.quarkus.it.jpa.postgresql.otherpu; + +import java.time.LocalDate; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.quarkus.runtime.annotations.RegisterForReflection; + +@Entity +public class EntityWithJsonOtherPU { + @Id + @GeneratedValue + Long id; + + @JdbcTypeCode(SqlTypes.JSON) + ToBeJsonWithDateTime json; + + public EntityWithJsonOtherPU() { + } + + public EntityWithJsonOtherPU(ToBeJsonWithDateTime json) { + this.json = json; + } + + @Override + public String toString() { + return "EntityWithJson{" + + "id=" + id + + ", json=" + json + + '}'; + } + + @RegisterForReflection + public static class ToBeJsonWithDateTime { + @JsonProperty + LocalDate date; + + public ToBeJsonWithDateTime() { + } + + public ToBeJsonWithDateTime(LocalDate date) { + this.date = date; + } + + @Override + public String toString() { + return "ToBeJsonWithDateTime{" + + "date=" + date + + '}'; + } + } +} diff --git a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/otherpu/OtherPuFormatMapper.java b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/otherpu/OtherPuFormatMapper.java new file mode 100644 index 0000000000000..803c3cab960f6 --- /dev/null +++ b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/otherpu/OtherPuFormatMapper.java @@ -0,0 +1,23 @@ +package io.quarkus.it.jpa.postgresql.otherpu; + +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.format.FormatMapper; +import org.hibernate.type.format.jackson.JacksonJsonFormatMapper; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class OtherPuFormatMapper implements FormatMapper { + + private final FormatMapper delegate = new JacksonJsonFormatMapper(new ObjectMapper()); + + @Override + public T fromString(CharSequence charSequence, JavaType javaType, WrapperOptions wrapperOptions) { + return delegate.fromString(charSequence, javaType, wrapperOptions); + } + + @Override + public String toString(T value, JavaType javaType, WrapperOptions wrapperOptions) { + return delegate.toString(value, javaType, wrapperOptions); + } +} diff --git a/integration-tests/jpa-postgresql/src/main/resources/application.properties b/integration-tests/jpa-postgresql/src/main/resources/application.properties index c3f7971387997..eb42b80ad548e 100644 --- a/integration-tests/jpa-postgresql/src/main/resources/application.properties +++ b/integration-tests/jpa-postgresql/src/main/resources/application.properties @@ -3,8 +3,13 @@ quarkus.datasource.password=hibernate_orm_test quarkus.datasource.jdbc.url=${postgres.url} quarkus.datasource.jdbc.max-size=8 +quarkus.hibernate-orm.packages=io.quarkus.it.jpa.postgresql quarkus.hibernate-orm.database.generation=drop-and-create quarkus.hibernate-orm.database.generation.create-schemas=true +# Define non-default PU so that we can configure a custom JSON format mapper. The default PU is using the default mapper. +quarkus.hibernate-orm."other".datasource= +quarkus.hibernate-orm."other".packages=io.quarkus.it.jpa.postgresql.otherpu +quarkus.hibernate-orm."other".mapping.json.mapper-formatter=io.quarkus.it.jpa.postgresql.otherpu.OtherPuFormatMapper #Necessary for assertions in JPAFunctionalityInGraalITCase: quarkus.native.enable-reports=true \ No newline at end of file