Hibernate学习笔记 多表映射

﹏ヽ暗。殇╰゛Y 2022-07-13 12:07 249阅读 0赞

前面说了Hibernate的单表映射,由于是实体类和数据表之间一对一的映射,所以比较简单。现在就来说说多表映射,这需要涉及到多个实体类和数据表之间的关系。因此稍微复杂一点。

建立实体类

我建立了两个实体类,一个作者类,一个文章类,其他方法都忽略了,就留下了注解。作者类如下:

  1. @Entity
  2. public class Author {
  3. @Id
  4. @GeneratedValue(strategy = GenerationType.IDENTITY)
  5. private int id;
  6. @NaturalId()
  7. private String username;
  8. @Column(nullable = false)
  9. private String password;
  10. @Column
  11. private String nickname;
  12. @Column(name = "register_time")
  13. @Temporal(TemporalType.DATE)
  14. private Date registerTime;
  15. }

文章类如下:

  1. @Entity
  2. public class Article {
  3. @Id
  4. @GeneratedValue(strategy = GenerationType.IDENTITY)
  5. private int id;
  6. @Column
  7. private String title;
  8. @Column
  9. @Lob
  10. private String content;
  11. @ManyToOne(targetEntity = Author.class)
  12. @JoinColumn(foreignKey = @ForeignKey(name = "FK_AUTHOR_ID"))
  13. private Author author;
  14. @Column(name = "create_time")
  15. @Temporal(TemporalType.TIMESTAMP)
  16. private Date createTime;
  17. @Column(name = "modify_time")
  18. @Temporal(TemporalType.TIMESTAMP)
  19. private Date modifyTime;
  20. }

文章实体类用到了另一个注解Lob,表示SQL中的大对象。Hibernate会自动根据所注解的对象生成合适的SQL语句,如果Lob注解到了字符串上,Hibernate会生成CLOB类型对象;如果注解到了byte[]数组之类的上面,就会生成BLOB类型的对象。

ManyToOne

