Hibernate使用JsonBinaryType无变更却更新实体问题咨询
解决Hibernate乐观锁下JSON属性误判脏数据导致版本号无故提升的问题
问题场景
- 技术栈:Spring Boot 3.2、Hibernate 6.3、hypersistence-utils-hibernate-63:3.7.0
- 实体字段定义:
@Type(JsonBinaryType::class) @Column(name = "errors") var verrors: Map<String, List<Error>> = emptyMap() - 异常现象:仅在**session-in-view(视图内会话)**场景下,只要
verrors属性有值,实体的乐观锁版本号就会被自动提升;直接调用服务层方法则不会触发此问题 - 预期效果:未实际修改属性内容时,实体不应触发更新(类似事务设为只读时的表现)
根因分析
Hibernate的脏检查机制在session-in-view模式下,会将从数据库加载的纯字符串格式JSON数据,与会话中反序列化生成的基于Error对象的Map-List内存结构做对比。由于两者的对象引用、内存存储形式不同,Hibernate误判该属性为脏数据,进而触发版本号更新操作。
可行解决方案
1. 自定义JSON类型,修正equals逻辑
扩展JsonBinaryType,重写isEqual方法,通过序列化后的JSON字符串对比内容,而非直接对比对象实例:
class CustomJsonBinaryType : JsonBinaryType() { private val objectMapper = ObjectMapper() override fun isEqual(x: Any?, y: Any?): Boolean { if (x === y) return true if (x == null || y == null) return false return try { objectMapper.writeValueAsString(x) == objectMapper.writeValueAsString(y) } catch (e: JsonProcessingException) { false } } }
修改实体字段的类型注解:
@Type(CustomJsonBinaryType::class) @Column(name = "errors") var verrors: Map<String, List<Error>> = emptyMap()
2. 手动脱离会话,避免自动脏检查
在控制器或服务层加载实体后,调用entityManager.detach()将实体从会话中移除,阻止Hibernate后续的脏检查:
@Autowired private lateinit var entityManager: EntityManager fun getEntity(id: Long): Entity { val entity = entityManager.find(Entity::class.java, id) entityManager.detach(entity) return entity }
注意:此方法仅适用于后续无需对该实体做持久化修改的场景。
3. 关闭全局session-in-view,按需控制会话
通过Spring配置关闭全局的session-in-view,避免会话长时间打开导致不必要的脏检查:
spring.jpa.open-in-view=false
之后在需要会话支持的方法上,通过@Transactional注解手动开启事务,控制会话范围。
4. 标记字段为不可变(适用于只读属性)
如果verrors属性在业务中不会被修改,可添加@Immutable注解告知Hibernate跳过该字段的脏检查:
@Type(JsonBinaryType::class) @Column(name = "errors") @Immutable var verrors: Map<String, List<Error>> = emptyMap()
内容的提问来源于stack exchange,提问作者Strinder
相关产品推荐
相关产品推荐

