Java 中的单表继承模式:使用统一表结构简化对象映射
也称为
- 类表继承
- STI
单表继承设计模式的意图
单表继承模式简化了 Java 应用程序中的数据库模式。
将继承层次结构简化存储在一个数据库表中,其中行代表不同类的对象,列代表所有属性的并集。
单表继承模式的详细解释以及实际示例
实际示例
想象一个图书馆系统,其中书籍、杂志和 DVD 都存储在一个库存表中。此表包含所有项目共有的属性列,例如
Title
、Author
、PublicationDate
和ItemType
,以及特定于某些类型的列,例如书籍的ISBN
、杂志的IssueNumber
和 DVD 的Duration
。每行代表图书馆中的一个项目,根据项目类型,某些列将保留为 null。此设置通过将所有库存项目保留在一个表中简化了数据库模式,类似于数据库环境中的单表继承。
通俗地说
单表继承将整个类层次结构存储在一个数据库表中,使用类型区分器列区分不同的子类。
维基百科说
单表继承是一种在关系数据库中模拟面向对象继承的方式。在将数据库表映射到面向对象语言中的对象时,数据库中的一个字段标识对象所属的层次结构中的哪个类。所有类的所有字段都存储在同一表中,因此得名“单表继承”。
Java 中单表继承模式的编程示例
单表继承是一种设计模式,它将类继承层次结构映射到一个数据库表中。表中的每一行代表层次结构中类的实例。一个特殊的区分器列用于标识每一行所属的类。
此模式在继承层次结构中的类在字段和行为方面没有显著差异时非常有用。它简化了数据库模式,并且可以通过避免连接来提高性能。
让我们看看如何在提供的代码中实现此模式。
我们层次结构中的基类是 Vehicle
。此类具有所有车辆共享的公共属性,例如 manufacturer
和 model
。
public abstract class Vehicle {
private String manufacturer;
private String model;
// other common properties...
}
我们有两个子类,PassengerVehicle
和 TransportVehicle
,它们扩展了 Vehicle
并添加了额外的属性。
public abstract class PassengerVehicle extends Vehicle {
private int noOfPassengers;
// other properties specific to passenger vehicles...
}
public abstract class TransportVehicle extends Vehicle {
private int loadCapacity;
// other properties specific to transport vehicles...
}
最后,我们有像 Car
和 Truck
这样的具体类,它们分别扩展了 PassengerVehicle
和 TransportVehicle
。
public class Car extends PassengerVehicle {
private int trunkCapacity;
// other properties specific to cars...
}
public class Truck extends TransportVehicle {
private int towingCapacity;
// other properties specific to trucks...
}
在此示例中,我们使用 Hibernate 作为我们的 ORM。我们使用 @Entity
和 @Inheritance
注释将我们的类层次结构映射到单个表。
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class Vehicle {
// properties...
}
@DiscriminatorColumn
注释用于指定表中的区分器列。此列将为每一行保存类名。
@DiscriminatorColumn(name = "vehicle_type")
public abstract class Vehicle {
// properties...
}
每个子类使用 @DiscriminatorValue
注释指定其区分器值。
@DiscriminatorValue("CAR")
public class Car extends PassengerVehicle {
// properties...
}
@DiscriminatorValue("TRUCK")
public class Truck extends TransportVehicle {
// properties...
}
VehicleService
类提供了保存和检索车辆的方法。当我们保存 Car
或 Truck
对象时,Hibernate 将自动将区分器列设置为相应的值。当我们检索车辆时,Hibernate 将使用区分器列来实例化正确的类。
public class VehicleService {
public Vehicle saveVehicle(Vehicle vehicle) {
// save vehicle to database...
}
public Vehicle getVehicle(Long id) {
// retrieve vehicle from database...
}
public List<Vehicle> getAllVehicles() {
// retrieve all vehicles from database...
}
}
最后,这是运行我们示例的 Spring Boot 应用程序。
@SpringBootApplication
@AllArgsConstructor
public class SingleTableInheritance implements CommandLineRunner {
//Autowiring the VehicleService class to execute the business logic methods
private final VehicleService vehicleService;
public static void main(String[] args) {
SpringApplication.run(SingleTableInheritance.class, args);
}
@Override
public void run(String... args) {
Logger log = LoggerFactory.getLogger(SingleTableInheritance.class);
log.info("Saving Vehicles :- ");
// Saving Car to DB as a Vehicle
Vehicle vehicle1 = new Car("Tesla", "Model S", 4, 825);
Vehicle car1 = vehicleService.saveVehicle(vehicle1);
log.info("Vehicle 1 saved : {}", car1);
// Saving Truck to DB as a Vehicle
Vehicle vehicle2 = new Truck("Ford", "F-150", 3325, 14000);
Vehicle truck1 = vehicleService.saveVehicle(vehicle2);
log.info("Vehicle 2 saved : {}\n", truck1);
log.info("Fetching Vehicles :- ");
// Fetching the Car from DB
Car savedCar1 = (Car) vehicleService.getVehicle(vehicle1.getVehicleId());
log.info("Fetching Car1 from DB : {}", savedCar1);
// Fetching the Truck from DB
Truck savedTruck1 = (Truck) vehicleService.getVehicle(vehicle2.getVehicleId());
log.info("Fetching Truck1 from DB : {}\n", savedTruck1);
log.info("Fetching All Vehicles :- ");
// Fetching the Vehicles present in the DB
List<Vehicle> allVehiclesFromDb = vehicleService.getAllVehicles();
allVehiclesFromDb.forEach(s -> log.info(s.toString()));
}
}
控制台输出
2024-05-27T12:29:49.949+03:00 INFO 56372 --- [ main] com.iluwatar.SingleTableInheritance : Starting SingleTableInheritance using Java 17.0.4.1 with PID 56372 (/Users/ilkka.seppala/git/java-design-patterns/single-table-inheritance/target/classes started by ilkka.seppala in /Users/ilkka.seppala/git/java-design-patterns)
2024-05-27T12:29:49.951+03:00 INFO 56372 --- [ main] com.iluwatar.SingleTableInheritance : No active profile set, falling back to 1 default profile: "default"
2024-05-27T12:29:50.154+03:00 INFO 56372 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2024-05-27T12:29:50.176+03:00 INFO 56372 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 19 ms. Found 1 JPA repository interface.
2024-05-27T12:29:50.315+03:00 INFO 56372 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
2024-05-27T12:29:50.345+03:00 INFO 56372 --- [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 6.4.4.Final
2024-05-27T12:29:50.360+03:00 INFO 56372 --- [ main] o.h.c.internal.RegionFactoryInitiator : HHH000026: Second-level cache disabled
2024-05-27T12:29:50.457+03:00 INFO 56372 --- [ main] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer
2024-05-27T12:29:50.468+03:00 INFO 56372 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2024-05-27T12:29:50.541+03:00 INFO 56372 --- [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:sti user=SA
2024-05-27T12:29:50.542+03:00 INFO 56372 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2024-05-27T12:29:50.930+03:00 INFO 56372 --- [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
2024-05-27T12:29:50.953+03:00 INFO 56372 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2024-05-27T12:29:51.094+03:00 INFO 56372 --- [ main] com.iluwatar.SingleTableInheritance : Started SingleTableInheritance in 1.435 seconds (process running for 1.678)
2024-05-27T12:29:51.095+03:00 INFO 56372 --- [ main] com.iluwatar.SingleTableInheritance : Saving Vehicles :-
2024-05-27T12:29:51.114+03:00 INFO 56372 --- [ main] com.iluwatar.SingleTableInheritance : Vehicle 1 saved : Car{PassengerVehicle(noOfPassengers=4)}
2024-05-27T12:29:51.115+03:00 INFO 56372 --- [ main] com.iluwatar.SingleTableInheritance : Vehicle 2 saved : Truck{ TransportVehicle(loadCapacity=3325), towingCapacity=14000}
2024-05-27T12:29:51.115+03:00 INFO 56372 --- [ main] com.iluwatar.SingleTableInheritance : Fetching Vehicles :-
2024-05-27T12:29:51.129+03:00 INFO 56372 --- [ main] com.iluwatar.SingleTableInheritance : Fetching Car1 from DB : Car{PassengerVehicle(noOfPassengers=0)}
2024-05-27T12:29:51.130+03:00 INFO 56372 --- [ main] com.iluwatar.SingleTableInheritance : Fetching Truck1 from DB : Truck{ TransportVehicle(loadCapacity=0), towingCapacity=14000}
2024-05-27T12:29:51.130+03:00 INFO 56372 --- [ main] com.iluwatar.SingleTableInheritance : Fetching All Vehicles :-
2024-05-27T12:29:51.169+03:00 INFO 56372 --- [ main] com.iluwatar.SingleTableInheritance : Car{PassengerVehicle(noOfPassengers=0)}
2024-05-27T12:29:51.169+03:00 INFO 56372 --- [ main] com.iluwatar.SingleTableInheritance : Truck{ TransportVehicle(loadCapacity=0), towingCapacity=14000}
2024-05-27T12:29:51.172+03:00 INFO 56372 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2024-05-27T12:29:51.173+03:00 INFO 56372 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2024-05-27T12:29:51.174+03:00 INFO 56372 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
单表继承模式是一种简单高效的方式,可以将继承层次结构映射到关系数据库。但是,如果子类具有许多唯一字段,则会导致表稀疏。在这种情况下,其他模式(如类表继承或具体表继承)可能更合适。
何时在 Java 中使用单表继承模式
- 当您有一个具有共享公共基类的子类的类层次结构,并且您希望将层次结构的所有实例存储在一个表中时使用。
- 对于大小适中的应用程序来说是理想的选择,在这些应用程序中,单个表的简单性胜过某些子类的空字段的性能成本。
单表继承模式教程
Java 中单表继承模式的实际应用
- Java 应用程序中的 Hibernate 和 JPA 实现通常使用单表继承进行 ORM 映射。
- Rails ActiveRecord 默认支持单表继承。
单表继承模式的优势和权衡
优点
在 Java ORM 中使用单表继承模式
- 通过减少表数来简化数据库模式。
- 由于所有数据都在一个表中,因此更容易管理关系和查询。
权衡
- 会导致稀疏填充的表,其中包含许多空值。
- 由于表大小和需要按类型过滤,因此可能会对大型层次结构造成性能问题。
- 继承层次结构中的更改需要模式更改。
相关的 Java 设计模式
- 类表继承:对层次结构中的每个类使用单独的表,减少空值,但增加连接的复杂性。
- 具体表继承:层次结构中的每个类都有自己的表,减少冗余,但增加表数。