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

从零开始 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)@NotNullprivate 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_ciCREATE 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_ciCREATE 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_ciCREATE 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_ciCREATE 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 {@Idprivate 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;@Synchronizedlong nextValue() {id++;return id;}
}@TestConfiguration
public class ExampleDataConfig {@Autowiredprivate IdGenerator idGenerator;@BeanStudent 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.hqwc.cn/news/15166.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL逻辑架构

MySQL逻辑架构 1. MySQL逻辑架构1.1 连接层1.2 服务层1.3 引擎层 2. SQL执行流程2.1 SQL执行流程2.2 查看SQL执行流程2.3 SQL语法顺序 1. MySQL逻辑架构 分为Connectors层、MySQL服务器对应架构、存储层&#xff1b;MySQL服务器对应架构分为连接层、服务层、引擎层&#xff0c…

RabbitMQ系列(26)--RabbitMQ实现高可用负载均衡

前言&#xff1a;我们以往只能连接一个指定的队列&#xff0c;不能自由地连接其他的队列&#xff0c;当我们连接的那个指定队列宕机了&#xff0c;生产者和消费者都没办法往队列发送消息和消费消息&#xff0c;而且生产者和消费者也不能自动的连接到其他正常运行的队列&#xf…

sqlite维护命令复习学习

前面已经看了一些sqlite命令&#xff0c;例如查看表名&#xff0c;查看表结构等&#xff1b;下面继续看一下&#xff1b; 查看全部表名&#xff1b; 查看单个或全部的表结构&#xff1b; 输出表结构和数据&#xff1b; 使用.output 把查询结果定向到1.txt&#xff1b; 重新定向…

【JavaEE初阶】JavaScript(WebAPI)

文章目录 1.WebAPI背景知识1.1什么是WebAPI1.2什么是API 2.DOM基本概念2.1什么是DOM2.2常用的DOMAPI2.2.1.选中页面元素2.2.2操作元素的属性1. 事件概念2.获取/修改元素内容3. 获取/修改元素属性4.获取/修改表单元素属性5.获取修改样式属性 2.2.3.操作页面节点1.新增节点2.删除…

【Linux】线程

目录 一、Linux线程概念 二、线程的特性 1、线程的优点 2、线程的缺点 3、线程异常 4、线程用途 三、进程与线程 四、Linux线程控制 1、创建线程 2、线程退出 3、等待线程 4、线程取消 5、其他接口 5.1、获取自己的线程id 6、线程分离 五、线程库 六、线程互斥…

Hcip第四次作业

要求&#xff1a; 1.如图连接&#xff0c;合理规划IP地址&#xff0c;所有路由器各自创建一个loopback接口 2.R1再创建三个接口IP地址为201.1.1.1/24、201.1.2.1/24、201.1.3.1/24 R5再创建三个接口IP地址为202.1.1.1/24、202.1.2.1/24、202.1.3.1/24 R7再创建三个接口IP地址为…

使用kubeshpere创建k8s容器日志系统grafana-loki

k8s日志聚合平台grafana-loki&#xff0c;可以统一查看所有容器的日志运行。 效果&#xff1a; 使用kubeshpere创建loki应用十分的方便&#xff0c;减少了很多操作&#xff0c;易用的同时也实用&#xff0c;k8s不再是精通人员才能使用的编排系统。 1、在企业空间中&#xff0…

Mongodb-5.0.18-zip下载与安装

1.下载地址 Download MongoDB Community Server | MongoDB 2.创建一个文件夹和master.cfg的文件 说明&#xff1a;目的是让mongodb存放数据库的位置在mongodb文件里面。 2.1创建名为mongodb的文件夹 2.2master.cfg 说明&#xff1a;在mongodb5.0.18目录下创建master.cfg。 …

微服务 02-rabbitmq在springboot中如何使用(上篇)

目录 前言: 上文传送 -> 安装rabbitmq传送门: -> rabbitmq使用出现问题解决传送门: 1. rabbitmq的六大模式: 1.1 简单模式: (一对一) -> 业务场景: 1.2 工作模式: (一对多) -> 业务场景: 1.3 发布与订阅模式: (广播) 1.4 路由模式: -> 业务场景 …

OpenCV 入门教程:图像读取和显示

OpenCV 入门教程&#xff1a;图像读取和显示 导语一、图像读取1.1、导入 OpenCV 库1.2、读取图像文件1.3、图像读取的返回值 二、图像显示2.1、创建窗口2.2、图像显示2.3、等待按键2.4、关闭窗口 三、示例应用总结 导语 在计算机视觉和图像处理领域&#xff0c;读取和显示图像…

《零基础学PIC单片机》目录

《零基础学PIC单片机》目录 1.《零基础学PIC单片机》&#xff0c;作者&#xff1a;赵化启 1.1芯片架构和指令 芯片架构和指令需要较多时间消化。 PIC单片机系统结构&#xff0c;讲解各模块的结构和功能&#xff1b;PIC汇编指令 1.2具有参考价值的内容 第3章&#xff1a;电…

Docker开启远程端口访问2375

开启方法&#xff1a; 1、修改/etc/default/docker下的配置 cat >> /etc/default/docker <<EOF DOCKER_OPTS"-H tcp://0.0.0.0:2375" EOF systemctl restart docker 2、修改/usr/lib/systemd/system/docker.service配置 cat >> /usr/lib/systemd/s…