@@ -560,9 +560,9 @@ See <<multitenancy>>.
560560`io.quarkus.hibernate.orm.runtime.tenant.TenantConnectionResolver`::
561561See <<programmatically-resolving-tenants-connections>>.
562562`org.hibernate.boot.model.FunctionContributor`::
563- Contribute custom SQL functions.
563+ See << custom- functions-and-types>> .
564564`org.hibernate.boot.model.TypeContributor`::
565- Contribute custom types.
565+ See << custom-functions-and- types>> .
566566
567567[[persistence-unit-active]]
568568=== Activate/deactivate persistence units
@@ -1578,6 +1578,149 @@ public class ExampleTenantConnectionResolver implements TenantConnectionResolver
15781578}
15791579----
15801580
1581+ [[custom-functions-and-types]]
1582+ === Custom functions, types and mappings
1583+
1584+ To register custom SQL functions or types, in Hibernate ORM, you can implement the standard Hibernate interfaces:
1585+
1586+ * `org.hibernate.boot.model.FunctionContributor`
1587+ * `org.hibernate.boot.model.TypeContributor`
1588+
1589+ Creating an application-scoped bean that implements one of these interfaces and annotating it with `@PersistenceUnitExtension`
1590+ (or `@PersistenceUnitExtension("nameOfYourPU")` for a <<multiple-persistence-units,named persistence unit>>)
1591+ will automatically register it with the corresponding persistence unit.
1592+
1593+ Here is an example of a custom function contributor:
1594+
1595+ [source,java]
1596+ ----
1597+ import java.util.List;
1598+ import org.hibernate.boot.model.FunctionContributions;
1599+ import org.hibernate.boot.model.FunctionContributor;
1600+ import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
1601+ import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
1602+ import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers;
1603+ import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
1604+ import org.hibernate.sql.ast.SqlAstTranslator;
1605+ import org.hibernate.sql.ast.spi.SqlAppender;
1606+ import org.hibernate.sql.ast.tree.SqlAstNode;
1607+ import org.hibernate.sql.ast.tree.expression.ReturnableType;
1608+ import org.hibernate.sql.ast.tree.expression.SqlAstNodeRenderingMode;
1609+ import org.hibernate.type.StandardBasicTypes;
1610+ import org.hibernate.type.spi.TypeConfiguration;
1611+ import io.quarkus.hibernate.orm.PersistenceUnitExtension;
1612+ import jakarta.enterprise.context.ApplicationScoped;
1613+
1614+ @ApplicationScoped
1615+ @PersistenceUnitExtension
1616+ public class CustomFunctionContributor implements FunctionContributor {
1617+
1618+ @Override
1619+ public void contributeFunctions(FunctionContributions functionContributions) {
1620+ functionContributions.getFunctionRegistry().register(
1621+ "addHardcodedSuffix",
1622+ new HardcodedSuffixFunction(
1623+ functionContributions.getTypeConfiguration(), "_some_suffix"));
1624+ }
1625+
1626+ private static final class HardcodedSuffixFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
1627+ private final String suffix;
1628+
1629+ private HardcodedSuffixFunction(TypeConfiguration typeConfiguration, String suffix) {
1630+ super("addHardcodedSuffix",
1631+ StandardArgumentsValidators.exactly(1),
1632+ StandardFunctionReturnTypeResolvers.invariant(
1633+ typeConfiguration.getBasicTypeRegistry().resolve(StandardBasicTypes.STRING)),
1634+ StandardFunctionArgumentTypeResolvers.impliedOrInvariant(typeConfiguration, StandardBasicTypes.STRING)
1635+ );
1636+ this.suffix = suffix;
1637+ }
1638+
1639+ @Override
1640+ public void render(SqlAppender sqlAppender, List<? extends SqlAstNode> sqlAstArguments, ReturnableType<?> returnType,
1641+ SqlAstTranslator<?> walker) {
1642+ sqlAppender.appendSql('(');
1643+ walker.render(sqlAstArguments.get(0), SqlAstNodeRenderingMode.DEFAULT);
1644+ sqlAppender.appendSql(" || '" + suffix + "')");
1645+ }
1646+ }
1647+ }
1648+
1649+ ----
1650+
1651+ And here is an example of a custom type contributor that registers a custom `UserType` (e.g. mapping a Boolean to "Y"/"N"):
1652+
1653+ [source,java]
1654+ ----
1655+ import java.sql.PreparedStatement;
1656+ import java.sql.ResultSet;
1657+ import java.sql.SQLException;
1658+ import org.hibernate.boot.model.TypeContributions;
1659+ import org.hibernate.boot.model.TypeContributor;
1660+ import org.hibernate.engine.spi.SharedSessionContractImplementor;
1661+ import org.hibernate.service.ServiceRegistry;
1662+ import org.hibernate.type.SqlTypes;
1663+ import org.hibernate.type.descriptor.WrapperOptions;
1664+ import org.hibernate.usertype.UserType;
1665+ import io.quarkus.hibernate.orm.PersistenceUnitExtension;
1666+ import jakarta.enterprise.context.ApplicationScoped;
1667+
1668+ @ApplicationScoped
1669+ @PersistenceUnitExtension
1670+ public class CustomTypeContributor implements TypeContributor {
1671+
1672+ @Override
1673+ public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
1674+ // Registers the custom type so it can be used via @Type(value = BooleanYesNoType.class) on your entity property
1675+ typeContributions.getTypeConfiguration()
1676+ .getBasicTypeRegistry()
1677+ .register(new BooleanYesNoType(), "boolean_yes_no");
1678+ }
1679+
1680+ public static final class BooleanYesNoType implements UserType<Boolean> {
1681+ @Override
1682+ public int getSqlType() {
1683+ return SqlTypes.VARCHAR;
1684+ }
1685+
1686+ @Override
1687+ public Class<Boolean> returnedClass() {
1688+ return Boolean.class;
1689+ }
1690+
1691+ @Override
1692+ public Boolean nullSafeGet(ResultSet rs, int position, WrapperOptions options) throws SQLException {
1693+ String value = rs.getString(position);
1694+
1695+ if (value == null) {
1696+ return null;
1697+ }
1698+
1699+ return "Y".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value);
1700+ }
1701+
1702+ @Override
1703+ public void nullSafeSet(PreparedStatement st, Boolean value, int position, WrapperOptions options) throws SQLException {
1704+ if (value == null) {
1705+ st.setNull(position, SqlTypes.VARCHAR);
1706+ } else {
1707+ st.setString(position, value ? "Y" : "N");
1708+ }
1709+ }
1710+
1711+ @Override
1712+ public Boolean deepCopy(Boolean value) {
1713+ return value;
1714+ }
1715+
1716+ @Override
1717+ public boolean isMutable() {
1718+ return false;
1719+ }
1720+ }
1721+ }
1722+ ----
1723+
15811724[[interceptors]]
15821725== Interceptors
15831726
0 commit comments