Hibernate3序列化存储的Blob在Hibernate5中无法反序列化问题求解
问题根因
Hibernate 3版本的org.hibernate.proxy.pojo.javassist.SerializableProxy类中,identifierGetterMethodClass字段存储的是类全限定名的字符串类型值,到Hibernate 5版本该字段调整为直接存储Class类型,二者序列化结构不兼容,导致旧版本序列化写入的Blob数据在新版本反序列化时抛出类型转换错误。
原有实现代码
Hibernate 3 Blob写入代码
ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(byteOut); out.writeObject(object); byte[] voBytes = byteOut.toByteArray(); Blob voBlob = Hibernate.createBlob(voBytes);
Hibernate 5 反序列化读取代码
try( ObjectInputStream in = new ObjectInputStream(blob.getBinaryStream())) { obj = in.readObject(); }
报错信息
java.lang.ClassCastException: cannot assign instance of java.lang.String to field org.hibernate.proxy.pojo.javassist.SerializableProxy.identifierGetterMethodClass of type java.lang.Class in instance of org.hibernate.proxy.pojo.javassist.SerializableProxy
(堆栈信息省略)
解决方案
方案一:自定义ObjectInputStream做兼容处理(无需修改历史数据,推荐)
继承ObjectInputStream重写对象解析逻辑,在反序列化SerializableProxy实例时,手动把字符串类型的类名转换为对应Class对象,适配新版本的字段类型要求,代码示例如下:
import org.hibernate.proxy.pojo.javassist.SerializableProxy; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.lang.reflect.Field; public class CompatibleHibernateObjectInputStream extends ObjectInputStream { public CompatibleHibernateObjectInputStream(InputStream in) throws IOException { super(in); enableResolveObject(true); } @Override protected Object resolveObject(Object obj) throws IOException { if (obj instanceof SerializableProxy) { try { Field field = SerializableProxy.class.getDeclaredField("identifierGetterMethodClass"); field.setAccessible(true); Object value = field.get(obj); // 旧数据中该字段为字符串类型,转换为Class if (value instanceof String) { Class<?> clazz = Class.forName((String) value); field.set(obj, clazz); } } catch (Exception e) { throw new IOException("兼容处理Hibernate代理序列化对象失败", e); } } return super.resolveObject(obj); } }
替换原有反序列化逻辑的输入流即可:
try( ObjectInputStream in = new CompatibleHibernateObjectInputStream(blob.getBinaryStream())) { obj = in.readObject(); }
方案二:历史数据全量迁移
如果后续不想保留兼容逻辑,可以一次性完成历史数据转换:
- 临时使用上述兼容输入流读取库中所有旧的Blob数据
- 用Hibernate 5原生序列化逻辑重新序列化反序列化后的对象,生成新的Blob覆盖原有字段
- 迁移完成后即可直接使用原生
ObjectInputStream读取数据,无需额外兼容处理
优化建议
后续持久化序列化对象时,建议先把Hibernate代理对象转换为实际的POJO实例再序列化,避免后续Hibernate版本迭代再次出现序列化不兼容问题。
内容的提问来源于stack exchange,提问作者shwetha

