Java小技巧:JPA和Hibernate中的继承关系

继承是面向对象编程中的一种常见模式,但是它不容易在数据库中复制。这个Java技巧向您展示了如何使用Hibernate在JPA中为继承关系建模。
了解四种不同的ORM继承策略的优缺点,并获得选择最能满足您的应用程序需求的技巧。在这里插入图片描述
我假设您已经通过JPA和Hibernate入门到Java持久性,包括如何建模实体和关系以及如何与JPA一起使用EntityManager。
ORM中的继承模式
继承是一种面向对象的模式,其中一个类扩展(或专门化)另一类以借用(或继承)其某些功能。例如,假设我们有一个名为的类Car,代表所有汽车,包括品牌,型号和年份。但是现在我们要创建一些更专业的汽车类型,例如SportsCar和SportUtilityVehicle。这些汽车将具有原始Car类的所有功能,但具有一些附加功能。A SportsCar可能需要特殊的参数来指定速度。A SportsUtilityVehicle可能需要标志来确定座位和牵引能力。
JPA指定了四种定义继承关系的策略:
映射超类
每课桌
单桌
已加入
我们将研究Hibernate实施的每种策略。
继承与多态
继承的好处之一是它允许您通过子类的超类对子类执行操作,这被称为多态。举例来说,如果你有一个Car基类,有一个drive()方法,和两个子类Car:SportsCar和SportsUtilityVehicle,然后你可以自由存储SportCarS和SportsUtilityVehicle集合s中CarS和调用drive()方法-全不知道,如果你驾驶SportsCar或一个SportsUtilityVehicle。我们将回顾支持多态性的JPA继承策略。
继承策略1:映射超类
映射超类是最简单的继承策略。它允许您在基类中定义通用属性,然后在您的其他类中扩展该超类。这是一个基于我们的Car类的示例:
@MappedSuperclasspublic abstract class Car {
@Id
@GeneratedValue
private Integer id;
private String make;
private String model;
private Integer year;

...}

本Car类注解与@MappedSuperclass诠释和定义了通用汽车的属性,如厂名,型号和年份,以及自动生成的id车。
现在让我们看看定义一个SportsCar类和SportUtilityVehicle扩展类时会发生什么Car:
@Entity@Table(name = “SPORTSCAR”)public class SportsCar extends Car {
private Integer topSpeed;
private Double zeroToSixty;

...}

@Entity@Table(name = “SUV”)public class SportsUtilityVehicle extends Car {
private Integer towingCapacity;
private Boolean thirdRowSeating;

...}

映射超类策略很简单,但有局限性,因为这两个类都将映射到包含所有汽车属性的表:原始Car属性和新的专用类属性。
使用这种模式意味着Hibernate将无法对单个汽车运行多态查询。如果我们定义了一个CarDealer管理不同类型的汽车类,有没有办法维持的列表Cars中CarDealer; 相反,我们需要创建的列表SportsCar和的列表SportUtilityVehicle。
使用稍微复杂一些的继承策略,我们可能会做得更好。
继承策略2:每类表
实现继承的下一个策略称为“每类表”。就像我们对映射的超类所做的那样,此策略将所有数据存储在其专用类表中,但是它保留了专用类与其基类之间的关系。
在这种情况下,专用类SportsCar和和SportsUtilityVehicle保持不变,但是基类被视为一个实体。
@Entity@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)public abstract class Car {
@Id
@GeneratedValue
private Integer id;
private String make;
private String model;
private Integer year;

...}

我们使用@Entity注释将基类定义为实体。然后@Inheritance,我们引入注释,指定InstanceType.TABLE_PER_CLASS策略。
使用此策略,我们可以执行多态查询,因此可以将的列表映射Car到CarDealer。
复杂的工会
尽管“每类表”提供了我们所需要的功能,但其生成的查询却很复杂。该策略还存在潜在的性能问题,因为我们必须执行所有Car子类表的并集,然后从中解析结果集。
例如,假设我们要在应用程序代码中添加两辆汽车:一辆SUV和一辆跑车。结果数据库表将包含以下内容:
Table: CARTable: SPORTSCAR
{ID: 1, MAKE: Carrera, MODEL: Porche, YEAR: 2018, TOPSPEED: 150, ZEROTOSIXTY: 4.5}Table: SUV
{ID: 2, MAKE: CX-9, MODEL: Mazda, YEAR: 2017, THIRDROW: true, TOWINGCAPACITY: 3000}

