从零开始 Spring Boot 63:Hibernate 继承映射

news2025/1/22 16:47:34

从零开始 Spring Boot 63:Hibernate 继承映射

spring boot

图源:简书 (jianshu.com)

关系型数据库设计中是不存在继承概念的,但实体类可以用继承来组织代码结构,所以需要用一种方式将实体类的继承结构映射到表结构。

本文将介绍几种在 JPA(Hibernate)中映射实体类继承层次的方式。

@MappedSuperclass

第一种方式是用@MappedSuperclass标记超类(Super Class),超类并不对应任何表结构,而是体现在子类对应的表中都拥有超类的字段(每个子类对应一张表)。

@ToString
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@MappedSuperclass
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotNull
    @NotBlank
    @Length(max = 45)
    @Column(unique = true)
    private String name;

    public Person(String name) {
        this.name = name;
    }
}

@ToString(callSuper = true)
@Getter
@Setter
@NoArgsConstructor
@Entity(name = "student")
public class Student extends Person {
    @Min(0)
    @Max(100)
    @NotNull
    private Integer averageScore;

    public Student(String name, Integer averageScore) {
        super(name);
        this.averageScore = averageScore;
    }
}

@ToString(callSuper = true)
@NoArgsConstructor
@Getter
@Setter
@Entity
@Table(name = "teacher")
public class Teacher extends Person {
    public enum Course {
        MATH, PHYSICS, CHEMISTRY, MUSIC, DRAW
    }

    public Teacher(String name, Course course) {
        super(name);
        this.course = course;
    }

    @Enumerated(EnumType.STRING)
    private Course course;
}

生成的表结构:

CREATE TABLE `teacher` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(45) NOT NULL,
  `course` enum('CHEMISTRY','DRAW','MATH','MUSIC','PHYSICS') DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `UK_5syf9tb34xn2g3cmjekoybhet` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

CREATE TABLE `student` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(45) NOT NULL,
  `average_score` int NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `UK_7pb8owoegbhhcrpopw4o1ykcr` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

单表

通过这种方式可以将所有的子类都映射到同一张表:

// ...
@Entity(name = "Person2")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Table(name = "person2")
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
	// ...
}

@Entity(name = "Student2")
public class Student extends Person {
	// ...
}

@Entity(name = "Teacher2")
public class Teacher extends Person{
	// ...
}

生成的表结构:

