diff --git a/docs/src/main/asciidoc/hibernate-orm.adoc b/docs/src/main/asciidoc/hibernate-orm.adoc index a86bfc06d6760..20eef819a4332 100644 --- a/docs/src/main/asciidoc/hibernate-orm.adoc +++ b/docs/src/main/asciidoc/hibernate-orm.adoc @@ -559,6 +559,10 @@ See <>. See <>. `io.quarkus.hibernate.orm.runtime.tenant.TenantConnectionResolver`:: See <>. +`org.hibernate.boot.model.FunctionContributor`:: +See <>. +`org.hibernate.boot.model.TypeContributor`:: +See <>. [[persistence-unit-active]] === Activate/deactivate persistence units @@ -1574,6 +1578,149 @@ public class ExampleTenantConnectionResolver implements TenantConnectionResolver } ---- +[[custom-functions-and-types]] +=== Custom functions, types and mappings + +To register custom SQL functions or types, in Hibernate ORM, you can implement the standard Hibernate interfaces: + +* `org.hibernate.boot.model.FunctionContributor` +* `org.hibernate.boot.model.TypeContributor` + +Creating an application-scoped bean that implements one of these interfaces and annotating it with `@PersistenceUnitExtension` +(or `@PersistenceUnitExtension("nameOfYourPU")` for a <>) +will automatically register it with the corresponding persistence unit. + +Here is an example of a custom function contributor: + +[source,java] +---- +import java.util.List; +import org.hibernate.boot.model.FunctionContributions; +import org.hibernate.boot.model.FunctionContributor; +import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; +import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; +import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers; +import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.ReturnableType; +import org.hibernate.sql.ast.tree.expression.SqlAstNodeRenderingMode; +import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.spi.TypeConfiguration; +import io.quarkus.hibernate.orm.PersistenceUnitExtension; +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +@PersistenceUnitExtension +public class CustomFunctionContributor implements FunctionContributor { + + @Override + public void contributeFunctions(FunctionContributions functionContributions) { + functionContributions.getFunctionRegistry().register( + "addHardcodedSuffix", + new HardcodedSuffixFunction( + functionContributions.getTypeConfiguration(), "_some_suffix")); + } + + private static final class HardcodedSuffixFunction extends AbstractSqmSelfRenderingFunctionDescriptor { + private final String suffix; + + private HardcodedSuffixFunction(TypeConfiguration typeConfiguration, String suffix) { + super("addHardcodedSuffix", + StandardArgumentsValidators.exactly(1), + StandardFunctionReturnTypeResolvers.invariant( + typeConfiguration.getBasicTypeRegistry().resolve(StandardBasicTypes.STRING)), + StandardFunctionArgumentTypeResolvers.impliedOrInvariant(typeConfiguration, StandardBasicTypes.STRING) + ); + this.suffix = suffix; + } + + @Override + public void render(SqlAppender sqlAppender, List sqlAstArguments, ReturnableType returnType, + SqlAstTranslator walker) { + sqlAppender.appendSql('('); + walker.render(sqlAstArguments.get(0), SqlAstNodeRenderingMode.DEFAULT); + sqlAppender.appendSql(" || '" + suffix + "')"); + } + } +} + +---- + +And here is an example of a custom type contributor that registers a custom `UserType` (e.g. mapping a Boolean to "Y"/"N"): + +[source,java] +---- +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import org.hibernate.boot.model.TypeContributions; +import org.hibernate.boot.model.TypeContributor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.service.ServiceRegistry; +import org.hibernate.type.SqlTypes; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.usertype.UserType; +import io.quarkus.hibernate.orm.PersistenceUnitExtension; +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +@PersistenceUnitExtension +public class CustomTypeContributor implements TypeContributor { + + @Override + public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { + // Registers the custom type so it can be used via @Type(value = BooleanYesNoType.class) on your entity property + typeContributions.getTypeConfiguration() + .getBasicTypeRegistry() + .register(new BooleanYesNoType(), "boolean_yes_no"); + } + + public static final class BooleanYesNoType implements UserType { + @Override + public int getSqlType() { + return SqlTypes.VARCHAR; + } + + @Override + public Class returnedClass() { + return Boolean.class; + } + + @Override + public Boolean nullSafeGet(ResultSet rs, int position, WrapperOptions options) throws SQLException { + String value = rs.getString(position); + + if (value == null) { + return null; + } + + return "Y".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value); + } + + @Override + public void nullSafeSet(PreparedStatement st, Boolean value, int position, WrapperOptions options) throws SQLException { + if (value == null) { + st.setNull(position, SqlTypes.VARCHAR); + } else { + st.setString(position, value ? "Y" : "N"); + } + } + + @Override + public Boolean deepCopy(Boolean value) { + return value; + } + + @Override + public boolean isMutable() { + return false; + } + } +} +---- + [[interceptors]] == Interceptors diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/ClassNames.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/ClassNames.java index 0e2d095c1481a..6f6fd34d971b7 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/ClassNames.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/ClassNames.java @@ -71,6 +71,8 @@ private static DotName createConstant(String fqcn) { public static final DotName FORMAT_MAPPER = createConstant("org.hibernate.type.format.FormatMapper"); public static final DotName JSON_FORMAT = createConstant("io.quarkus.hibernate.orm.JsonFormat"); public static final DotName XML_FORMAT = createConstant("io.quarkus.hibernate.orm.XmlFormat"); + public static final DotName FUNCTION_CONTRIBUTOR = createConstant("org.hibernate.boot.model.FunctionContributor"); + public static final DotName TYPE_CONTRIBUTOR = createConstant("org.hibernate.boot.model.TypeContributor"); public static final List GENERATORS = List.of( createConstant("org.hibernate.generator.Assigned"), diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java index 50b3cf55fe724..bed462675e9e9 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java @@ -92,7 +92,9 @@ public class HibernateOrmCdiProcessor { ClassNames.TENANT_CONNECTION_RESOLVER, ClassNames.INTERCEPTOR, ClassNames.STATEMENT_INSPECTOR, - ClassNames.FORMAT_MAPPER); + ClassNames.FORMAT_MAPPER, + ClassNames.FUNCTION_CONTRIBUTOR, + ClassNames.TYPE_CONTRIBUTOR); @BuildStep AnnotationsTransformerBuildItem convertJpaResourceAnnotationsToQualifier( 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 3dcf952e0105f..c442ad16f302b 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 @@ -135,9 +135,11 @@ public interface HibernateOrmConfigPersistenceUnit { * * This setting is exposed mainly to allow registration of types, converters and SQL functions. * ==== + * * @deprecated Use TypeContributor, FunctionContributor or AdditionalMappingContributor instead. * * @asciidoclet */ + @Deprecated Optional<@WithConverter(TrimmedStringConverter.class) String> metadataBuilderContributor(); /** diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/functioncontributors/CustomFunctionContributor.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/functioncontributors/CustomFunctionContributor.java new file mode 100644 index 0000000000000..764a301043d18 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/functioncontributors/CustomFunctionContributor.java @@ -0,0 +1,60 @@ +package io.quarkus.hibernate.orm.functioncontributors; + +import static org.hibernate.query.sqm.produce.function.FunctionParameterType.STRING; + +import java.util.List; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.hibernate.boot.model.FunctionContributions; +import org.hibernate.boot.model.FunctionContributor; +import org.hibernate.metamodel.model.domain.ReturnableType; +import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; +import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; +import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers; +import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; +import org.hibernate.sql.ast.SqlAstNodeRenderingMode; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.spi.TypeConfiguration; + +import io.quarkus.hibernate.orm.PersistenceUnitExtension; + +@ApplicationScoped +@PersistenceUnitExtension +public class CustomFunctionContributor implements FunctionContributor { + @Override + public void contributeFunctions(FunctionContributions functionContributions) { + functionContributions.getFunctionRegistry().register( + "addHardcodedSuffix", + new HardcodedSuffixFunction( + functionContributions.getTypeConfiguration(), "_some_suffix")); + } + + private static final class HardcodedSuffixFunction extends AbstractSqmSelfRenderingFunctionDescriptor { + private final String suffix; + + private HardcodedSuffixFunction(TypeConfiguration typeConfiguration, String suffix) { + super("addHardcodedSuffix", + StandardArgumentsValidators.exactly(1), + StandardFunctionReturnTypeResolvers.invariant( + typeConfiguration.getBasicTypeRegistry().resolve(StandardBasicTypes.STRING)), + StandardFunctionArgumentTypeResolvers.impliedOrInvariant(typeConfiguration, STRING)); + + this.suffix = suffix; + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + sqlAppender.appendSql('('); + walker.render(sqlAstArguments.get(0), SqlAstNodeRenderingMode.DEFAULT); + sqlAppender.appendSql(" || '" + suffix + "')"); + } + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/functioncontributors/FunctionContributorTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/functioncontributors/FunctionContributorTest.java new file mode 100644 index 0000000000000..a032defee2ae1 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/functioncontributors/FunctionContributorTest.java @@ -0,0 +1,35 @@ +package io.quarkus.hibernate.orm.functioncontributors; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.inject.Inject; +import jakarta.persistence.EntityManager; +import jakarta.transaction.Transactional; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class FunctionContributorTest { + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot(jar -> jar + .addClass(MyEntity.class) + .addClass(CustomFunctionContributor.class)); + + @Inject + EntityManager entityManager; + + @Test + @Transactional + public void test() { + MyEntity entity = new MyEntity(); + entity.setName("some_name"); + entityManager.persist(entity); + + assertThat(entityManager.createQuery("select addHardcodedSuffix(e.name) from MyEntity e", String.class) + .getSingleResult()) + .isEqualTo("some_name_some_suffix"); + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/functioncontributors/MyEntity.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/functioncontributors/MyEntity.java new file mode 100644 index 0000000000000..6e9ba69c6c314 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/functioncontributors/MyEntity.java @@ -0,0 +1,31 @@ +package io.quarkus.hibernate.orm.functioncontributors; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +@Entity +public class MyEntity { + @Id + private long id; + + private String name; + + public MyEntity() { + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/typecontributors/BooleanYesNoType.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/typecontributors/BooleanYesNoType.java new file mode 100644 index 0000000000000..7c461378b9830 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/typecontributors/BooleanYesNoType.java @@ -0,0 +1,52 @@ +package io.quarkus.hibernate.orm.typecontributors; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.hibernate.type.SqlTypes; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.usertype.UserType; + +final class BooleanYesNoType implements UserType { + + @Override + public int getSqlType() { + return SqlTypes.VARCHAR; + } + + @Override + public Class returnedClass() { + return Boolean.class; + } + + @Override + public Boolean nullSafeGet(ResultSet rs, int position, WrapperOptions options) throws SQLException { + String value = rs.getString(position); + + if (value == null) { + return null; + } + + return "Y".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value); + } + + @Override + public void nullSafeSet(PreparedStatement st, Boolean value, int position, WrapperOptions options) throws SQLException { + if (value == null) { + st.setNull(position, SqlTypes.VARCHAR); + } else { + st.setString(position, value ? "Y" : "N"); + } + } + + @Override + public Boolean deepCopy(Boolean value) { + return value; + } + + @Override + public boolean isMutable() { + return false; + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/typecontributors/CustomTypeContributor.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/typecontributors/CustomTypeContributor.java new file mode 100644 index 0000000000000..3db0626cda8d6 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/typecontributors/CustomTypeContributor.java @@ -0,0 +1,20 @@ +package io.quarkus.hibernate.orm.typecontributors; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.hibernate.boot.model.TypeContributions; +import org.hibernate.boot.model.TypeContributor; +import org.hibernate.service.ServiceRegistry; + +import io.quarkus.hibernate.orm.PersistenceUnitExtension; + +@ApplicationScoped +@PersistenceUnitExtension +public class CustomTypeContributor implements TypeContributor { + @Override + public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { + typeContributions.getTypeConfiguration() + .getBasicTypeRegistry() + .register(new BooleanYesNoType(), "boolean_yes_no"); + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/typecontributors/MyEntity.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/typecontributors/MyEntity.java new file mode 100644 index 0000000000000..3a73ff1c77ee1 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/typecontributors/MyEntity.java @@ -0,0 +1,27 @@ +package io.quarkus.hibernate.orm.typecontributors; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +import org.hibernate.annotations.Type; + +@Entity +public class MyEntity { + @Id + private long id; + + @Type(value = BooleanYesNoType.class) + public Boolean active; + + public MyEntity() { + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/typecontributors/TypeContributorTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/typecontributors/TypeContributorTest.java new file mode 100644 index 0000000000000..837a8bde9df00 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/typecontributors/TypeContributorTest.java @@ -0,0 +1,43 @@ +package io.quarkus.hibernate.orm.typecontributors; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.inject.Inject; +import jakarta.persistence.EntityManager; +import jakarta.transaction.Transactional; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class TypeContributorTest { + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot(jar -> jar + .addClass(MyEntity.class) + .addClass(BooleanYesNoType.class) + .addClass(CustomTypeContributor.class)); + + @Inject + EntityManager entityManager; + + @Test + @Transactional + public void testCustomType() { + MyEntity entity = new MyEntity(); + entity.active = true; + + entityManager.persist(entity); + entityManager.flush(); + entityManager.clear(); + + MyEntity savedEntity = entityManager.find(MyEntity.class, entity.getId()); + + assertThat(savedEntity.active).isTrue(); + + String savedValue = (String) entityManager.createNativeQuery("SELECT active FROM MyEntity WHERE id = " + entity.getId()) + .getSingleResult(); + assertThat(savedValue).isEqualTo("Y"); + } +} diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/PersistenceUnitUtil.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/PersistenceUnitUtil.java index 0a79c26d0082f..763fca561e1f9 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/PersistenceUnitUtil.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/PersistenceUnitUtil.java @@ -36,10 +36,11 @@ public static Annotation qualifier(String persistenceUnitName) { } } - public static InjectableInstance singleExtensionInstanceForPersistenceUnit(Class beanType, + public static InjectableInstance singleExtensionInstanceForPersistenceUnit( + Class beanType, String persistenceUnitName, Annotation... additionalQualifiers) { - InjectableInstance instance = extensionInstanceForPersistenceUnit(beanType, persistenceUnitName, + InjectableInstance instance = extensionInstancesForPersistenceUnit(beanType, persistenceUnitName, additionalQualifiers); if (instance.isAmbiguous()) { List ambiguousClassNames = instance.handlesStream().map(h -> h.getBean().getBeanClass().getCanonicalName()) @@ -52,7 +53,9 @@ public static InjectableInstance singleExtensionInstanceForPersistenceUni return instance; } - public static InjectableInstance extensionInstanceForPersistenceUnit(Class beanType, String persistenceUnitName, + public static InjectableInstance extensionInstancesForPersistenceUnit( + Class beanType, + String persistenceUnitName, Annotation... additionalQualifiers) { if (additionalQualifiers.length == 0) { return Arc.container().select(beanType, new PersistenceUnitExtension.Literal(persistenceUnitName)); 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 ea5c2a40032e1..5fbfc8b72622f 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 @@ -39,6 +39,8 @@ import org.hibernate.boot.archive.scan.spi.Scanner; import org.hibernate.boot.beanvalidation.BeanValidationIntegrator; import org.hibernate.boot.internal.MetadataImpl; +import org.hibernate.boot.model.FunctionContributor; +import org.hibernate.boot.model.TypeContributor; import org.hibernate.boot.model.process.spi.ManagedResources; import org.hibernate.boot.model.process.spi.MetadataBuildingProcess; import org.hibernate.boot.registry.StandardServiceRegistry; @@ -68,6 +70,7 @@ import io.quarkus.hibernate.orm.runtime.BuildTimeSettings; import io.quarkus.hibernate.orm.runtime.IntegrationSettings; +import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil; import io.quarkus.hibernate.orm.runtime.boot.xml.RecordableXmlMapping; import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationStaticDescriptor; import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationStaticInitListener; @@ -188,6 +191,9 @@ public FastBootMetadataBuilder(final QuarkusPersistenceUnitDefinition puDefiniti applyMetadataBuilderContributor(); + applyTypeContributors(); + applyFunctionContributors(); + // Unable to automatically handle: // AvailableSettings.ENHANCER_ENABLE_DIRTY_TRACKING, // AvailableSettings.ENHANCER_ENABLE_LAZY_INITIALIZATION, @@ -663,6 +669,16 @@ private void applyMetadataBuilderContributor() { } } + private void applyTypeContributors() { + PersistenceUnitUtil.extensionInstancesForPersistenceUnit(TypeContributor.class, persistenceUnit.getName()) + .stream().forEach(metamodelBuilder::applyTypes); + } + + private void applyFunctionContributors() { + PersistenceUnitUtil.extensionInstancesForPersistenceUnit(FunctionContributor.class, persistenceUnit.getName()) + .stream().forEach(metamodelBuilder::applyFunctions); + } + @SuppressWarnings("unchecked") private T loadSettingInstance(String settingName, Object settingValue, Class clazz) { T instance = null;