I am migrating a project from Hibernate 5 to Hibernate 6. One old class implemented UserType interface in Hibernate 5 but cannot be compiled in Hibernate 6. So, I need to create new custom user type mapping for the class below:
public class AlertData implements Serializable {
private static final long serialVersionUID = 1L;
private AlertType type;
private Object data;
private AlertData(AlertType type, Object data) {
this.type = type;
this.data = data;
}
}
public enum AlertType {
PRODUCER, TIME, SIZE;
}
The “type” should be mapped to a String. The “data” should be saved as a Jason String in database. Code below is my old class:
public class AlertDataType implements UserType {
private ObjectMapper mapper = new ObjectMapper();
@Override
public int[] sqlTypes() {
return new int[] {StringType.INSTANCE.sqlType(), Types.JAVA_OBJECT};
}
@Override
public Class returnedClass() {
return AlertData.class;
}
@Override
public boolean equals(Object x, Object y) throws HibernateException {
if (x == y)
return true;
if (Objects.isNull(x) || Objects.isNull(y))
return false;
return x.equals(y);
}
@Override
public int hashCode(Object o) throws HibernateException {
return o.hashCode();
}
@Override
public Object nullSafeGet(ResultSet resultSet, String[] names,
SharedSessionContractImplementor sharedSessionContractImplementor, Object o)
throws HibernateException, SQLException {
AlertType type = AlertType.valueOf(resultSet.getString(names[0]));
String jsonData = resultSet.getString(names[1]);
// remove leading & ending double-quote (")
if (jsonData.length() > 2 && jsonData.startsWith("\"") && jsonData.endsWith("\"")) {
jsonData = jsonData.substring(1, jsonData.length() - 1);
}
// remove \ character.
jsonData = jsonData.replace("\\", "");
Object data = null;
if (type == AlertType.SIZE) {
try {
data = mapper.readValue(jsonData, SizeAlertData.class);
} catch (JsonProcessingException e) {
throw new RuntimeException("Failed to deserialize Size Alert Data: " + e.getMessage(), e);
}
} else if (type == AlertType.TIME) {
try {
data = mapper.readValue(jsonData, TimeAlertData[].class);
} catch (JsonProcessingException e) {
throw new RuntimeException("Failed to deserialize Timed Alert Data: " + e.getMessage(), e);
}
} else if (type == AlertType.PRODUCER) {
try {
data = mapper.readValue(jsonData, String[].class);
} catch (JsonProcessingException e) {
throw new RuntimeException("Failed to deserialize Producer Alert Data: " + e.getMessage(), e);
}
}
return AlertData.create(type, data);
}
@Override
public void nullSafeSet(PreparedStatement preparedStatement, Object o, int i,
SharedSessionContractImplementor sharedSessionContractImplementor)
throws HibernateException, SQLException {
if (o == null) {
preparedStatement.setNull(i, StringType.INSTANCE.sqlType());
preparedStatement.setNull(i + 1, Types.OTHER);
} else {
final AlertData alertData = (AlertData) o;
preparedStatement.setString(i, alertData.getType().toString());
try {
String s = mapper.writeValueAsString(alertData.getData());
preparedStatement.setObject(i + 1, s, Types.OTHER);
} catch (JsonProcessingException e) {
throw new RuntimeException("Failed to serialize alert data: " + e.getMessage(), e);
}
}
}
@Override
public Object deepCopy(Object o) throws HibernateException {
if (Objects.isNull(o)) {
return null;
}
AlertData alertData = (AlertData) o;
Object data = null;
if (alertData.getType() == AlertType.SIZE) {
SizeAlertData sizeAlertData = (SizeAlertData) alertData.getData();
data = SizeAlertData.create(sizeAlertData.min, sizeAlertData.max);
} else if (alertData.getType() == AlertType.TIME) {
TimeAlertData[] timeAlertData = (TimeAlertData[]) alertData.getData();
data = Arrays.copyOf(timeAlertData, timeAlertData.length);
} else if (alertData.getType() == AlertType.PRODUCER) {
String[] producers = (String[]) alertData.getData();
data = Arrays.copyOf(producers, producers.length);
}
return AlertData.create(alertData.getType(), data);
}
@Override
public boolean isMutable() {
return false;
}
@Override
public Serializable disassemble(Object o) throws HibernateException {
return (Serializable) o;
}
@Override
public Object assemble(Serializable serializable, Object o) throws HibernateException {
return serializable;
}
@Override
public Object replace(Object o, Object o1, Object o2) throws HibernateException {
return o;
}
}
I created a class implements CompositeUserType interface but got exception “java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Enum”.
My new custom user type class:
public class AlertDataType implements CompositeUserType<AlertData> {
private ObjectMapper mapper = new ObjectMapper();
public static class AlertDataMapper {
private Object data;
private AlertType type;
}
@Override
public Object getPropertyValue(AlertData component, int property) throws HibernateException {
if (component == null) {
return null;
}
switch (property) {
case 0:
if (component.getData() == null) {
return null;
}
try {
return mapper.writeValueAsString(component.getData());
} catch (JsonProcessingException e) {
throw new HibernateException("Failed to serialize alert data", e);
}
case 1:
return component.getType() == null ? null : component.getType().toString();
default:
throw new HibernateException("Unknown property index: " + property);
}
}
@Override
public AlertData instantiate(ValueAccess values, SessionFactoryImplementor sessionFactory) {
// Ensure proper handling of AlertType conversion
String typeString = values.getValue(1, String.class);
AlertType type;
try {
type = AlertType.valueOf(typeString);
} catch (IllegalArgumentException e) {
throw new HibernateException(
"Invalid AlertType value in database: " + values.getValue(0, String.class), e);
}
String jsonData = values.getValue(0, String.class);
// remove leading & ending double-quote (")
if (jsonData.length() > 2 && jsonData.startsWith("\"") && jsonData.endsWith("\"")) {
jsonData = jsonData.substring(1, jsonData.length() - 1);
}
// remove \ character.
jsonData = jsonData.replace("\\", "");
Object data = null;
try {
switch (type) {
case SIZE:
data = mapper.readValue(jsonData, SizeAlertData.class);
break;
case TIME:
data = mapper.readValue(jsonData, TimeAlertData[].class);
break;
case PRODUCER:
data = mapper.readValue(jsonData, String[].class);
break;
default:
throw new HibernateException("Unhandled AlertType: " + type);
}
} catch (JsonProcessingException e) {
throw new HibernateException(
"Failed to deserialize Alert Data for type " + type + ": " + e.getMessage(), e);
}
return AlertData.create(type, data);
}
@Override
public Class<?> embeddable() {
return AlertDataMapper.class;
}
@Override
public Class<AlertData> returnedClass() {
return AlertData.class;
}
@Override
public boolean equals(AlertData x, AlertData y) {
if (x == y)
return true;
if (x == null || y == null)
return false;
return x.equals(y);
}
@Override
public int hashCode(AlertData x) {
return x.hashCode();
}
@Override
public AlertData deepCopy(AlertData value) {
if (Objects.isNull(value)) {
return null;
}
AlertData alertData = value;
Object data = null;
if (alertData.getType() == AlertType.SIZE) {
SizeAlertData sizeAlertData = (SizeAlertData) alertData.getData();
data = SizeAlertData.create(sizeAlertData.min, sizeAlertData.max);
} else if (alertData.getType() == AlertType.TIME) {
TimeAlertData[] timeAlertData = (TimeAlertData[]) alertData.getData();
data = Arrays.copyOf(timeAlertData, timeAlertData.length);
} else if (alertData.getType() == AlertType.PRODUCER) {
String[] producers = (String[]) alertData.getData();
data = Arrays.copyOf(producers, producers.length);
}
return AlertData.create(alertData.getType(), data);
}
@Override
public boolean isMutable() {
return false;
}
@Override
public Serializable disassemble(AlertData value) {
return value;
}
@Override
public AlertData assemble(Serializable cached, Object owner) {
return (AlertData) cached;
}
@Override
public AlertData replace(AlertData original, AlertData target, Object owner) {
return original;
}
}
Changes in entity class:
@Embedded
@AttributeOverride(name = "data", column = @Column(name = "data", nullable = false))
@AttributeOverride(name = "type", column = @Column(name = "type", nullable = false))
@CompositeType(AlertDataType.class)
protected AlertData data;
Exception stack trace:
java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Enum (java.lang.String and java.lang.Enum are in module java.base of loader 'bootstrap')
at org.hibernate.type.EnumType.equals(EnumType.java:72)
at org.hibernate.type.CustomType.isEqual(CustomType.java:166)
at org.hibernate.type.AbstractType.isSame(AbstractType.java:103)
at org.hibernate.type.AbstractType.isDirty(AbstractType.java:87)
at org.hibernate.type.ComponentType.isDirty(ComponentType.java:279)
at org.hibernate.persister.entity.DirtyHelper.isDirty(DirtyHelper.java:69)
at org.hibernate.persister.entity.DirtyHelper.findDirty(DirtyHelper.java:44)
at org.hibernate.persister.entity.AbstractEntityPersister.findDirty(AbstractEntityPersister.java:4525)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.dirtyCheck(DefaultFlushEntityEventListener.java:555)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.isUpdateNecessary(DefaultFlushEntityEventListener.java:211)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:135)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:107)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:214)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:90)
at org.hibernate.event.internal.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:48)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:107)
at org.hibernate.internal.SessionImpl.autoFlushIfRequired(SessionImpl.java:1385)
at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.lambda$new$0(ConcreteSqmSelectQueryPlan.java:100)
at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.withCacheableSqmInterpretation(ConcreteSqmSelectQueryPlan.java:305)
at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.performList(ConcreteSqmSelectQueryPlan.java:246)
at org.hibernate.query.sqm.internal.QuerySqmImpl.doList(QuerySqmImpl.java:546)
at org.hibernate.query.spi.AbstractSelectionQuery.list(AbstractSelectionQuery.java:363)
at org.hibernate.query.sqm.internal.QuerySqmImpl.list(QuerySqmImpl.java:1032)
at org.hibernate.query.Query.getResultList(Query.java:94)
at com.missionessential.dmeapp.db.jpa.AlertJpaRepository.getByTimeRange(AlertJpaRepository.java:64)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:218)
at jdk.proxy2/jdk.proxy2.$Proxy95.getByTimeRange(Unknown Source)
at com.missionessential.dmeapp.db.jpa.AlertsTest.persistSize(AlertsTest.java:78)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at org.junit.vintage.engine.execution.RunnerExecutor.execute(RunnerExecutor.java:42)
at org.junit.vintage.engine.VintageTestEngine.executeAllChildren(VintageTestEngine.java:80)
at org.junit.vintage.engine.VintageTestEngine.execute(VintageTestEngine.java:72)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:147)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:127)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:90)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:55)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:102)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:54)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:95)
at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:91)
at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:60)
at org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:98)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:40)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:529)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:756)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:452)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
Should I use CompositeUserType in this scenario? How to fix this issue?