Hibernate 解决 Metamodel 属性类型错误问题

因为项目上使用了 Hibernate Metamodel,而且自定义映射了数据库的 JSON 字段类型,导致生成的 Metamodel 属性类型有误。虽然并没有用映射的这些属性做 CriteriaQuery,所以其实在项目的使用上没有影响,但是启动时还是会报一个 ERROR 日志,所以寻找一个能够根治的解决办法。

1
ERROR org.hibernate.metamodel.internal.MetadataContext [MetadataContext.java:491] - HHH015007: Illegal argument on static metamodel field injection : Foo_#bars; expected type : org.hibernate.metamodel.model.domain.internal.SingularAttributeImpl; encountered type : javax.persistence.metamodel.MapAttribute

一、定位问题

首先是背景信息: 因为前端需要在数据库存储 JSON 字段,而且现在版本的 MySQL 天然支持 JSON ,所以建表时采用了 JSON 作为字段类型。然后在 JPA 实体层面,引入了 hibernate-types 依赖来增加 Hibernate 对 JSON 的支持,字段属性增加了 @Type(type = "json") 注解,本身定义为了 Map 类型。

其实在 Hibernate 对 JSON 处理这件事情上,传统方案是通过自定义 Convert 来实现。或者可能不是 JSON Object 而是 JSON Array,但无论是哪种情况,均会遇到同样的问题。

通过报错信息,对代码进行 DEBUG 后得知,Hibernate 在启动时,会根据实体属性是否存在与其他实体的关系,生成不同的 Attribute。比如 OneToMany 一对多关系就会生成 ListAttributeSetAttribute,没有关系的属性都是 SingularAttribute。而启动时通过反射,会 set Metamodel 的对应属性,我们可以在 target 下面看见,Metamodel 的类型为 MapAttribute,所以我们可以确定,问题就出在了生成 Metamodel 的时候。具体是不是直接拿实体定义的数据类型来生成的我没有去看源码,不过可以肯定的是它在判断类型的时候缺少额外的判断。

二、解决问题

同样作为一个共性问题,在 Goolge 进行搜索后,定位到了一条仍然是 Hibernate 官方 Bug 追踪的链接 Incorrect metamodel for basic collections。简单的来说,就是当前已经在 5.5.0.Alpha1 的版本中得到了解决,不过如果你认真看了下面整个的 Activity 记录,就会发现仍然是存在一个小插曲。

三、插曲

这个问题是在 2018 年的 3 月份被提出来的,而 5.5.0.Alpha1 已经是 2021 年 8 月份的事情了,照理说这么一个小问题不会需要三年多的时间来处理,而这个疑问的答案就在下面的讨论之中。

简单来说,这个问题在当月就被解决掉了,伴随着 5.2.17 的版本发布,这个问题理论上来说就不应该再存在了。然而事与愿违,在 5.2.17 版本发布之后,很快就有人提出了新的问题。在下面的 Comment 中有记录,同时这个人也单独提了一个 issue NPE for Criteria query containing fetch join as a regression of HHH-12338,即这次的改动造成了 NPE。然后可以看到在另一个维护者与之前 Vlad Mihalcea 的一番讨论之后,他们决定恢复这次的改动。

但是回退了就意味着问题又回来了,上面提到的 Vlad Mihalcea 从红帽离职,不再维护 Hibernate 还弄了一个新的项目就是上面提到的 hibernate-types。所以其他人在遇到相同的问题时,有部分和我们一样是采用的 hibernate-types 方案,自然而然有人把问题又丢给了 Vlad Mihalcea,不过是在 hibernate-types 项目。

于是 Vlad Mihalcea 在 Github 提了新的 PR,见 HHH-12338 - Incorrect metamodel for basic collections。不过很不幸的是,并没有后续。

直到今年的 7 月,又一位开发者忍无可忍决定解决这个问题,提了新的 PR 出来,见 HHH-14724 Test-case for metamodel compile error with converters and validation。这次终于引起了重视,并在一番努力之后,终于得到了合并,问题就此解决。

题外话,这件事情,是不是跟前阵子遇到的 Hibernate 配置自动清除二级缓存之集合缓存 里面的插曲有点像?笑。

四、亲力亲为

虽说这件事情现在看来终于告一段落了,不过由于时今年 9 月份才最终解决,虽然代码已经被合并到了各个版本的分支中,但 5.4 已经半年没有发过新的版本了。虽说 5.5 和 5.6 的版本已经没问题了,但由于项目框架的问题,并不能直接升级 Hibernate 的版本,而且本来 5.5 的版本就存在很大改动,现有架构并不能保证完全兼容(比如还是上面提到的 hibernate-types 就需要引入另外一个包),所以最后没有办法,只能决定采用重打包的方式自己对源码进行更新。

剩下的事情就不再赘述了,大概流程就是下载 Hibernate 源码,找到 5.4.32.Final 的 tag,在此基础上手动把修复代码的变更拷贝过来,然后改个版本号,发布到私仓,再更改当前项目依赖的版本号,最后成功解决。