红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 专栏
  3. Spring Boot 学习笔记
  4. 正文

从零开始 Spring Boot 60:一个实体映射到多个表

2023年7月4日 1777点热度 0人点赞 0条评论

spring boot

图源:简书 (jianshu.com)

在之前的文章中我们讨论了 JPA 中的一对一关系,实际上存在一种特殊的一对一关系,即将一个实体映射到多张表,本文会讨论这种关系。

我之前提过,有时候会因为性能上的考量将一张表拆分成多张表,虽然拆分后也可以用一对一关系来表示和实现,但这样并不是特别合适,因为一对一关系中一边的关系是可以为null的,比如说一个学生对应一个电子邮件地址,也可能有的学生没有电子邮件地址,此时email表可能就没有一条对应的数据。但如果是从同一张表上拆分出的两张表,必然存在一对一的关系,即使另一张表中的数据都是null字段。

下面我们将说明如何实现这种关系。

示例

假设我们有一张表,保存学生的所有信息,这个表可以用下面的实体类表示:

@Entity
@Table(name = "student")
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
​
    @NotNull
    @NotBlank
    @Length(max = 45)
    private String name;
    @NotNull
    private Boolean loveMusic = false;
    @NotNull
    private Boolean loveDraw = false;
    private String address;
    private String email;
}

Hibernate 生成的表结构如下:

CREATE TABLE `student` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(45) NOT NULL,
  `address` varchar(255) DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL,
  `love_draw` bit(1) NOT NULL,
  `love_music` bit(1) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

假如这个表有性能瓶颈,我们需要将其中的爱好相关字段拆分出来,但我们又不希望改变对这个实体的使用习惯,即我们依然希望通过这个实体的属性访问相关数据。

@SecondaryTable

为了实现这个目的,可以按下面的方式修改实体类:

@Entity(name = "Student2")
@Table(name = "student2")
@SecondaryTable(name = "student_hobbies2",
        pkJoinColumns = @PrimaryKeyJoinColumn(name = "student_id"))
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
​
    @NotNull
    @NotBlank
    @Length(max = 45)
    private String name;
    @NotNull
    @Column(table = "student_hobbies2")
    private Boolean loveMusic = false;
    @NotNull
    @Column(table = "student_hobbies2")
    private Boolean loveDraw = false;
    private String address;
    private String email;
}

这里用@SecondaryTable注解来表示要拆分出去的副表,name表示副表的名称,pkJoinColumns表示用于关联两张表的主键(外键)。

此外,还需要将拆分出去的字段相关的属性的@Column注解标注为副表字段(table=xxx)。

现在 Hibernate 生成的表结构为:

CREATE TABLE `student2` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `address` varchar(255) DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL,
  `name` varchar(45) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
​
CREATE TABLE `student_hobbies2` (
  `love_draw` bit(1) NOT NULL,
  `love_music` bit(1) NOT NULL,
  `student_id` bigint NOT NULL,
  PRIMARY KEY (`student_id`),
  CONSTRAINT `FKp37nmekoeubhhvwpsw0euvps3` FOREIGN KEY (`student_id`) REFERENCES `student2` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

但对于实体的调用方来说,不会有任何影响,依然可以用之前的方式使用实体类,甚至不会“察觉”这个实体中的字段已经拆分成了两张表,这就是实体抽象层的好处。

@SecondaryTables

可以用多个@SecondaryTable让一个实体对应多张表:

// ...
@SecondaryTable(name = "student_hobbies3",
        pkJoinColumns = @PrimaryKeyJoinColumn(name = "student_id"))
@SecondaryTable(name = "student_detail3",
        pkJoinColumns = @PrimaryKeyJoinColumn(name = "student_id"))
public class Student {
    // ...
    @NotNull
    @Column(table = "student_hobbies3")
    private Boolean loveMusic = false;
    @NotNull
    @Builder.Default
    @Column(table = "student_hobbies3")
    private Boolean loveDraw = false;
    @Column(table = "student_detail3")
    private String address;
    @Column(table = "student_detail3")
    private String email;
}

JDK8 之前的版本不支持重复注解,可以这样:

@SecondaryTables({
        @SecondaryTable(name = "student_hobbies3",
                pkJoinColumns = @PrimaryKeyJoinColumn(name = "student_id")),
        @SecondaryTable(name = "student_detail3",
                pkJoinColumns = @PrimaryKeyJoinColumn(name = "student_id"))})
public class Student {
    // ...
}

@Embeddable 和 @Embedded

之前我们介绍过@Embeddable和@Embedded的用途,映射到副表的属性同样可以用类似的方式进行分组:

@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity(name = "Student4")
@Table(name = "student4")
@SecondaryTable(name = "student_hobbies4",
        pkJoinColumns = @PrimaryKeyJoinColumn(name = "student_id"))
@SecondaryTable(name = "student_detail4",
        pkJoinColumns = @PrimaryKeyJoinColumn(name = "student_id"))
public class Student {
    @ToString
    @Embeddable
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Hobbies {
        @NotNull
        @Builder.Default
        @Column(table = "student_hobbies4")
        private Boolean loveMusic = false;
        @NotNull
        @Builder.Default
        @Column(table = "student_hobbies4")
        private Boolean loveDraw = false;
    }
​
    @ToString
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    @Embeddable
    public static class Details {
        @Column(table = "student_detail4")
        private String address;
        @Column(table = "student_detail4")
        private String email;
    }
​
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
​
    @NotNull
    @NotBlank
    @Length(max = 45)
    @Column(unique = true)
    private String name;
    @NotNull
    @Embedded
    @Builder.Default
    private Hobbies hobbies = new Hobbies();
    @NotNull
    @Embedded
    @Builder.Default
    private Details details = new Details();
}

如果@Embeddable标记的类需要重用,也可以使用@AttributeOverrides注解,具体可以看这篇文章。

The End,谢谢阅读。

本文的完整示例可以从这里获取。

参考资料

  • 从零开始 Spring Boot 56:JPA中的一对一关系 - 红茶的个人站点 (icexmoon.cn)

  • Mapping a Single Entity to Multiple Tables in JPA

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: @SecondaryTable hibernate spring
最后更新:2023年7月4日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇
下一篇 >

文章评论

取消回复

*

code

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号