CREATE TABLE `person2` (
  `dtype` varchar(31) NOT NULL,
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(45) NOT NULL,
  `average_score` int DEFAULT NULL,
  `course` tinyint DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

这里的dtype字段是 Hibernate 自动生成的,用于区分不同实体的数据,比如下图的示例数据:

image-20230707104311800

可以看到,如果不是当前类的属性,就用null填充(相应的字段也不会存在NOT NULL约束)。此外,父类(超类)的实例也可以被持久化(保存到数据库)。

鉴别器

用于区分同一张表中的不同实体数据的功能被称作鉴别器(Discriminator),我们可以指定鉴别器对应的表字段的名称、类型,以及不同实体对应的字段值。

@DiscriminatorColumn(name = "type",
        discriminatorType = DiscriminatorType.INTEGER)
@DiscriminatorValue("null")
public class Person {
	// ...
}

@DiscriminatorValue("1")
public class Student extends Person {
	// ...
}

@DiscriminatorValue("2")
public class Teacher extends Person {
    // ...
}

生成的表结构:

CREATE TABLE `person3` (
  `type` int DEFAULT NULL,
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(45) NOT NULL,
  `average_score` int DEFAULT NULL,
  `course` tinyint DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

现在区分实体数据的字段是type,类型是int

@DiscriminatorValue有两个特殊的值:

  • @DiscriminatorValue("null"),对应表中鉴别器字段为null的数据,可以用于映射超类(根)实体。
  • @DiscriminatorValue("not null"),如果数据与任何@DiscriminatorValue都不对应,就会映射到由这个注解标记的实体。

连接的表

可以用多张表连接的方式映射实体的继承关系。

这种方式更符合继承的语义和一般直觉,数据库模型可以表示为:

image-20230707112650976

person表的主键id同时是tercherstudent表的外键,且都是一对一的对应关系。

JPA 中的实体表示:

@Inheritance(strategy = InheritanceType.JOINED)
public class Person {
	// ...
}

public class Student extends Person {
	// ...
}

public class Teacher extends Person {
	// ...
}

生成的表结构:

CREATE TABLE `person4` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(45) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

CREATE TABLE `student4` (
  `average_score` int NOT NULL,
  `id` bigint NOT NULL,
  PRIMARY KEY (`id`),
  CONSTRAINT `FKmbyfbqlr0ebtwwrdxgvguwncj` FOREIGN KEY (`id`) REFERENCES `person4` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

CREATE TABLE `teacher4` (
  `course` tinyint DEFAULT NULL,
  `id` bigint NOT NULL,
  PRIMARY KEY (`id`),
  CONSTRAINT `FKbuvkvpo0bh33c9tcjt7t5oyej` FOREIGN KEY (`id`) REFERENCES `person4` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

默认情况下子类对应的表用id字段作为主键,可以通过@PrimaryKeyJoinColumn注解进行定义:

@PrimaryKeyJoinColumn(name = "person_id")
public class Student extends Person {
	// ...
}

@PrimaryKeyJoinColumn(name = "person_id")
public class Teacher extends Person {
	// ...
}

生成的表结构:

CREATE TABLE `student4` (
  `average_score` int NOT NULL,
  `person_id` bigint NOT NULL,
  PRIMARY KEY (`person_id`),
  CONSTRAINT `FK67c9opl6rxlof46m2wpni45q1` FOREIGN KEY (`person_id`) REFERENCES `person4` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

CREATE TABLE `teacher4` (
  `course` enum('CHEMISTRY','DRAW','MATH','MUSIC','PHYSICS') NOT NULL,
  `person_id` bigint NOT NULL,
  PRIMARY KEY (`person_id`),
  CONSTRAINT `FK2gyqqosqu69ld6t43aiiuyw1a` FOREIGN KEY (`person_id`) REFERENCES `person4` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

每张表对应一个类

这种策略下,每个类都会对应一张表,并包含全部的属性。与@MappedSuperclass不同的是,父类也会对应一张表。

@Entity(name = "Person5")
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Person {
    @Id
    private Long id;
	// ...
}

@Entity(name = "Student5")
public class Student extends Person {
	// ...
}

@Entity(name = "Teacher5")
public class Teacher extends Person {
	// ...
}

在这种情况下,不能依赖 Hibernate 的 identity key generation 生成主键值,而是要自己定义生成策略:

@Component
public class IdGenerator {
    private long id = 0;

    @Synchronized
    long nextValue() {
        id++;
        return id;
    }
}

@TestConfiguration
public class ExampleDataConfig {
    @Autowired
    private IdGenerator idGenerator;

    @Bean
    Student student() {
        Student icexmoon = new Student("icexmoon", 90);
        icexmoon.setId(idGenerator.nextValue());
        return icexmoon;
    }
	// ...
}

生成的表数据类似下面这样:

image-20230707125223018

image-20230707125240352

image-20230707125258176

多态查询

@Inheritance注解定义的实体继承映射可以用多态查询 JPQL:

List<Person> persons = session.createQuery("from Person5", Person.class).getResultList();
persons.forEach(p->{
    System.out.println(p);
});

对超类的查询结果会包含所有的子类:

Person(id=7, name=Tom)
Person(id=8, name=Adam)
Student(super=Person(id=1, name=icexmoon), averageScore=90)
Student(super=Person(id=2, name=lalala), averageScore=95)
Student(super=Person(id=3, name=JackChen), averageScore=85)
Teacher(super=Person(id=4, name=Catherine), course=MATH)
Teacher(super=Person(id=5, name=Tina), course=MUSIC)
Teacher(super=Person(id=6, name=LiLei), course=CHEMISTRY)

@MappedSuperclass定义的继承映射在查询时有所不同:

List<Person> persons = session.createQuery("from com.example.ineritancemapping.v1.Person", Person.class).getResultList();
persons.forEach(p->{
    System.out.println(p);
});

JPQL 中的基类使用了完全限定名称(包含了完整包名),这是因为Person本身并不是一个 Hibernate 管理的实体类。

查询结果同样包含所有的子类,当然并不包括超类本身,因为这种情况下超类不是实体类,没有持久化数据。

Student(super=Person(id=10, name=icexmoon), averageScore=90)
Student(super=Person(id=11, name=lalala), averageScore=85)
Student(super=Person(id=12, name=JackChen), averageScore=95)
Teacher(super=Person(id=10, name=BrusLee), course=CHEMISTRY)
Teacher(super=Person(id=11, name=Tina), course=MATH)
Teacher(super=Person(id=12, name=Cacherine), course=MUSIC)

还需要注意的是,此种情况下子类的 id 并不存在关联关系,由各自的 identity generator 生成,所以在上面这个示例中是可以出现重复 id 的。这和@Inheritance实现的继承映射有所不同。

如果不希望某个子类出现在对父类的 JPQL 查询结果中,可以使用@Polymorphism(type = PolymorphismType.EXPLICIT)标记。

The End,谢谢阅读。

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

参考资料

  • Hibernate Inheritance Mapping | Baeldung

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/728050.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LCD1602显示屏只亮不显示字符

代码能在普中的板子能正常显示&#xff0c;但是换了块板子就不行&#xff1a; 调节显示屏下方的可调电阻 在调试中找到自己适合的值&#xff0c;就可以看见字符了

5、加载3dtileset模型并定位到模型

这一节使用CCesium加载3dtiles模型&#xff0c;3dtiles模型使用ceisum官网示例中的模型&#xff0c;加载3dtiles功能目前只能添加没有压缩的模型&#xff0c;draco或其他解压缩功能没有写。 1、在上一个例子的基础上&#xff0c;将鼠标事件改成右键的鼠标事件Cesium::ScreenSp…

科研热点|重磅!华为,唯一单位首发Nature!

今日&#xff0c;国际顶级学术期刊《自然》(Nature)杂志正刊发表了华为云盘古大模型研发团队研究成果——《三维神经网络用于精准中期全球天气预报》&#xff08;《Accurate medium-range global weather forecasting with 3D neural networks》&#xff09;。数据显示&#xf…

医学报告怎样翻译效果好?

我们知道&#xff0c;医学报告是医学翻译领域比较重要的一个项目&#xff0c;许多国际医学期刊和杂志都要求医学报告必须是英语的&#xff0c;促使医学报告翻译的需求量越来越大。那么&#xff0c;怎样翻译医学报告&#xff0c;医学报告中译英哪里比较专业&#xff1f; 据了解&…

Java类加载深度剖析-大白话

Java类加载深度剖析 1.类加载的入口2.AppClassLoader、ExtClassLoader、BootstrapClassLoader的血脉渊源3.ExtClassLoader究竟是不是孙大圣4.为什么自定义类加载器的父类加载器是AppClassLoader呢&#xff1f;5.我们应该如何打破双亲委派机制呢&#xff1f;6.如何保证同class对…

实现UDP通信

UDP通信的实现过程 write/read到send/recv 函数原型&#xff1a; ssize_t send(int sockfd, const void *buf, size_t len, int flags); ssize_t recv(int sockfd, void *buf, size_t len, int flags); 前三个参数同read/write一样&#xff1b; ssize_t read(int fd, void *bu…

滚珠螺杆的基本知识点

滚珠螺杆具有定位精度高、高寿命、低污染和可做高速正逆向的传动及变换传动等特性&#xff0c;因具上述特性&#xff0c;滚珠螺杆已成为近来精密科技产业及精密机械产业的定位及测量系统上的重要零组件之一。 滚珠螺杆主要由螺杆、螺帽、钢珠、固定座、刮刷器及回流管所构成的&…

thinkphp开发宠物领养商城系统 金融投资理财源码 中英文语言 支持增加多种语言

程序代码里面 除了线下支付&#xff0c;增加了4个线上支付方式 1、新增短信接口&#xff1a;短信宝、云片短信、网建短信。 2、新增阿里API实名认证&#xff0c;支持开启或关闭。 3、新增阿里API银行卡实名认证&#xff0c;支持开启或关闭。 4、新增项目分类开关、支付宝和微信…

基于springboot+vue框架的电影订票系统_wqc3k

随着网络科技的不断发展以及人们经济水平的逐步提高&#xff0c;计算机如今已成为人们生活中不可缺少的一部分&#xff0c;为电影订票方便管理&#xff0c;基于java技术设计与实现了一款简洁、轻便的管理系统。本系统解决了电影订票事务中的主要问题&#xff0c;包括个人中心、…

微服务拆分原则

库存供应链服务 交易和订单服务 用户服务 1. 业务之间耦合降低 相互调用较少 进行拆分 2.修改频率区分不同服务

Qlib全新升级:强化学习能否重塑金融决策模式?

编者按&#xff1a;2020年&#xff0c;微软亚洲研究院开源了金融 AI 通用技术平台 Qlib。Qlib 以金融 AI 研究者和金融行业 IT 从业者为用户&#xff0c;针对金融场景研发了一个适应人工智能算法的高性能基础设施和数据、模型管理平台。一经开源&#xff0c;Qlib 便掀起了一阵热…

对话罗氏中国:数字化创新驱动下的高效运营与合规实践

上海斯歌与罗氏中国已合作十余年&#xff0c;罗氏几乎见证了上海斯歌发展的全过程。近日&#xff0c;斯歌与罗氏 China Market Domain 架构负责人—— Brian Yang&#xff0c;就企业数字化探索、新技术的展望及斯歌产品等话题展开了探讨。 罗氏中国简介 罗氏&#xff08;Roche&…

Nacos启动报错

错误如下 org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name communicationController defined in URL [jar:file:/D:/apache/maven/vip/xiaonuo/nacos/nacos-config/2.1.2.GA/nacos-config-2.1.2.GA.jar!/com/alibaba/nacos…

前后端实现mqtt物联网消息互通对话(图文详解)

需求&#xff1a;前端使用mqtt订阅主题和硬件设备进行通讯功能&#xff0c;不走后端&#xff0c;前端操作可以控制。从部署到对话&#xff0c;跟着图文一套下来你也可以学会。很简单的。后端用node&#xff0c;前端就用原生的js&#xff0c;如果要使用vue&#xff0c;可以看我另…

OpenCV4使用applyColorMap()函数,可以将灰度图或彩色图转换成自定义的彩色图,或opencv提供的20多种色彩值

文章目录 1、applyColorMap()函数的使用&#xff1a;&#xff08;1&#xff09;函数原型&#xff1a;void applyColorMap(InputArray src, OutputArray dst, int colormap)void applyColorMap(InputArray src, OutputArray dst, InputArray userColor) &#xff08;2&#xff0…

记录CompletableFuture使用遇到的坑-多数据源

现象&#xff1a;使用了allof().get()去阻塞线程等待子线程跑完&#xff0c;但子线程还是没跑完就结束了。 代码如图&#xff1a; 如果您熟悉CompletableFuture的.allOf方法应该知道题主是想等3个异步任务完成再往下执行&#xff0c;但事实是3个异步任务执行到最后 "好像…

2023年最新全国分省、市路网数据shp

最新全国分省、市路网数据 2023年 ​ 最近有小伙伴反应无法进入OSM地图官网下载数据&#xff0c;所以这次带来全国路网数据shp文件。获取时间&#xff1a;2023年5月 数据格式&#xff1a;shp 坐标系&#xff1a;GCJ-02 数据概览 分省如下&#xff1a; ​ 例如河北&#xff…

DDR3 控制器 MIG IP 详解完整版 (nativeVIVADOVerilog)

文章目录 前言一、MIG IP 核的配置二、MIG 交互的接口三、常用IP例化值四、小实验传图 前言 本节主要是介绍 Xilinx DDR 控制器 IP 的创建流程、IP 用户使用接口 native 协议介绍和IP 对应的 Example Design 的仿真和上板验证。。 提示&#xff1a;以下是本篇文章正文内容&…

开放式耳机哪个好?综合性能不错的开放式耳机推荐

传统入耳式耳机容易滑落&#xff0c;而且戴久了耳朵疼&#xff0c;开放式耳机的出现就避免了这个问题的出现&#xff0c;本文就为大家推荐几款使用感较好的开放式耳机&#xff0c;一起来看看吧~ 一、NANK南卡OE Pro开放式耳机 南卡OE Pro凭借着顶级的佩戴体验和极高的音质水准…

图像几何变换笔记

图像缩放 图像缩放是指对图像大小进行调整&#xff0c;对原图进行放大或缩小。图像缩放一般通过插值采样来实现。 常见的插值方法&#xff1a;最近邻插值、双线性插值、双三次插值。 最近邻插值 最近邻插值是最简单的一种插值方法&#xff0c;通过映射将原始图片中的像素值映射…