We are migrating from hibernate 5.x.x to hibernate 6.5.2. Our code base has custom user type which needs to be migrated/update. We have type mismatch issues while migrating these custom user type.
Below is our custom user type.
/**
* A platform specific implementation of SpcfUniqueId.
*/
public class SpcfUniqueIdImpl extends SpcfUniqueId
{
/**
* needed for serialization
*/
private static final long serialVersionUID = -5416092023493031186L;
/**
* the byte array to hold the value of this UUID
*/
private byte[] mBytes = null;
/**
* Constructs a new SpcfUniqueId instance with an empty value
*/
public SpcfUniqueIdImpl()
{
this(false);
}
/**
* Constructs a new SpcfUniqueId instance with either empty or random value.
* @param initializeToRandomValue Initialize with a pseudo-random initial value if true
*/
public SpcfUniqueIdImpl(boolean initializeToRandomValue)
{
String uniqueId = null;
if (initializeToRandomValue)
{
uniqueId = doGenerateRandomUniqueIdString();
}
else
{
uniqueId = EmptyGuid;
}
mBytes = toByteArray(uniqueId);
}
/**
* Constructs a new SpcfUniqueId instance from the specified string.
* The string must be a series of 32 hexadecimal digits in the standard
* format: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX or one of the acceptable
* following formats: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,
* {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX},
* or (XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX). Any non-hexadecimal
* digit character can be used as a separator, if consistent.
* @param s The string representation of the unique id value.
* @throws SpcfArgumentNullException - if the specified string is null
* @throws SpcfIllegalArgumentException - if the specified string is empty or does
* not have the expected format
*/
public SpcfUniqueIdImpl(String s)
{
SpcfParamValidator.checkIsNotNull(s, "s");
mBytes = toByteArray(validateFormat(s));
}
/**
* Constructs a new SpcfUniqueId instance from the specified byte array.
* The byte array must be 16 bytes long and match byte order for java.util.UUID
* @param bytes The binary representation of the java.util.UUID.
* @throws SpcfArgumentNullException - if the specified byte array is null
* @throws SpcfIllegalArgumentException - if the byte array is not the expected size.
*/
public SpcfUniqueIdImpl(byte[] bytes)
{
SpcfParamValidator.checkIsNotNull(bytes, "bytes");
if( bytes.length != 16 )
{
throw new SpcfIllegalArgumentException( "bytes must be 16 bytes in length" );
}
mBytes = bytes;
}
/**
* Constructs a new SpcfUniqueId instance from the specified java.util.UUID.
* @param uid The java.util.UUID to encapsulate.
* @throws SpcfArgumentNullException - if the specified byte java.util.UUID is null
*/
public SpcfUniqueIdImpl(java.util.UUID uid)
{
SpcfParamValidator.checkIsNotNull(uid, "uid");
mBytes = toByteArray(uid.toString());
}
/**
* @see com.intuit.spc.foundations.portability.SpcfUniqueId#equals(Object)
*/
@Override
public boolean equals(Object o)
{
if (o == null)
{
return false;
}
if (o instanceof SpcfUniqueIdImpl)
{
return java.util.Arrays.equals(mBytes, ((SpcfUniqueIdImpl)o).toByteArray());
}
return false;
}
/**
* @see com.intuit.spc.foundations.portability.SpcfUniqueId#hashCode()
*/
@Override
public int hashCode()
{
int hash = 0;
for (int i = 0; i < mBytes.length; i ++)
hash += mBytes[i] * 31 ^ mBytes.length - (i + 1);
return hash;
}
/**
* @see com.intuit.spc.foundations.portability.SpcfUniqueId#toString()
*/
@Override
public String toString()
{
long mostSig = 0;
for (int i = 0; i < 8; i++) {
mostSig = (mostSig << 8) | (mBytes[i] & 0xff);
}
long leastSig = 0;
for (int i = 8; i < 16; i++) {
leastSig = (leastSig << 8) | (mBytes[i] & 0xff);
}
java.util.UUID uid = new UUID(mostSig, leastSig);
return uid.toString();
}
/**
* @see com.intuit.spc.foundations.portability.SpcfUniqueId#getStandardFormatString()
*/
@Override
public String getStandardFormatString()
{
return toString();
}
/**
* @see com.intuit.spc.foundations.portability.SpcfUniqueId#doGenerateRandomUniqueIdString()
*/
@Override
protected String doGenerateRandomUniqueIdString()
{
java.util.UUID uid = java.util.UUID.randomUUID();
return uid.toString();
}
/**
* Get the byte array in standard network byte order.
* @param standardizedString the UUID string in standarized order
* @return the UUID in a byte array in standard network byte order
*/
private byte[] toByteArray(String standardizedString)
{
byte[] byteArray = new byte[16];
String id = standardizedString;
for (int i = 0, j = 0; i < 36; ++j)
{
// Need to bypass hyphens:
switch (i)
{
case 8 :
case 13 :
case 18 :
case 23 :
++i;
}
char c = id.charAt(i);
if (c >= '0' && c <= '9')
{
byteArray[j] = (byte) ((c - '0') << 4);
}
else if (c >= 'a' && c <= 'f')
{
byteArray[j] = (byte) ((c - 'a' + 10) << 4);
}
c = id.charAt(++i);
if (c >= '0' && c <= '9')
{
byteArray[j] |= (byte) (c - '0');
}
else if (c >= 'a' && c <= 'f')
{
byteArray[j] |= (byte) (c - 'a' + 10);
}
++i;
}
return byteArray;
}
/**
* Get the byte array in standard network byte order.
* @return the UUID in a byte array in standard network byte order
*/
public byte[] toByteArray()
{
return mBytes;
}
/**
* Get the equivalent platform specific java.util.UUID
* @return the java.util.UUID representation of this value
*/
public java.util.UUID getSpecific()
{
//return java.util.UUID.fromString(getStandardizedString());
return java.util.UUID.fromString(toString());
}
/**
* Validates a string to see if its in accepted format. Returns standard
* format if it is, else throws SpcfIllegalArgumentException.
* The string must be a series of 32 hexadecimal digits in the standard
* format: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX or one of the acceptable
* following formats: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,
* {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX},
* or (XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX). Any non-hexadecimal
* digit character can be used as a separator, if consistent.
* @param s The string representation of the unique id value.
* @return the UUID string in standard format
* @throws SpcfIllegalArgumentException - if the specified string is empty or does
* not have the expected format
*/
protected String validateFormat(String s)
{
// valid lengths are 32, 36, 34, 38
// valid brackets are {} and ()
int original_length = s.length();
String tempId = s.toLowerCase();
if (original_length == 0)
{
throw new SpcfIllegalArgumentException("Unique ID: " + s + " " + "cannot be zero length.");
}
if (original_length > 38 || original_length < 32 )
{
throw new SpcfIllegalArgumentException("Unique ID: " + s + " " + "is not the correct size");
}
if (s.charAt(0) == '{')
{
if (s.charAt(original_length - 1) != '}')
{
throw new SpcfIllegalArgumentException("Unique ID: " + s + " " + "does not have the correct format.");
}
tempId = tempId.substring(1, original_length - 1);
}
if (s.charAt(0) == '(')
{
if (s.charAt(original_length - 1) != ')')
{
throw new SpcfIllegalArgumentException("Unique ID: " + s + " " + "does not have the correct format.");
}
tempId = tempId.substring(1, original_length - 1);
}
// at this point, length must be either 32 (no separators) or 36 to be valid
if (tempId.length() == 36)
{
//remove separators and check consistency
char separator;
separator = tempId.charAt(8);
boolean separatorsNotConsistent = false;
if (java.lang.Character.isLetterOrDigit(separator))
{
//separator cannot be a hex digit
throw new SpcfIllegalArgumentException("Unique ID: " + s + " " + "does not have the correct format.");
}
// make sure separator is consistent
if (tempId.charAt(13) != separator) { separatorsNotConsistent = true;}
if (tempId.charAt(18) != separator) { separatorsNotConsistent = true;}
if (tempId.charAt(23) != separator) { separatorsNotConsistent = true;}
if (separatorsNotConsistent)
{
throw new SpcfIllegalArgumentException("Unique ID: " + s + " " + "does not have the correct format.");
}
StringBuilder sb = new StringBuilder();
sb.append(tempId.substring(0, 8));
sb.append(tempId.substring(9, 13));
sb.append(tempId.substring(14, 18));
sb.append(tempId.substring(19, 23));
sb.append(tempId.substring(24));
//remove separators and check consistency
tempId = sb.toString();
}
// at this point separators are
if (tempId.length() != 32)
{
throw new SpcfIllegalArgumentException("Unique ID: " + s + " " + "does not have the correct format.");
}
//validate hex digits
String hexDigits = "0123456789abcdef";
char c;
for (int i = 0; i < tempId.length(); i++)
{
c = tempId.charAt(i);
if (hexDigits.indexOf(c) < 0)
{
throw new SpcfIllegalArgumentException("Unique ID: " + s + " " + "has invalid hexadecimal digits.");
}
}
// add standard separators
StringBuilder sbFinalId = new StringBuilder();
sbFinalId.append(tempId.substring(0, 8));
sbFinalId.append("-");
sbFinalId.append(tempId.substring(8, 12));
sbFinalId.append("-");
sbFinalId.append(tempId.substring(12, 16));
sbFinalId.append("-");
sbFinalId.append(tempId.substring(16, 20));
sbFinalId.append("-");
sbFinalId.append(tempId.substring(20));
return sbFinalId.toString();
}
}
User type implementation.
/**
* This class implements the UserType to persist SpcfUniqueId to the database.
*/
public final class SpcfUniqueIdUserType implements UserType<SpcfUniqueId>, Serializable
{
private static final long serialVersionUID = -5421900602922035504L;
/**
* logger for logging SQL binding variable with the SQL. Use log4j logger
* so we respect the same log level and log to where Hibernate logs.
*/
private static final SpcfLogger sHibernateLogger = Application.getLogger("org.hibernate.type");
/**
* Types returned by sql-types method.
*/
private static final int[] sSqlTypes = new int[] { Types.VARCHAR };
/**
* @see org.hibernate.usertype.UserType#sqlTypes()
*/
public final int[] sqlTypes()
{
return sSqlTypes;
}
/**
* @see org.hibernate.usertype.UserType#equals(Object, Object)
*/
public final boolean equals(SpcfUniqueId x, SpcfUniqueId y)
{
return (x == null) ? (y == null) : x.equals(y);
}
/**
* @see org.hibernate.usertype.UserType#hashCode(Object)
*/
public final int hashCode(SpcfUniqueId obj)
{
return obj.hashCode();
}
/**
* @see org.hibernate.usertype.UserType#isMutable()
*/
public final boolean isMutable()
{
return true;
}
@Override
public int getSqlType() {
return 0;
}
/**
* @see org.hibernate.usertype.UserType#returnedClass()
*/
@SuppressWarnings("unchecked")
public final Class returnedClass()
{
return SpcfUniqueId.class;
}
/**
* @see org.hibernate.usertype.UserType#deepCopy(Object)
*/
public final SpcfUniqueId deepCopy(SpcfUniqueId value)
{
if (value == null)
{
return null;
}
// else if (value instanceof String)
// {
// return SpcfTypeConversionUtilities.convertStringToUniqueId((String)value);
// }
else if (value instanceof SpcfUniqueId)
{
return value;
}
else
{
throw new HibernateException("Unsupported value type: " + value.getClass());
}
}
/**
* @see org.hibernate.usertype.UserType#nullSafeGet(ResultSet, String[], SharedSessionContractImplementor, Object)
*/
@Override
public final SpcfUniqueId nullSafeGet(ResultSet rs, int position, SharedSessionContractImplementor sharedSessionContractImplementor, Object owner) throws SQLException
{
String uniqueId = rs.getString(position);
return SpcfTypeConversionUtilities.convertStringToUniqueId(uniqueId);
}
/**
* @see org.hibernate.usertype.UserType#nullSafeSet(PreparedStatement, Object, int, SharedSessionContractImplementor)
*/
@Override
public final void nullSafeSet(PreparedStatement st, SpcfUniqueId value, int index, SharedSessionContractImplementor sharedSessionContractImplementor) throws SQLException
{
Object localValue = value;
// convert from SpcfUniqueId to String
if (value instanceof SpcfUniqueId)
{
localValue = SpcfTypeConversionUtilities.convertUniqueIdToString((SpcfUniqueId)value);
}
// If value is null or BigDecimal then simply save the value
if (sHibernateLogger.isDebugEnabled())
{
sHibernateLogger.debug(this.getClass().getSimpleName() + " - binding '"
+ value + "' to parameter: " + index);
}
st.setString(index, (String)localValue);
}
/**
* @see org.hibernate.usertype.UserType#assemble(Serializable, Object)
*/
public final SpcfUniqueId assemble(Serializable cached, Object owner) throws HibernateException
{
return null;
}
/**
* @see org.hibernate.usertype.UserType#disassemble(Object)
*/
public final Serializable disassemble(SpcfUniqueId value) throws HibernateException
{
return null;
}
/**
* @see org.hibernate.usertype.UserType#replace(Object, Object, Object)
*/
public final SpcfUniqueId replace(SpcfUniqueId original, Object target, Object owner) throws HibernateException
{
return deepCopy(original);
}
}
Stack trace:
java.lang.ArrayStoreException: com.intuit.spc.foundations.portabilitySpecific.SpcfUniqueIdImpl
at org.hibernate.type.descriptor.java.ArrayJavaType.unwrap(ArrayJavaType.java:272)
at org.hibernate.type.descriptor.java.ArrayJavaType.unwrap(ArrayJavaType.java:33)
at org.hibernate.type.descriptor.jdbc.ArrayJdbcType$1.getArray(ArrayJdbcType.java:136)
at org.hibernate.type.descriptor.jdbc.ArrayJdbcType$1.doBind(ArrayJdbcType.java:101)
at org.hibernate.type.descriptor.jdbc.BasicBinder.bind(BasicBinder.java:61)
at org.hibernate.sql.exec.internal.AbstractJdbcParameter.bindParameterValue(AbstractJdbcParameter.java:130)
at org.hibernate.sql.exec.internal.AbstractJdbcParameter.bindParameterValue(AbstractJdbcParameter.java:101)
at org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess.bindParameters(DeferredResultSetAccess.java:203)
at org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess.executeQuery(DeferredResultSetAccess.java:233)
at org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess.getResultSet(DeferredResultSetAccess.java:167)
at org.hibernate.sql.results.jdbc.internal.JdbcValuesResultSetImpl.advanceNext(JdbcValuesResultSetImpl.java:265)
at org.hibernate.sql.results.jdbc.internal.JdbcValuesResultSetImpl.processNext(JdbcValuesResultSetImpl.java:145)
at org.hibernate.sql.results.jdbc.internal.AbstractJdbcValues.next(AbstractJdbcValues.java:19)
at org.hibernate.sql.results.internal.RowProcessingStateStandardImpl.next(RowProcessingStateStandardImpl.java:67)
at org.hibernate.sql.results.spi.ListResultsConsumer.consume(ListResultsConsumer.java:182)
at org.hibernate.sql.results.spi.ListResultsConsumer.consume(ListResultsConsumer.java:33)
at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.doExecuteQuery(JdbcSelectExecutorStandardImpl.java:211)
at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.executeQuery(JdbcSelectExecutorStandardImpl.java:83)
at org.hibernate.sql.exec.spi.JdbcSelectExecutor.list(JdbcSelectExecutor.java:76)
at org.hibernate.sql.exec.spi.JdbcSelectExecutor.list(JdbcSelectExecutor.java:65)
at org.hibernate.loader.ast.internal.CollectionBatchLoaderArrayParam.initializeKeys(CollectionBatchLoaderArrayParam.java:204)
at org.hibernate.loader.ast.internal.AbstractCollectionBatchLoader.load(AbstractCollectionBatchLoader.java:94)
at org.hibernate.loader.ast.internal.CollectionBatchLoaderArrayParam.load(CollectionBatchLoaderArrayParam.java:118)
at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:703)
at org.hibernate.event.internal.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:67)
at com.intuit.sbd.payroll.psp.hibernate.PSPInitializeCollectionEventListener.onInitializeCollection(PSPInitializeCollectionEventListener.java:44)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127)
at org.hibernate.internal.SessionImpl.initializeCollection(SessionImpl.java:1720)
at org.hibernate.collection.spi.AbstractPersistentCollection.lambda$initialize$3(AbstractPersistentCollection.java:615)
at org.hibernate.collection.spi.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:264)
at org.hibernate.collection.spi.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:613)
at org.hibernate.collection.spi.AbstractPersistentCollection.read(AbstractPersistentCollection.java:136)
Analysis:
Getting java.lang.ArrayStoreException: at org.hibernate.type.descriptor.java.ArrayJavaType.unwrap(ArrayJavaType.java:272)
final Class<?> preferredJavaTypeClass = type.getComponentType();
final Object[] unwrapped = (Object[]) Array.newInstance( preferredJavaTypeClass, value.length );
for ( int i = 0; i < value.length; i++ ) {
unwrapped[i] = getElementJavaType().unwrap( value[i], preferredJavaTypeClass, options );
}
Unwrapped array was of string type where as unwrapped value was of SpcfUniqueId type, on further digging we identified the preferred java type was string instead of SpcfUniqueId
classname ArrayJdbcType
Preferred java type is computed as follows:
private java.sql.Array getArray(X value, WrapperOptions options) throws SQLException {
final TypeConfiguration typeConfiguration = options.getSessionFactory().getTypeConfiguration();
final JdbcType elementJdbcType = ( (ArrayJdbcType) getJdbcType() ).getElementJdbcType();
final JdbcType underlyingJdbcType = typeConfiguration.getJdbcTypeRegistry()
.getDescriptor( elementJdbcType.getDefaultSqlTypeCode() );
final Class<?> preferredJavaTypeClass = elementJdbcType.getPreferredJavaTypeClass( options );
final Class<?> elementJdbcJavaTypeClass;
if ( preferredJavaTypeClass == null ) {
elementJdbcJavaTypeClass = underlyingJdbcType.getJdbcRecommendedJavaTypeMapping(
null,
null,
typeConfiguration
).getJavaTypeClass();
}
else {
elementJdbcJavaTypeClass = preferredJavaTypeClass;
}
//noinspection unchecked
final Class<Object[]> arrayClass = (Class<Object[]>)
Array.newInstance( elementJdbcJavaTypeClass, 0 ).getClass();
final Object[] objects = getJavaType().unwrap( value, arrayClass, options );
final SharedSessionContractImplementor session = options.getSession();
final String typeName = getElementTypeName( elementJdbcType, session );
return session.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection()
.createArrayOf( typeName, objects );
}
elementJdbcType was of type UserTypeSqlTypeAdapter and we were getting null.
UserTypeSqlTypeAdapter extends JdbcType and does not override getPreferredJavaTypeClass() and returns null instead of correct type.
As per our understanding UserTypeSqlTypeAdapter should override getPreferredJavaType as follows:
public Class getPreferredJavaTypeClass() {
return userType.returnedClass();
}
Is our understanding correct, can you please help us ?