Migration of dialect to Hibernate 6

I am trying to migrate from Hibernate 5 to 6 and I am a bit stuck with following code:

import org.hibernate.dialect.MySQL57Dialect
import org.hibernate.dialect.function.SQLFunctionTemplate
import org.hibernate.dialect.function.StandardSQLFunction
import org.hibernate.type.StandardBasicTypes
import org.hibernate.type.StringType
import java.sql.Types

class CustomMySQLDialect : MySQLDialect(DatabaseVersion.make(5, 70)) {
    init {
        registerFunction("group_concat", StandardSQLFunction("group_concat", StringType()))
        registerFunction("match_against", SQLFunction(StandardBasicTypes.DOUBLE, "match (?1) against (?2 in boolean mode)"))

        registerHibernateType(Types.FLOAT, StandardBasicTypes.DOUBLE.name)
        registerHibernateType(Types.BIGINT, StandardBasicTypes.LONG.name)
        registerHibernateType(Types.TINYINT, StandardBasicTypes.LONG.name)
        registerHibernateType(Types.INTEGER, StandardBasicTypes.LONG.name)
        registerHibernateType(Types.DECIMAL, StandardBasicTypes.LONG.name)
    }
}

It seems that the registerFunction and registerHibernateType has been removed and their replacement is not trivial. Is there a way to od this simply?

There is no easy way, but I think you don’t need registerHibernateType anymore. As for registerFunction, I’d suggest you rather introduce a org.hibernate.boot.model.FunctionContributor:

public class MyFunctionContributor imlements org.hibernate.boot.model.FunctionContributor {
    public void contributeFunctions(FunctionContributions functionContributions) {
        functionContributions.getFunctionRegistry().registerAlternateKey("group_concat", "listagg");
        functionContributions.getFunctionRegistry().registerPattern("match_against", "match (?1) against (?2 in boolean mode)", functionContributions.getTypeConfiguration().getBasicTypeRegistry().resolve(StandardBasicTypes.DOUBLE));
    }
}

Note that you also need a file META-INF/services/org.hibernate.boot.model.FunctionContributor that contains the fully qualified class name of MyFunctionContributor.

But I need this :slight_smile: I want to avoid fetching numbers from database as Short or Int and I want to always get Long.

If you want to affect how native query results are materialized, you have to override the Dialect#resolveSqlTypeDescriptor method similar to this:

public class MyDialect extends MySQLDialect {

//...

	public JdbcType resolveSqlTypeDescriptor(
			String columnTypeName,
			int jdbcTypeCode,
			int precision,
			int scale,
			JdbcTypeRegistry jdbcTypeRegistry) {
		
		switch ( jdbcTypeCode ) {
			case Types.FLOAT:
				jdbcTypeCode = Types.DOUBLE;
				break;
			case Types.TINYINT:
			case Types.INTEGER:
			case Types.DECIMAL:
				jdbcTypeCode = Types.BIGINT;
				break;
		}
		return super.resolveSqlTypeDescriptor( columnTypeName, jdbcTypeCode, precision, scale, jdbcTypeRegistry );
	}
}

This seems to help, thanks!

Hi @beikov,

I upgraded Hibernate from 5.6 to 6.2 and I want to use FunctionContributor to use custom functions in my Hibernate queries. and I use register method like that:

@Override
	public void contributeFunctions(FunctionContributions functionContributions) {
		functionContributions.getFunctionRegistry().register("string_agg",
				new StandardSQLFunction("string_agg", StandardBasicTypes.STRING));

}

and I created the file META-INF/services/org.hibernate.boot.model.FunctionContributor. This file contains fully qualified class name. But somehow my custom function cannot be recognized. Can you help me out to understand what is wrong?

Can you help me out to understand what is wrong?

What you did is correct and it should work, but if it doesn’t, then maybe something is wrong with your environment or build. Either way, note that ORM 6.2 comes with the standard function called listagg that translates to string_agg. Also see https://docs.jboss.org/hibernate/orm/6.2/userguide/html_single/Hibernate_User_Guide.html#hql-aggregate-functions-orderedset

I found my mistake. I had to change usage of date parameter. Thanks for replying me.

Hi @beikov,
I have following custom dialect config in hibernate 5

  public Oracle12cCustomDialect() {
        super();
        this.registerHibernateType(2003, DecimalArrayType.class.getName());
    }

However, when I upgrade to hibernate 6 along with spring boot 3. I get the following error when I remove the above code.