继承策略3:单一表格
下一个策略将所有实体的所有字段映射到单个表,并带有一个鉴别符列以标识实例类型:
@Entity@Inheritance(strategy = InheritanceType.SINGLE_TABLE)@DiscriminatorColumn(name = “Car_Type”)public abstract class Car {
@Id
@GeneratedValue
private Integer id;
private String make;
private String model;
private Integer year;

...}

@Entity@Table(name = “SPORTSCAR”)@DiscriminatorValue(“SportsCar”)public class SportsCar extends Car {
private Integer topSpeed;
private Double zeroToSixty;

...}

@Entity@Table(name = “SUV”)@DiscriminatorValue(“SUV”)public class SportsUtilityVehicle extends Car {
private Integer towingCapacity;
private Boolean thirdRowSeating;

...}

单表策略是使用@Inheritance带有的注释实现的InstanceType.SINGLE_TABLE。我们为此添加了一个@DiscriminatorColumn注释,该注释定义了用于区分特定类类型的列名。特殊类(例如SportsCar和SportsUtilityVehicle)扩展了基类并添加了@DiscriminatorValue注释,该注释指定名称以标识其在数据库中的实例。
这种实现方式的挑战在于,每个表都维护所有专用类的所有属性的所有字段。您还会失去将专用字段定义为非null的功能,因为对于其他专用类,它们将为null。例如,代表a的SportsCar行将具有topSpeed和zeroToSixty值,但将具有null towingCapacity和thirdRowSeating列值。
基于我们对这种模式的实现,数据库的内容如下:
Table: CAR
{CAR_TYPE: SportsCar, ID: 1, MAKE: Carrera, MODEL: Porche, YEAR: 2018, TOPSPEED: 150, ZEROTOSIXTY: 4.5, THIRDROW: null, TOWINGCAPACITY: null},
{CAR_TYPE: SUV, ID: 2, MAKE: CX-9, MODEL: Mazda, YEAR: 2017, TOPSPEED: null, ZEROTOSIXTY: null, THIRDROW: true, TOWINGCAPACITY: 3000},

请注意,这两辆车都存储在CAR表中,并且每个表[都有一个] CAR_TYPE属性(来自@DiscriminatorColumn),该属性告诉Hibernate汽车的类型(来自@DiscriminatorValue。)还要注意,每一行都包含所有列值,未使用的列值设置为null。
这种策略允许进行高效且多态的查询。不利的一面是,可能需要维护许多null和不必要的列。
继承策略4:已加入
最终的JPA继承策略将基类和专用类之间的数据分离到各自的表中。基类表包含基类中定义的所有通用属性,而专用类表仅包含其特定属性。我们通过使用InstanceType.JOINED继承策略完成此分离:
@Entity@Inheritance(strategy = InheritanceType.JOINED)public abstract class Car {
@Id
@GeneratedValue
private Integer id;
private String make;
private String model;
private Integer year;

...}

定义继承关系时,您最期望的就是这种类型的继承的效果。对于我们的示例,数据库包含以下值:
Table: CAR
{ID: 1, MAKE: Carrera, MODEL: Porche, YEAR: 2018}
{ID: 2, MAKE: CX-9, MODEL: Mazda, YEAR: 2017}Table: SPORTSCAR
{TOPSPEED: 150, ZEROTOSIXTY: 4.5, ID: 1}Table: SUV
{THIRDROW: true, TOWINGCAPACITY: 3000, ID: 2}

使用此策略会导致表中没有很多null值,就像使用该SINGLE_TABLE策略一样。您也不必编写所需的全套表联合TABLE_PER_CLASS。不利的一面是,Hibernate必须在基类表和专用类表之间执行联接,这会使查询变得相当复杂。
选择JPA继承策略
通过四个选择,决定使用哪种策略取决于您的用例。通常,TABLE_PER_CLASS由于查询开销,我建议避免使用该策略。我还建议避免使用该MAPPED_SUPERCLASS策略,或者仅在不需要多态使用类的情况下使用该策略。
剩下SINGLE_TABLE和JOINED策略。
如果您需要快速查询,并且不介意null在单个表中维护多余的未使用列,则可以采用该SINGLE_TABLE策略。
如果您不想维护未使用的列,并且想要像在考虑数据库模型时那样对继承关系进行建模,请使用该JOINED策略。
就个人而言,我几乎总是JOINED在JPA中使用继承关系。
最后,开发这么多年我也总结了一套学习Java的资料与面试题,如果你在技术上面想提升自己的话,可以关注我,私信发送领取资料或者在评论区留下自己的联系方式,有时间记得帮我点下转发让跟多的人看到哦。在这里插入图片描述

发布了38 篇原创文章 · 获赞 8 · 访问量 2691

猜你喜欢

转载自blog.csdn.net/zhaozihao594/article/details/103991168
今日推荐