Spring Boot3.2+Hibernate6下Spring Cache缓存/反序列化Hibernate代理方案
问题现象
升级至Spring Boot 3.2.0后,Spring Cache出现反序列化异常:缓存的JPA实体X包含即时加载的ManyToOne关联实体O,O又关联懒加载实体P。P被序列化为org.hibernate.proxy.pojo.bytebuddy.SerializableProxy,从缓存读取时抛出java.lang.IllegalStateException: Could not identify any active SessionFactory having UUID ...异常,但该逻辑在Spring Boot 2.7/Hibernate 5.6环境下运行正常。
原因分析
Hibernate 6对SerializableProxy类做了修改:新增sessionFactoryUuid成员变量,其readResolve方法在反序列化时会通过该UUID查找对应的SessionFactory。但缓存中保存的代理对象携带的UUID,与当前运行环境的SessionFactory UUID无法匹配,导致查找失败。而Hibernate 5.6的readResolve方法并未依赖SessionFactory UUID来完成反序列化。
新旧版本readResolve代码对比
旧代码(Spring Boot 2.7/Hibernate 5.6):
private Object readResolve() { BytecodeProvider bytecodeProvider = Environment.getBytecodeProvider(); if ( !( bytecodeProvider instanceof BytecodeProviderImpl ) ) { throw new IllegalStateException( "The bytecode provider is not ByteBuddy, unable to deserialize a ByteBuddy proxy." ); } HibernateProxy proxy = ( (BytecodeProviderImpl) bytecodeProvider ).getByteBuddyProxyHelper().deserializeProxy( this ); afterDeserialization( (ByteBuddyInterceptor) proxy.getHibernateLazyInitializer() ); return proxy; }
新代码(Spring Boot 3.2/Hibernate 6.4.1):
private Object readResolve() { final SessionFactoryImplementor sessionFactory = retrieveMatchingSessionFactory( this.sessionFactoryUuid, this.sessionFactoryName ); BytecodeProviderImpl byteBuddyBytecodeProvider = retrieveByteBuddyBytecodeProvider( sessionFactory ); HibernateProxy proxy = byteBuddyBytecodeProvider.getByteBuddyProxyHelper().deserializeProxy( this ); afterDeserialization( (ByteBuddyInterceptor) proxy.getHibernateLazyInitializer() ); return proxy; }
解决方案
临时方案(不推荐)
缓存前手动取消实体代理(如调用Hibernate.unproxy()),但该操作会破坏懒加载设计,强制加载关联实体,额外消耗系统资源,仅适合临时应急。
最优配置方案
通过配置Hibernate SessionFactory名称并禁用JNDI查找,让反序列化逻辑通过名称匹配SessionFactory,替代UUID匹配:
在application.properties中添加以下配置:
hibernate.session_factory_name=custom-hibernate-session hibernate.session_factory_name_is_jndi=false
或在application.yml中配置:
hibernate: session_factory_name: custom-hibernate-session session_factory_name_is_jndi: false
最佳实践
- 优先缓存DTO而非JPA实体:将业务数据转换为DTO(数据传输对象)后再缓存,完全规避懒加载代理的序列化/反序列化问题,同时符合领域模型与数据传输分离的设计原则。
- 控制JPA实体序列化行为:若必须缓存JPA实体,可使用Jackson的Hibernate 6模块(
jackson-datatype-hibernate6),配置序列化时自动取消代理或忽略未加载的关联字段。 - 分布式环境下保持SessionFactory名称一致:在多节点部署场景中,确保所有节点的
hibernate.session_factory_name配置相同,避免因名称不匹配导致反序列化失败。
内容的提问来源于stack exchange,提问作者Yury K.