java.lang.NullPointerException: Cannot invoke "String.hashCode()" because "this.typeName" is null
	at org.hibernate.dialect.OracleArrayJdbcType.hashCode(OracleArrayJdbcType.java:202)
	at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1694)
	at org.hibernate.type.BasicTypeRegistry.resolve(BasicTypeRegistry.java:192)
	at org.hibernate.type.BasicTypeRegistry.resolve(BasicTypeRegistry.java:160)
	at org.hibernate.sql.results.jdbc.internal.ResultSetAccess.resolveType(ResultSetAccess.java:146)

Can you help me resolve this ?
It looks like I can’t use registerHibernateType anymore and without that this error occurs.

Read the migration guides. Hibernate 6 is not a drop-in replacement. You will have to adapt your code:

Hi @beikov,

I checked doc and also dug deep into codebase and it looks like a bug in hibernate.

For the column type MDSYS.SDO_ORDINATE_ARRAY which my query is returning, typeName field is being null from org.hibernate.type.descriptor.jdbc.JdbcType.
Check the below snapshot from my debug session.

And due to this reason I am getting following error

java.lang.NullPointerException: Cannot invoke "String.hashCode()" because "this.typeName" is null
	at org.hibernate.dialect.OracleArrayJdbcType.hashCode(OracleArrayJdbcType.java:202)
	at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1694)
	at org.hibernate.type.BasicTypeRegistry.resolve(BasicTypeRegistry.java:192)
	at org.hibernate.type.BasicTypeRegistry.resolve(BasicTypeRegistry.java:160)
	at org.hibernate.sql.results.jdbc.internal.ResultSetAccess.resolveType(ResultSetAccess.java:146)

If it’s a bug as I suspect it is, let me know where can I raise this bug issue ?
Thanks in advance !!

You seem to be using a custom dialect, but this is not encouraged anymore. Please share the dialect contents.

You can ignore that it’s an empty class, contents are commented for that (It was required for hibernate version 5),

So you are running a native query that returns an Oracle “array”, though it seems SDO_ORDINATE_ARRAY is rather a spatial type.
I guess we are simply missing a translation from that type to the SqlTypes constants in resolveSqlTypeDescriptor, similar to what we already do for MDSYS.SDO_GEOMETRY in OracleDialect#resolveSqlTypeDescriptor. Can you explain to me what this SDO_ORDINATE_ARRAY represents? How was this deserialized before? As Geometry?

1 Like

We were simply doing below and deserialization was taken care of for SDO_ORDINATE_ARRAY.
DecimalArrayType is coming from third party dependency.

this.registerHibernateType(2003, DecimalArrayType.class.getName());
        <dependency>
            <groupId>com.vladmihalcea</groupId>
            <artifactId>hibernate-types-52</artifactId>
            <version>2.11.1</version>
        </dependency>

Fully qualified class name: com.vladmihalcea.hibernate.type.array.DecimalArrayType

And for collecting query results in Java, in our projection we were using BigDecimal[] getOrdinates(); Snapshot below for the projection.

Lastly, you can refer below doc for SDO_ORDINATE_ARRAY

Please create an issue in the issue tracker(https://hibernate.atlassian.net) for this feature request. It should be easy to support, but if you need a solution now, you’ll have to override the resolveSqlTypeDescriptor of your custom dialect, something like this:

		if ( jdbcTypeCode == ARRAY && "MDSYS.SDO_ORDINATE_ARRAY".equals( columnTypeName ) ) {
			final JdbcTypeConstructor jdbcTypeConstructor = jdbcTypeRegistry.getConstructor( jdbcTypeCode );
			if ( jdbcTypeConstructor != null ) {
				return jdbcTypeConstructor.resolveType(
						jdbcTypeRegistry.getTypeConfiguration(),
						this,
						jdbcTypeRegistry.getDescriptor( SqlTypes.NUMERIC ),
						ColumnTypeInformation.EMPTY
				);
			}
		}
1 Like

Thank you very much !!! @beikov
But Now, It’s throwing me this error after trying your solution.

PS: I was on hibernate 6.1.5, In order to try your solution I upgraded hibernate to 6.3.0

There is a bug in 6.3.0 which was already fixed, but we didn’t release 6.3.1 yet. Please try 6.2.8 for now.

1 Like

6.2.8 does not have JdbcTypeConstructor to use, Is there any other way can we configure this ?

Yeah, you use this:

		final JdbcType jdbcType = jdbcTypeRegistry.getDescriptor( jdbcTypeCode );
		if ( jdbcTypeCode == Types.ARRAY && "MDSYS.SDO_ORDINATE_ARRAY".equals( columnTypeName ) && jdbcType instanceof ArrayJdbcType ) {
			return ( (ArrayJdbcType) jdbcType ).resolveType(
					jdbcTypeRegistry.getTypeConfiguration(),
					this,
					jdbcTypeRegistry.getDescriptor( SqlTypes.NUMERIC),
					null
			);
		}
1 Like