解决Hibernate AnnotationException:非法尝试映射非集合
1. 引言
当我们在 Hibernate 中映射实体关系时,字段声明中的一个小错误就可能阻止应用程序启动。一个常见的例子是 “org.hibernate.AnnotationException: Illegal attempt to map a non collection as a @OneToMany, @ManyToMany or @CollectionOfElements” 错误。
在本教程中,我们将探究 Hibernate 抛出此异常的原因。接着,我们会用一个简单的映射来复现它,并介绍几种修复方法。
2. 理解异常
Hibernate 期望任何被 @OneToMany、@ManyToMany 或 @ElementCollection 注解的字段都是集合类型的关联。实际上,这意味着我们必须使用 Hibernate 所识别的 集合接口 来声明该字段:
Collection或List:允许重复元素的有序组Set:仅包含唯一元素的组Map:一组键值对,适用于基于键的关联
Hibernate 会用其自身实现来管理这些集合。 当我们持久化 实体 时,Hibernate 会用支持延迟加载和脏检查的代理替换该字段的值。只有当字段类型是 Hibernate 控制的接口时,它才能做到这一点。
因此,当注解用在并非这些接口的字段上时,Hibernate 无法构建代理。所以,它会在启动时快速失败并抛出 AnnotationException。
3. 复现异常
我们用一个经典的父子类映射来复现该问题。首先,定义一个 Comment 实体:
现在,创建一个拥有多条评论的 Post 实体。这里,我们故意用具体的 ArrayList 类型来声明字段:
当 Hibernate 在启动时构建元数据并扫描此映射时,会抛出:org.hibernate.AnnotationException: Illegal attempt to map a non collection as a @OneToMany, @ManyToMany or @CollectionOfElements: com.baeldung.Post.comments
注意,较新的 Hibernate 版本会将同样的消息改为使用 @ElementCollection 而不是旧的 @CollectionOfElements,但原因完全相同。
尽管 ArrayList 在技术上是列表,但 Hibernate 拒绝它,因为它是具体类而不是集合接口。
4. 修复映射
修复方法很简单:我们用集合接口而非实现来声明字段。因此,将类型从 ArrayList 改为 List:
我们应该注意到,我们仍然用 new ArrayList<>() 初始化了字段。这没有问题,因为只有声明字段的类型需要是接口。一旦实体成为受管状态,Hibernate 会将其值替换为自身的实现。
我们可以将同样的规则应用于其他注解。例如,当需要避免重复时,Set 与 @ManyToMany 搭配得很好。@ElementCollection 接受任何标准集合接口。要记住的是,字段的编译时类型必须是接口,而不是我们赋值给它的类。
至于选择哪个接口,当顺序或重复很重要时,List 是常见的默认选项;而当每个元素必须唯一时,Set 则更为合适。只要字段类型保持为接口,映射就始终有效。
5. 一个相关原因:映射单个引用
当我们在单值字段上放置集合注解时,也会遇到同样的异常。 例如,一条评论属于唯一的一篇文章,所以我们可能会错误地写成:
这种情况下,post 持有单个 Post 对象,而不是集合,因此 Hibernate 会抛出相同的 AnnotationException。然而,在这种情况下,换成接口类型也无济于事,因为关系本身就被错误地建模了。
相反,我们应该选择与基数匹配的注解。考虑到多条评论映射到一篇文章,@ManyToOne 才是正确的选择:
类似地,如果字段表示一对一关联,我们应该选择 @OneToOne。
这也将我们看到的两种修复方法联系在一起。在双向关系中,@ManyToOne 端是关联的所有者,而 Post 实体通过 @OneToMany(mappedBy = "post") 映射反向端。这里,@OneToMany 字段仍然是 List,而 @ManyToOne 字段仍然是单个引用。这样,每个注解都与它分配的类型匹配,Hibernate 就能毫无抱怨地构建映射。
6. 结论
在本文中,我们研究了 Hibernate 的 AnnotationException,它警告我们映射了不正确的类型。
问题的根源在于注解与字段类型之间的不匹配。当我们使用 @OneToMany、@ManyToMany 或 @ElementCollection 时,字段必须是集合接口,如 List 或 Set,而不能是具体类(如 ArrayList)。当字段实际持有单个引用时,我们应该改用 @ManyToOne 或 @OneToOne。
通过将注解与字段的基数匹配,并始终通过接口声明集合,我们可以保持映射有效,并让 Hibernate 来处理它们的管理。
与往常一样,代码可在 Github 上找到。
这篇文章 Solving org.hibernate.AnnotationException: Illegal Attempt to Map a Non Collection 首次出现在 Baeldung 上。
Like on Facebook • Pin it! • Post to X.com • Subscribe by email • Subscribe by RSS • View Comments • Follow Comments via RSS