上面的Article类中应用了一个ManyToOne注解。一个作者可以写很多篇文章,所以文章和作者的关系正是多对一。这个注解表示的也正是这种外键关系。如果我们查看一下MySQL的表生成语句,会发现Article表是这个样子的:

  1. CREATE TABLE `article` ( `id` int(11) NOT NULL AUTO_INCREMENT, `content` longtext, `create_time` datetime DEFAULT NULL, `modify_time` datetime DEFAULT NULL, `title` varchar(255) DEFAULT NULL, `author_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `FK_AUTHOR_ID` (`author_id`), CONSTRAINT `FK_AUTHOR_ID` FOREIGN KEY (`author_id`) REFERENCES `author` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8

上面的文章实体类还应用了另一个注解JoinColumn,这个注解用来控制数据库外键的行为。我这里是用来修改外键约束的名称。其他的使用方法需要查看官方文档。

  1. @JoinColumn(foreignKey = @ForeignKey(name = "FK_AUTHOR_ID"))

这样,一个基本的外键映射就建立好了。但是有时候还不能满足需求,这样的话就需要双向的映射了。

单向的OneToMany

在介绍这种映射之前,我们先建立一个评论实体类,多余的内容省略了。

  1. @Entity
  2. public class Comment {
  3. @Id
  4. @GeneratedValue(strategy = GenerationType.IDENTITY)
  5. private int id;
  6. @ManyToOne
  7. private Author author;
  8. @Lob
  9. private String content;
  10. @Column(name = "create_time")
  11. @Temporal(TemporalType.TIMESTAMP)
  12. private Date createTime;
  13. }

建立好评论类之后,我们就可以建立文章类和评论类之间的多对一关系了。可以注意到我在author字段上应用了ManyToOne注解。本来也应该有一个应用ManyToOne注解的article字段来表示评论所属的文章,但是为了演示单向的OneToMany映射,所以我故意不添加这个文章属性。

有的同学可能想到了,多对一注解应用到字段上没有问题。但是一对多注解,如何应用到普通字段上呢。所以,这里需要一个集合。我们在文章实体类中添加如下一段,对应的Getter省略了:

  1. @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
  2. private List<Comment> comments = new ArrayList<>();

这样就建立了评论集合和评论实体类的单向一对多映射。对于单向一对多映射,Hibernate会建立一个映射表,比如这里就会建立一个article_comment表,表的内容就是两张表的主键。orphanRemoval指定当出现孤立数据时是否删除孤立数据。cascade指定了级联操作的类型,这里使用ALL允许所有操作。指定了ALL之后,我们就可以通过直接在Article类中添加评论,级联地更新comment表。CascadeType还有另外几个值,这里就不再细述了。

单向的一对多映射并不高效,如果删除了某文章的某评论,Hibernate进行的操作是这样:首先删除关联表中该文章关联的所有评论,然后再将其他评论添加回关联表中,最后,根据orphanRemoval决定是否删除评论表中孤立的评论。

双向的OneToMany

理解了单向OneToMany之后,很容易就能理解双向OneToMany了。两个实体类一边需要使用ManyToOne注解,另外一边的集合类使用OneToMany注解。需要注意在双向注解中,OneToMany需要额外一个参数,mappedBy,指定ManyToOne注解那一边的属性名,这样Hibernate才会明白这是一个双向注解。

  1. @OneToMany(mappedBy = "author", cascade = CascadeType.ALL, orphanRemoval = true)
  2. private List<Comment> comments = new ArrayList<>();

定义了双向注解之后,Hibernate不会再生成一个映射表,而是直接控制外键。因此比单向映射更高效。

OneToOne

一对一映射也是一种常用的映射关系。比方说我们要实现用户头像的功能。由于用户上传的头像文件大小可大可小,因此不能放在用户表中。这时候就需要一个头像表,这个表中每个头像和用户表中的每个用户就是一一对应的关系。

一对一关系也存在单向和双向的。首先我们看看单向映射。首先需要一个头像实体类:

  1. @Entity
  2. public class Avatar {
  3. @Id
  4. @GeneratedValue(strategy = GenerationType.IDENTITY)
  5. private int id;
  6. @Lob
  7. private byte[] avatar;
  8. public int getId() {
  9. return id;
  10. }
  11. public void setId(int id) {
  12. this.id = id;
  13. }
  14. public byte[] getAvatar() {
  15. return avatar;
  16. }
  17. public void setAvatar(byte[] avatar) {
  18. this.avatar = avatar;
  19. }
  20. @Override
  21. public boolean equals(Object o) {
  22. if (this == o) return true;
  23. if (o == null || getClass() != o.getClass()) return false;
  24. Avatar avatar1 = (Avatar) o;
  25. return id == avatar1.id &&
  26. Arrays.equals(avatar, avatar1.avatar);
  27. }
  28. @Override
  29. public int hashCode() {
  30. return Objects.hash(id, avatar);
  31. }
  32. @Override
  33. public String toString() {
  34. return "Avatar{" +
  35. "id=" + id +
  36. '}';
  37. }
  38. }

然后需要更新Author类:

  1. @OneToOne
  2. private Avatar avatar;

这样单向一对一映射就完成了。使用这种方法建立的底层数据库,和使用ManyToOne是一样的。看一下数据表,就会发现这样建立出来的用户表存在一个外键,指向头像表。但是仔细考虑一下两张表的关系,头像是依附于用户存在的,所以外键应该是头像表的,指向用户表。这样就需要使用双向一对一映射。

首先需要更新头像类,添加一对一映射。

  1. @OneToOne
  2. private Author author;

作者类同样需要更新,一旦使用双向映射,就需要添加mappedBy属性。这里添加cascade以便可以级联更新头像表。

  1. @OneToOne(mappedBy = "author", cascade = CascadeType.ALL, orphanRemoval = true)
  2. private Avatar avatar;

如果查看生成的数据表的话,就会发现,这次外键生成在了头像表一边。

ManyToMany

有了一对一、一对多、多对一映射的概念之后,多对多就很容易理解了。以上面我们建立的作者、文章、评论实体类为例,我们如果添加一个标签类,一个标签下可以存在多篇文章;一篇文章也可以有多个标签,这样就实现了一个多对多映射。要实现多对多映射,必须要有一个关联表。另外需要注意的是,使用多对多映射时,不能把级联属性指定为CascadeType.DELETE或者CascadeType.ALL,我们应该不希望在删除一篇文章的标签时,同时将该标签下的所有文章都删除吧?

另外Hibernate的多对多映射存在一个问题,就是和单向一对多一样,删除一个关联,需要先删除所有关联,然后将其他的重新插入。所以,一般情况下我们不能使用多对多映射,而是建立一个中间类,然后使用双向一对多映射将要关联的类分别和中间类映射。这就比较麻烦了,所以我就不写了。

发表评论

表情:
评论列表 (有 0 条评论,249人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Hibernate关系映射之一对映射

    一、基本概述 在表中的一对多,是使用外键关联,通过一张表的一个键另一个表的外键来建立一多关系;而在类中表示为一个类中有一个集合属性包含对方类的很多对象,而在另一个类中,只包