Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HHH-19035 UserType.isComparable() #9609

Merged
merged 4 commits into from
Jan 14, 2025
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
misc code cleanups around UserTypes
gavinking committed Jan 13, 2025
commit 5c2809a3e1922e12b9f172bdaaedb395f2887349
139 changes: 63 additions & 76 deletions hibernate-core/src/main/java/org/hibernate/type/CustomType.java
Original file line number Diff line number Diff line change
@@ -17,7 +17,6 @@
import org.hibernate.engine.internal.CacheHelper;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
@@ -26,14 +25,16 @@
import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.internal.UserTypeJavaTypeWrapper;
import org.hibernate.type.internal.UserTypeSqlTypeAdapter;
import org.hibernate.type.internal.UserTypeJdbcTypeAdapter;
import org.hibernate.type.internal.UserTypeVersionJavaTypeWrapper;
import org.hibernate.type.spi.TypeConfiguration;
import org.hibernate.usertype.EnhancedUserType;
import org.hibernate.usertype.LoggableUserType;
import org.hibernate.usertype.UserType;
import org.hibernate.usertype.UserVersionType;

import static org.hibernate.internal.util.collections.ArrayHelper.EMPTY_STRING_ARRAY;

/**
* Adapts {@link UserType} to the generic {@link Type} interface, in order
* to isolate user code from changes in the internal Type contracts.
@@ -63,36 +64,22 @@ public class CustomType<J>
private final JdbcLiteralFormatter<J> jdbcLiteralFormatter;

public CustomType(UserType<J> userType, TypeConfiguration typeConfiguration) throws MappingException {
this( userType, ArrayHelper.EMPTY_STRING_ARRAY, typeConfiguration );
this( userType, EMPTY_STRING_ARRAY, typeConfiguration );
}

public CustomType(UserType<J> userType, String[] registrationKeys, TypeConfiguration typeConfiguration)
throws MappingException {
public CustomType(UserType<J> userType, String[] registrationKeys, TypeConfiguration typeConfiguration) {
this.userType = userType;
name = userType.getClass().getName();

if ( userType instanceof JavaType<?> ) {
//noinspection unchecked
mappedJavaType = (JavaType<J>) userType;
}
else if ( userType instanceof JavaTypedExpressible) {
//noinspection unchecked
mappedJavaType = ( (JavaTypedExpressible<J>) userType ).getExpressibleJavaType();
}
else if ( userType instanceof UserVersionType ) {
mappedJavaType = new UserTypeVersionJavaTypeWrapper<>( (UserVersionType<J>) userType );
}
else {
mappedJavaType = new UserTypeJavaTypeWrapper<>( userType );
}
mappedJavaType = getMappedJavaType( userType );

final BasicValueConverter<J, Object> valueConverter = userType.getValueConverter();
if ( valueConverter != null ) {
final BasicValueConverter<J, Object> converter = userType.getValueConverter();
if ( converter != null ) {
// When an explicit value converter is given,
// we configure the custom type to use that instead of adapters that delegate to UserType.
// This is necessary to support selecting a column with multiple domain type representations.
jdbcType = userType.getJdbcType( typeConfiguration );
jdbcJavaType = valueConverter.getRelationalJavaType();
jdbcJavaType = converter.getRelationalJavaType();
//noinspection unchecked
valueExtractor = (ValueExtractor<J>) jdbcType.getExtractor( jdbcJavaType );
//noinspection unchecked
@@ -102,18 +89,34 @@ else if ( userType instanceof UserVersionType ) {
}
else {
// create a JdbcType adapter that uses the UserType binder/extract handling
jdbcType = new UserTypeSqlTypeAdapter<>( userType, mappedJavaType, typeConfiguration );
jdbcType = new UserTypeJdbcTypeAdapter<>( userType, mappedJavaType );
jdbcJavaType = jdbcType.getJdbcRecommendedJavaTypeMapping( null, null, typeConfiguration );
valueExtractor = jdbcType.getExtractor( mappedJavaType );
valueBinder = jdbcType.getBinder( mappedJavaType );
jdbcLiteralFormatter = userType instanceof EnhancedUserType
? jdbcType.getJdbcLiteralFormatter( mappedJavaType )
: null;
jdbcLiteralFormatter =
userType instanceof EnhancedUserType ? jdbcType.getJdbcLiteralFormatter( mappedJavaType ) : null;
}

this.registrationKeys = registrationKeys;
}

private JavaType<J> getMappedJavaType(UserType<J> userType) {
if ( userType instanceof JavaType<?> ) {
//noinspection unchecked
return (JavaType<J>) userType;
}
else if ( userType instanceof JavaTypedExpressible<?> ) {
//noinspection unchecked
return ( (JavaTypedExpressible<J>) userType).getExpressibleJavaType();
}
else if ( userType instanceof UserVersionType<J> userVersionType ) {
return new UserTypeVersionJavaTypeWrapper<>( userVersionType );
}
else {
return new UserTypeJavaTypeWrapper<>( userType );
}
}

public UserType<J> getUserType() {
return userType;
}
@@ -176,13 +179,8 @@ public Object assemble(Serializable cached, SharedSessionContractImplementor ses
// in which case we will try to use a converter for assembling,
// or if that doesn't exist, simply use the relational value as is
if ( assembled == null && cached != null ) {
final BasicValueConverter<J, Object> valueConverter = getUserType().getValueConverter();
if ( valueConverter == null ) {
return cached;
}
else {
return valueConverter.toDomainValue( cached );
}
final BasicValueConverter<J, Object> converter = getUserType().getValueConverter();
return converter == null ? cached : converter.toDomainValue( cached );
}
return assembled;
}
@@ -193,7 +191,7 @@ public Serializable disassemble(Object value, SharedSessionContractImplementor s
}

@Override
public Serializable disassemble(Object value, SessionFactoryImplementor sessionFactory) throws HibernateException {
public Serializable disassemble(Object value, SessionFactoryImplementor sessionFactory) {
return disassembleForCache( value );
}

@@ -204,27 +202,19 @@ private Serializable disassembleForCache(Object value) {
// in which case we will try to use a converter for disassembling,
// or if that doesn't exist, simply use the domain value as is
if ( disassembled == null ){
final BasicValueConverter<J, Object> valueConverter = getUserType().getValueConverter();
if ( valueConverter == null ) {
return disassembled;
}
else {
return (Serializable) valueConverter.toRelationalValue( (J) value );
}
final BasicValueConverter<J, Object> converter = getUserType().getValueConverter();
return converter == null ? disassembled : (Serializable) converter.toRelationalValue( (J) value );
}
else {
return disassembled;
}
return disassembled;
}

@Override
public Object disassemble(Object value, SharedSessionContractImplementor session) {
// Use the value converter if available for conversion to the jdbc representation
final BasicValueConverter<J, Object> valueConverter = getUserType().getValueConverter();
if ( valueConverter == null ) {
return value;
}
else {
return valueConverter.toRelationalValue( (J) value );
}
final BasicValueConverter<J, Object> converter = getUserType().getValueConverter();
return converter == null ? value : converter.toRelationalValue( (J) value );
}

@Override
@@ -240,12 +230,7 @@ public void addToCacheKey(MutableCacheKeyBuilder cacheKey, Object value, SharedS
}
else {
cacheKey.addValue( disassembled );
if ( value == null ) {
cacheKey.addHashCode( 0 );
}
else {
cacheKey.addHashCode( getUserType().hashCode( (J) value ) );
}
cacheKey.addHashCode( value == null ? 0 : getUserType().hashCode( (J) value ) );
}
}

@@ -255,7 +240,7 @@ public Object replace(
Object target,
SharedSessionContractImplementor session,
Object owner,
Map<Object, Object> copyCache) throws HibernateException {
Map<Object, Object> copyCache) {
return getUserType().replace( (J) original, (J) target, owner );
}

@@ -302,8 +287,8 @@ public String toLoggableString(Object value, SessionFactoryImplementor factory)
if ( value == null ) {
return "null";
}
else if ( userType instanceof LoggableUserType ) {
return ( (LoggableUserType) userType ).toLoggableString( value, factory );
else if ( userType instanceof LoggableUserType loggableUserType ) {
return loggableUserType.toLoggableString( value, factory );
}
else if ( userType instanceof EnhancedUserType<?> ) {
return ( (EnhancedUserType<Object>) userType ).toString( value );
@@ -315,32 +300,32 @@ else if ( userType instanceof EnhancedUserType<?> ) {

@Override
public boolean[] toColumnNullness(Object value, MappingContext mapping) {
boolean[] result = new boolean[ getColumnSpan(mapping) ];
final boolean[] result = new boolean[ getColumnSpan(mapping) ];
if ( value != null ) {
Arrays.fill(result, true);
Arrays.fill( result, true );
}
return result;
}

@Override
public boolean isDirty(Object old, Object current, boolean[] checkable, SharedSessionContractImplementor session)
throws HibernateException {
return checkable[0] && isDirty(old, current, session);
return checkable[0] && isDirty( old, current, session );
}

@Override
public boolean canDoSetting() {
if ( getUserType() instanceof ProcedureParameterNamedBinder ) {
return ((ProcedureParameterNamedBinder<?>) getUserType() ).canDoSetting();
}
return false;
return getUserType() instanceof ProcedureParameterNamedBinder<?> procedureParameterNamedBinder
&& procedureParameterNamedBinder.canDoSetting();
}

@Override
public void nullSafeSet(
CallableStatement statement, J value, String name, SharedSessionContractImplementor session) throws SQLException {
public void nullSafeSet(CallableStatement statement, J value, String name, SharedSessionContractImplementor session)
throws SQLException {
if ( canDoSetting() ) {
((ProcedureParameterNamedBinder<J>) getUserType() ).nullSafeSet( statement, value, name, session );
//noinspection unchecked
( (ProcedureParameterNamedBinder<J>) getUserType() )
.nullSafeSet( statement, value, name, session );
}
else {
throw new UnsupportedOperationException(
@@ -351,17 +336,17 @@ public void nullSafeSet(

@Override
public boolean canDoExtraction() {
if ( getUserType() instanceof ProcedureParameterExtractionAware ) {
return ((ProcedureParameterExtractionAware<?>) getUserType() ).canDoExtraction();
}
return false;
return getUserType() instanceof ProcedureParameterExtractionAware<?> procedureParameterExtractionAware
&& procedureParameterExtractionAware.canDoExtraction();
}

@Override
public J extract(CallableStatement statement, int startIndex, SharedSessionContractImplementor session) throws SQLException {
public J extract(CallableStatement statement, int startIndex, SharedSessionContractImplementor session)
throws SQLException {
if ( canDoExtraction() ) {
//noinspection unchecked
return ((ProcedureParameterExtractionAware<J>) getUserType() ).extract( statement, startIndex, session );
return ((ProcedureParameterExtractionAware<J>) getUserType() )
.extract( statement, startIndex, session );
}
else {
throw new UnsupportedOperationException(
@@ -375,7 +360,8 @@ public J extract(CallableStatement statement, String paramName, SharedSessionCon
throws SQLException {
if ( canDoExtraction() ) {
//noinspection unchecked
return ((ProcedureParameterExtractionAware<J>) getUserType() ).extract( statement, paramName, session );
return ((ProcedureParameterExtractionAware<J>) getUserType() )
.extract( statement, paramName, session );
}
else {
throw new UnsupportedOperationException(
@@ -391,7 +377,8 @@ public int hashCode() {

@Override
public boolean equals(Object obj) {
return ( obj instanceof CustomType ) && getUserType().equals( ( (CustomType<?>) obj ).getUserType() );
return obj instanceof CustomType<?> customType
&& getUserType().equals( customType.getUserType() );
}

@Override
Original file line number Diff line number Diff line change
@@ -60,22 +60,26 @@ public String toString(Duration value) {
if ( value == null ) {
return null;
}
String seconds = String.valueOf( value.getSeconds() );
String nanos = String.valueOf( value.getNano() );
String zeros = StringHelper.repeat( '0', 9 - nanos.length() );
else {
final String seconds = String.valueOf( value.getSeconds() );
final String nanos = String.valueOf( value.getNano() );
final String zeros = StringHelper.repeat( '0', 9 - nanos.length() );
return seconds + zeros + nanos;
}
}

@Override
public Duration fromString(CharSequence string) {
if ( string == null ) {
return null;
}
int cutoff = string.length() - 9;
return Duration.ofSeconds(
Long.parseLong( string.subSequence( 0, cutoff ).toString() ),
Long.parseLong( string.subSequence( cutoff, string.length() ).toString() )
);
else {
final int cutoff = string.length() - 9;
return Duration.ofSeconds(
Long.parseLong( string.subSequence( 0, cutoff ).toString() ),
Long.parseLong( string.subSequence( cutoff, string.length() ).toString() )
);
}
}

@Override
Original file line number Diff line number Diff line change
@@ -312,53 +312,24 @@ default CastType getCastType() {
}

static CastType getCastType(int typeCode) {
switch ( typeCode ) {
case INTEGER:
case TINYINT:
case SMALLINT:
return CastType.INTEGER;
case BIGINT:
return CastType.LONG;
case FLOAT:
case REAL:
return CastType.FLOAT;
case DOUBLE:
return CastType.DOUBLE;
case CHAR:
case NCHAR:
case VARCHAR:
case NVARCHAR:
case LONGVARCHAR:
case LONGNVARCHAR:
return CastType.STRING;
case CLOB:
return CastType.CLOB;
case BOOLEAN:
return CastType.BOOLEAN;
case DECIMAL:
case NUMERIC:
return CastType.FIXED;
case DATE:
return CastType.DATE;
case TIME:
case TIME_UTC:
case TIME_WITH_TIMEZONE:
return CastType.TIME;
case TIMESTAMP:
return CastType.TIMESTAMP;
case TIMESTAMP_WITH_TIMEZONE:
return CastType.OFFSET_TIMESTAMP;
case JSON:
case JSON_ARRAY:
return CastType.JSON;
case SQLXML:
case XML_ARRAY:
return CastType.XML;
case NULL:
return CastType.NULL;
default:
return CastType.OTHER;
}
return switch ( typeCode ) {
case INTEGER, TINYINT, SMALLINT -> CastType.INTEGER;
case BIGINT -> CastType.LONG;
case FLOAT, REAL -> CastType.FLOAT;
case DOUBLE -> CastType.DOUBLE;
case CHAR, NCHAR, VARCHAR, NVARCHAR, LONGVARCHAR, LONGNVARCHAR -> CastType.STRING;
case CLOB -> CastType.CLOB;
case BOOLEAN -> CastType.BOOLEAN;
case DECIMAL, NUMERIC -> CastType.FIXED;
case DATE -> CastType.DATE;
case TIME, TIME_UTC, TIME_WITH_TIMEZONE -> CastType.TIME;
case TIMESTAMP -> CastType.TIMESTAMP;
case TIMESTAMP_WITH_TIMEZONE -> CastType.OFFSET_TIMESTAMP;
case JSON, JSON_ARRAY -> CastType.JSON;
case SQLXML, XML_ARRAY -> CastType.XML;
case NULL -> CastType.NULL;
default -> CastType.OTHER;
};
}

/**
Original file line number Diff line number Diff line change
@@ -67,10 +67,9 @@ public UserTypeJavaTypeWrapper(UserType<J> userType) {
}

private int compare(J first, J second) {
if ( userType.equals( first, second ) ) {
return 0;
}
return Comparator.<J, Integer>comparing( userType::hashCode ).compare( first, second );
return userType.equals( first, second ) ? 0
: Comparator.<J, Integer>comparing( userType::hashCode )
.compare( first, second );
}

@Override
@@ -115,40 +114,44 @@ public boolean areEqual(J one, J another) {

@Override
public J fromString(CharSequence string) {
if ( userType instanceof EnhancedUserType<?> ) {
return ( (EnhancedUserType<J>) userType ).fromStringValue( string );
if ( userType instanceof EnhancedUserType<J> enhancedUserType ) {
return enhancedUserType.fromStringValue( string );
}
throw new UnsupportedOperationException( "No support for parsing UserType values from String: " + userType );
}

@Override
public String toString(J value) {
if ( userType.returnedClass().isInstance( value ) && userType instanceof EnhancedUserType<?> ) {
return ( (EnhancedUserType<J>) userType ).toString( value );
}
return value == null ? "null" : value.toString();
return userType.returnedClass().isInstance( value )
&& userType instanceof EnhancedUserType<J> enhancedUserType
? enhancedUserType.toString( value )
: value == null ? "null" : value.toString();
}

@Override
public <X> X unwrap(J value, Class<X> type, WrapperOptions options) {
final BasicValueConverter<J, Object> valueConverter = userType.getValueConverter();
if ( value != null && !type.isInstance( value ) && valueConverter != null ) {
final Object relationalValue = valueConverter.toRelationalValue( value );
return valueConverter.getRelationalJavaType().unwrap( relationalValue, type, options );
final BasicValueConverter<J, Object> converter = userType.getValueConverter();
if ( value != null && !type.isInstance( value ) && converter != null ) {
final Object relationalValue = converter.toRelationalValue( value );
return converter.getRelationalJavaType().unwrap( relationalValue, type, options );
}
else {
//noinspection unchecked
return (X) value;
}
//noinspection unchecked
return (X) value;
}

@Override
public <X> J wrap(X value, WrapperOptions options) {
final BasicValueConverter<J, Object> valueConverter = userType.getValueConverter();
if ( value != null && !userType.returnedClass().isInstance( value ) && valueConverter != null ) {
final J domainValue = valueConverter.toDomainValue( value );
return valueConverter.getDomainJavaType().wrap( domainValue, options );
final BasicValueConverter<J, Object> converter = userType.getValueConverter();
if ( value != null && !userType.returnedClass().isInstance( value ) && converter != null ) {
final J domainValue = converter.toDomainValue( value );
return converter.getDomainJavaType().wrap( domainValue, options );
}
else {
//noinspection unchecked
return (J) value;
}
//noinspection unchecked
return (J) value;
}

@Override
@@ -181,16 +184,11 @@ public Serializable disassemble(J value, SharedSessionContract session) {
// in which case we will try to use a converter for disassembling,
// or if that doesn't exist, simply use the domain value as is
if ( disassembled == null && value != null ) {
final BasicValueConverter<J, Object> valueConverter = userType.getValueConverter();
if ( valueConverter == null ) {
return (Serializable) value;
}
else {
return valueConverter.getRelationalJavaType().getMutabilityPlan().disassemble(
valueConverter.toRelationalValue( value ),
session
);
}
final BasicValueConverter<J, Object> converter = userType.getValueConverter();
return converter == null
? (Serializable) value
: converter.getRelationalJavaType().getMutabilityPlan()
.disassemble( converter.toRelationalValue( value ), session );
}
return disassembled;
}
@@ -203,15 +201,12 @@ public J assemble(Serializable cached, SharedSessionContract session) {
// in which case we will try to use a converter for assembling,
// or if that doesn't exist, simply use the relational value as is
if ( assembled == null && cached != null ) {
final BasicValueConverter<J, Object> valueConverter = userType.getValueConverter();
if ( valueConverter == null ) {
return (J) cached;
}
else {
return valueConverter.toDomainValue( cached );
}
final BasicValueConverter<J, Object> converter = userType.getValueConverter();
return converter == null ? (J) cached : converter.toDomainValue( cached );
}
else {
return assembled;
}
return assembled;
}
}
}
Original file line number Diff line number Diff line change
@@ -28,19 +28,19 @@
*
* @author Steve Ebersole
*/
public class UserTypeSqlTypeAdapter<J> implements JdbcType {
public class UserTypeJdbcTypeAdapter<J> implements JdbcType {
private final UserType<J> userType;
private final JavaType<J> jtd;
private final JavaType<J> javaType;

private final ValueExtractor<J> valueExtractor;
private final ValueBinder<J> valueBinder;

public UserTypeSqlTypeAdapter(UserType<J> userType, JavaType<J> jtd, TypeConfiguration typeConfiguration) {
public UserTypeJdbcTypeAdapter(UserType<J> userType, JavaType<J> javaType) {
this.userType = userType;
this.jtd = jtd;
this.javaType = javaType;

this.valueExtractor = new ValueExtractorImpl<>( userType, jtd );
this.valueBinder = new ValueBinderImpl<>( userType, typeConfiguration );
this.valueExtractor = new ValueExtractorImpl<>( userType );
this.valueBinder = new ValueBinderImpl<>( userType );
}

@Override
@@ -63,15 +63,15 @@ public boolean isComparable() {
@SuppressWarnings("unchecked")
public <X> ValueBinder<X> getBinder(JavaType<X> javaType) {
assert javaType.getJavaTypeClass() == null
|| jtd.getJavaTypeClass().isAssignableFrom( javaType.getJavaTypeClass() );
|| this.javaType.getJavaTypeClass().isAssignableFrom( javaType.getJavaTypeClass() );
return (ValueBinder<X>) valueBinder;
}

@Override
@SuppressWarnings("unchecked")
public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) {
assert javaType.getJavaTypeClass() == null
|| javaType.getJavaTypeClass().isAssignableFrom( jtd.getJavaTypeClass() );
|| javaType.getJavaTypeClass().isAssignableFrom( this.javaType.getJavaTypeClass() );
return (ValueExtractor<X>) valueExtractor;
}

@@ -81,7 +81,7 @@ public <T> JavaType<T> getJdbcRecommendedJavaTypeMapping(
Integer scale,
TypeConfiguration typeConfiguration) {
//noinspection unchecked
return (JavaType<T>) jtd;
return (JavaType<T>) javaType;
}

@Override
@@ -97,11 +97,9 @@ public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType)

private static class ValueExtractorImpl<J> implements ValueExtractor<J> {
private final UserType<J> userType;
private final JavaType<J> javaType;

public ValueExtractorImpl(UserType<J> userType, JavaType<J> javaType) {
public ValueExtractorImpl(UserType<J> userType) {
this.userType = userType;
this.javaType = javaType;
}

@Override
@@ -138,36 +136,32 @@ public J extract(CallableStatement statement, String paramName, WrapperOptions o
}

private void logExtracted(int paramIndex, J extracted) {
if ( ! JdbcExtractingLogging.LOGGER.isTraceEnabled() ) {
return;
}

if ( extracted == null ) {
JdbcExtractingLogging.logNullExtracted( paramIndex, userType.getSqlType() );
}
else {
JdbcExtractingLogging.logExtracted( paramIndex, userType.getSqlType(), extracted );
if ( JdbcExtractingLogging.LOGGER.isTraceEnabled() ) {
if ( extracted == null ) {
JdbcExtractingLogging.logNullExtracted( paramIndex, userType.getSqlType() );
}
else {
JdbcExtractingLogging.logExtracted( paramIndex, userType.getSqlType(), extracted );
}
}
}

private void logExtracted(String paramName, J extracted) {
if ( ! JdbcExtractingLogging.LOGGER.isTraceEnabled() ) {
return;
}

if ( extracted == null ) {
JdbcExtractingLogging.logNullExtracted( paramName, userType.getSqlType() );
}
else {
JdbcExtractingLogging.logExtracted( paramName, userType.getSqlType(), extracted );
if ( JdbcExtractingLogging.LOGGER.isTraceEnabled() ) {
if ( extracted == null ) {
JdbcExtractingLogging.logNullExtracted( paramName, userType.getSqlType() );
}
else {
JdbcExtractingLogging.logExtracted( paramName, userType.getSqlType(), extracted );
}
}
}
}

private static class ValueBinderImpl<J> implements ValueBinder<J> {
private final UserType<J> userType;

public ValueBinderImpl(UserType<J> userType, TypeConfiguration typeConfiguration) {
public ValueBinderImpl(UserType<J> userType) {
this.userType = userType;
}

@@ -185,7 +179,7 @@ public void bind(PreparedStatement st, J value, int index, WrapperOptions option
}

@Override
public void bind(CallableStatement st, J value, String name, WrapperOptions options) throws SQLException {
public void bind(CallableStatement st, J value, String name, WrapperOptions options) {
throw new UnsupportedOperationException( "Using UserType for CallableStatement parameter binding not supported" );
}
}