Ohhnews

分类导航

$ cd ..
Baeldung原文

解决Hibernate AnnotationException:非法尝试映射非集合

#hibernate#java异常#orm映射#集合注解#实体关系

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 所识别的 集合接口 来声明该字段:

  • CollectionList:允许重复元素的有序组
  • Set:仅包含唯一元素的组
  • Map:一组键值对,适用于基于键的关联

Hibernate 会用其自身实现来管理这些集合。 当我们持久化 实体 时,Hibernate 会用支持延迟加载和脏检查的代理替换该字段的值。只有当字段类型是 Hibernate 控制的接口时,它才能做到这一点。

因此,当注解用在并非这些接口的字段上时,Hibernate 无法构建代理。所以,它会在启动时快速失败并抛出 AnnotationException

3. 复现异常

我们用一个经典的父子类映射来复现该问题。首先,定义一个 Comment 实体:

$ java
@Entity
public class Comment {
    @Id
    @GeneratedValue
    private Long id;
    private String text;
    // getters and setters
}

现在,创建一个拥有多条评论的 Post 实体。这里,我们故意用具体的 ArrayList 类型来声明字段:

$ java
@Entity
public class Post {
    @Id
    @GeneratedValue
    private Long id;
    @OneToMany
    private ArrayList<Comment> comments = new ArrayList<>();
    // getters and setters
}

当 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

$ java
@Entity
public class Post {
    @Id
    @GeneratedValue
    private Long id;
    @OneToMany
    private List<Comment> comments = new ArrayList<>();
    // getters and setters
}

我们应该注意到,我们仍然用 new ArrayList<>() 初始化了字段。这没有问题,因为只有声明字段的类型需要是接口。一旦实体成为受管状态,Hibernate 会将其值替换为自身的实现。

我们可以将同样的规则应用于其他注解。例如,当需要避免重复时,Set@ManyToMany 搭配得很好。@ElementCollection 接受任何标准集合接口。要记住的是,字段的编译时类型必须是接口,而不是我们赋值给它的类。

至于选择哪个接口,当顺序或重复很重要时,List 是常见的默认选项;而当每个元素必须唯一时,Set 则更为合适。只要字段类型保持为接口,映射就始终有效。

5. 一个相关原因:映射单个引用

当我们在单值字段上放置集合注解时,也会遇到同样的异常。 例如,一条评论属于唯一的一篇文章,所以我们可能会错误地写成:

$ java
@Entity
public class Comment {
    @Id
    @GeneratedValue
    private Long id;
    @OneToMany
    private Post post;
}

这种情况下,post 持有单个 Post 对象,而不是集合,因此 Hibernate 会抛出相同的 AnnotationException。然而,在这种情况下,换成接口类型也无济于事,因为关系本身就被错误地建模了。

相反,我们应该选择与基数匹配的注解。考虑到多条评论映射到一篇文章,@ManyToOne 才是正确的选择:

$ java
@Entity
public class Comment {
    @Id
    @GeneratedValue
    private Long id;
    @ManyToOne
    private Post post;
}

类似地,如果字段表示一对一关联,我们应该选择 @OneToOne

这也将我们看到的两种修复方法联系在一起。在双向关系中,@ManyToOne 端是关联的所有者,而 Post 实体通过 @OneToMany(mappedBy = "post") 映射反向端。这里,@OneToMany 字段仍然是 List,而 @ManyToOne 字段仍然是单个引用。这样,每个注解都与它分配的类型匹配,Hibernate 就能毫无抱怨地构建映射。

6. 结论

在本文中,我们研究了 Hibernate 的 AnnotationException,它警告我们映射了不正确的类型。

问题的根源在于注解与字段类型之间的不匹配。当我们使用 @OneToMany@ManyToMany@ElementCollection 时,字段必须是集合接口,如 ListSet,而不能是具体类(如 ArrayList)。当字段实际持有单个引用时,我们应该改用 @ManyToOne@OneToOne

通过将注解与字段的基数匹配,并始终通过接口声明集合,我们可以保持映射有效,并让 Hibernate 来处理它们的管理。

与往常一样,代码可在 Github 上找到。

这篇文章 Solving org.hibernate.AnnotationException: Illegal Attempt to Map a Non Collection 首次出现在 Baeldung 上。

Like on FacebookPin it!Post to X.comSubscribe by emailSubscribe by RSSView CommentsFollow Comments via RSS