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

这篇具有很好参考价值的文章主要介绍了从零开始 Spring Boot 63:Hibernate 继承映射。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

从零开始 Spring Boot 63:Hibernate 继承映射,JAVA,spring boot,hibernate,继承映射

图源:简书 (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 自动生成的,用于区分不同实体的数据,比如下图的示例数据:

从零开始 Spring Boot 63:Hibernate 继承映射,JAVA,spring boot,hibernate,继承映射

可以看到,如果不是当前类的属性,就用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都不对应,就会映射到由这个注解标记的实体。

连接的表

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

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

从零开始 Spring Boot 63:Hibernate 继承映射,JAVA,spring boot,hibernate,继承映射

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;
    }
	// ...
}

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

从零开始 Spring Boot 63:Hibernate 继承映射,JAVA,spring boot,hibernate,继承映射

从零开始 Spring Boot 63:Hibernate 继承映射,JAVA,spring boot,hibernate,继承映射

从零开始 Spring Boot 63:Hibernate 继承映射,JAVA,spring boot,hibernate,继承映射

多态查询

@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,谢谢阅读。

可以从这里获取本文的完整示例代码。文章来源地址https://www.toymoban.com/news/detail-531476.html

参考资料

  • Hibernate Inheritance Mapping | Baeldung

到了这里,关于从零开始 Spring Boot 63:Hibernate 继承映射的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 从零开始学Spring Boot系列-SpringApplication

    SpringApplication类提供了一种从main()方法启动Spring应用的便捷方式。在很多情况下, 你只需委托给 SpringApplication.run这个静态方法 : 当应用启动时, 你应该会看到类似下面的东西: 默认情况下会显示INFO级别的日志信息, 包括一些相关的启动详情, 比如启动应用的用户等。 通过

    2024年04月08日
    浏览(62)
  • 从零开始学Spring Boot系列-集成mybatis

    在Spring Boot的应用开发中,MyBatis是一个非常流行的持久层框架,它支持定制化SQL、存储过程以及高级映射。在本篇文章中,我们将学习如何在Spring Boot项目中集成MyBatis,以便通过MyBatis进行数据库操作。 首先,我们需要在项目中添加MyBatis的依赖。在Spring Boot中,我们通常会使

    2024年03月10日
    浏览(129)
  • 从零开始学Spring Boot系列-集成Kafka

    Apache Kafka是一个开源的分布式流处理平台,由LinkedIn公司开发和维护,后来捐赠给了Apache软件基金会。Kafka主要用于构建实时数据管道和流应用。它类似于一个分布式、高吞吐量的发布-订阅消息系统,可以处理消费者网站的所有动作流数据。这种动作流数据包括页面浏览、搜

    2024年03月21日
    浏览(62)
  • Spring Boot(04):让你的Spring Boot应用“火力全开”,从零开始学习starter

            Spring Boot是一款非常流行的Java开发框架,其具有快速开发、自动化配置、内嵌服务器、易于扩展等特点,因此备受开发者欢迎。在日常开发中,我们经常需要在不同的环境中进行测试和部署,此时,如何实现开发、测试、生产环境的快速切换,成为了我们需要解决

    2024年04月13日
    浏览(61)
  • 从零开始 Spring Boot 52:@Embedded 和 @Embeddable

    图源:简书 (jianshu.com) 这篇文章会介绍 @Embedded 和 @Embeddable 两个注解在 JPA 中的用法。 先看一个示例: 这里使用了 Lombok 相关注解(比如 @Builder )帮助构建实体类,详细内容可以阅读我的相关文章。 user_student 是一个学生表,其中的 contacts_ 开头的字段保存联系人信息,这体

    2024年02月12日
    浏览(67)
  • 从零开始 Spring Boot 38:Lombok 与依赖注入

    图源:简书 (jianshu.com) 在之前的文章中,我详细介绍了 Lombok 的用法,考虑到在 Spring 中使用依赖注入(DI)是如此的频繁,因此有必要讨论使用 Lombok 时可能对依赖注入造成的影响。 我们都知道,Spring 中的依赖注入分为三种情况: 通过属性进行依赖注入。 通过构造器进行依

    2024年02月08日
    浏览(70)
  • 从零开始 Spring Boot 37:初始化 ApplicationContext

    图源:简书 (jianshu.com) 从前文可以知道,作为 Ioc 容器的 ApplicationContext,需要进行一系列步骤来初始化以最终就绪(对于 Web 应用来说就是可以提供Http服务)。 这些步骤大概可以分为以下内容: 准备上下文关联的 Environment 。 初始化 ApplicationContext( ApplicationContextInitializers

    2024年02月08日
    浏览(44)
  • 从零开始学Spring Boot系列-外部化配置

    Spring Boot 允许你将配置外部化,以便可以在不同的环境中使用相同的应用程序代码。可以使用属性文件、YAML文件、环境变量和命令行参数将配置外部化。属性值可以通过使用 @Value 注解直接注入 bean,可以通过 Spring 的 Environment 抽象访问,也可以通过 @ConfigurationProperties。 Sp

    2024年04月10日
    浏览(107)
  • 从零开始 Spring Boot 51:JPA 中的默认列值

    图源:简书 (jianshu.com) JPA 是一个 ORM 框架,因此,通常我们需要在实体类中定义表结构,这其中就包含可能的字段默认值。 本文介绍如何在 Hibernate(JPA)中设置默认列值(Default Column Value)。 最简单的方式是对实体类指定一个默认的属性值,比如: 测试用例: 这样做的缺点

    2024年02月11日
    浏览(91)
  • 从零开始 Spring Boot 57:JPA中的一对多关系

    图源:简书 (jianshu.com) 在上篇文章中我们介绍了如何在 JPA 中实现实体的一对一关系,在关系型数据库设计中,除了一对一关系,还存在一对多关系。本篇文章介绍如何在 JPA 中实现一对多关系。 假设我们有两张表,学生表和电子邮件账号表,一个学生可以有多个电子邮件账

    2024年02月12日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包