原文:JPA implementation patterns: Mapping inheritance hierarchies
作者:Vincent Partington
出处:http://blog.xebia.com/2009/06/21/jpa-implementation-patterns-mapping-inheritance-hierarchies/
上周我在正在进行的JPA实施模式博客系列中讨论比较了域访问与属性访问之间的优缺点,本周我会详细论述在映射继承的层次体系时JPA所提供的选择。
JPA提供了三种把Java的继承层次体系映射到数据库表的方式:
1. InheritanceType.SINGLE_TABLE—整个继承的层次体系被映射到一张表上,一个对象正好被存放成表中的一行,存放在鉴别器列中的鉴别器值指明了对象的类型,任何在超类中或者是在层次体系的不同分支中未被用到的字段都被设定为NULL,这是JPA默认使用的继承映射策略。
2. InheritanceType.TABLE_PER_CLASS—层次体系中的每一个具体实体类被映射到一个单独的表上,一个对象正好存放成其类型对应的特定表中的一行,该特定表包含了这一具体类的所有域的列,其中包括了任何继承的域,这意味着继承层次体系中的同层级的每个类自己都会有它们继承自超类的域的副本,在对超类进行查询的时候,会执行对单独表的一个联合(UNION)语句。
3. InheritanceType.JOINED—层次体系中的每个类都被表示成一个单独的表,不会造成域重复的现象出现,一个对象被分布存储在多个表中,占据每个表的一行,这些表组成了该对象的类继承的层次体系。子类与其超类之间的是(is-a)关系被表示成从“子表”到“超表”的外键关系,通过使用连接(JOIN)语句连接被映射的表来加载实体的所有域。
可在DataNucleus的文档中找到一个很好的关于JPA继承映射选项的图形化比较,其中包括了对@MappedSuperclass选项的描述。
现在要关心的问题是:在什么情况下,哪一种方法的效果最好?
单表(SINGLE_TABLE)—每个类层次体系一张表
单表策略的优点是简单,加载实体的时候只需要查询一张表,使用鉴别器列来确定实体的类型。在手工检查或者修改存储在数据库中的实体的时候,简单化也是有帮助的。
这一策略的缺点是当层次体系中有很多的类时,单表会变得非常大,此外,映射到层次体系中的子类的列应该是可为空的,对于大型的继承层次体系来说,这尤其令人讨厌。最后一点是,层次体系中的任何一个类的变化都需要改变单表,这使得单表策略只适用于小型的继承层次体系。
每个类一张表(TABLE_PER_CLASS)—每个具体类一张表
每个类一张表策略并不要求列是可设为空的,从而数据库模式(database schema)相对易于理解,因此也容易手工检查或者修改。
缺点是以多态方式加载实体需要所有被映射表的一个联合(UNION)语句,这可能会影响性能。最后一点是,对应于超类域的列的重复导致了数据库的设计是非规范化的,这使得很难针对重复的列执行聚合(SQL)查询。因此,这一策略最适合于宽但不深的继承层次体系,在这样的继承体系中,超类的域不会是你想要针对之进行查询的。
连接(JOINED)—每个类一张表
连接策略提供了漂亮的规范化数据库模式,不存在任何的重复列或是不必要的可空列,因此,其最适合大型的继承层次体系,无论是有深度的或者有宽度的都可以。
这一策略确实使得更难以手动地检查或者修改数据,此外,需要用来加载实体的连接(JOIN)操作可能会成为性能问题或是继承策略在规模方面的一个彻头彻尾的障碍。还需要注意的是,Hibernate在使用连接策略时并未正确地处理鉴别器列。
顺便说一下,在使用Hibernate代理的时候,一定要注意,延迟加载使用上述三种策略中的任一种映射的类时,总是返回一个代理,而该代理是超类的一个实例。
这些就是所有可用的选项了吗?
那么,总结一下,在从JPA的标准继承映射选项中进行选择时,以下规则适用:
小型的继承层次体系->单表。
宽度的继承层次体系->每个类一张表。
深度的继承层次体系->连接。
但如果继承的层次体系有相当的宽度或者非常深的话则会怎样呢?以及如果系统中的类经常要修改的话又会怎样呢?如同我们在为我们的Java EE部署自动化产品Deployit创建一个持久命令框架和一个灵活CMDB时所发现的那样,大型继承层次体系底部的具体类可能会经常改变,因此,这两个问题往往会一起得到一个肯定的答案,很幸运一个方案就可以解决这两个问题!
使用二进制大对象(blob)
首先需要注意的是,继承占据了对象关系阻抗失配中的很大分量,然后我们应该问自己这样的一个问题:为什么我们甚至会把所有这些经常改变的具体类映射到数据库的表上?如果对象数据已有真正的突破的话,那么把这些类存储在这样的数据库中可能情况会更好一些,而实际的情况是,这个世界是属于关系数据库的,所以这是不可能的。也可能存在这样的情况,对于对象模型的某一部分来说,关系模型实际上是有意义的,因为你希望执行查询以及让数据库来管理(外键)关系,但就一些地方来说,你实际上只是对简单的对象持久感兴趣。
一个很好的例子就是我在前面提到过的“持久命令框架”,该框架需要存储关于每个命令的一般信息,例如到其所属的“变更计划”(一种执行上下文)的引用、开始和结束时间、日志输出等等,但是它还需要存储代表了真正要完成的工作(对wsadimin或者wlst或者用例中类似工具的调用)的命令对象。
对于第一部分来说,继承模型是最适合的,对于第二部分来说,简单的序列化就可以实现,因此,我们先定义一个简单的接口,我们系统中的不同命令对象会实现这一接口:
public interface Command {
void execute();
}
然后我们创建一个存储了元数据(我们想存储在关系模型中的数据)和已序列化的命令对象这两者的实体:
@Entity
public class CommandMetaData {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
@ManyToOne
private ChangePlan changePlan;
private Date startOfExecution;
private Date endOfExecution;
@Lob
private String log;
@Lob
@Column(name = "COMMAND", updatable = false)
private byte[] serializedCommand;
@Transient
private Command command;
public CommandMetaData(Command details) {
serializedCommand = serializeCommand(details);
}
public Command getCommand() {
if (command != null) {
command = deserializeCommand(serializedCommand);
}
return command;
}
[... rest omitted ...]
}
因为@Lob这一注解的缘故,serializedCommand域是一个在数据库中被当成blob存储的字节数组,列名被显式地设为“COMMAND”,以防止“SERIALIZEDCOMMAND”这一缺省列名在数据库模式中出现。
把Command域标记为@Transient以防止其被存储到数据库中。
在CommandMetaData对象创建的时候,一个Command对象被传入,构造函数序列化该命令对象并把结果存放在serializedCommand域中。在那之后,命令就不能够被修改了(不存在setCommand()方法),因此,serializedCommand能够被标记为不可更新的,这可以防止每次CommandMetaData的其他域(例如log域)被更新时,这一相当大的blob域被写入数据库中。
每次getCommand方法被调用时,如果需要的话命令会被反序列化,然后被返回。如果该对象在多个并发线程中使用的话,可以把getCommand标记为同步的(synchronized)。
关于这一做法需要注意一些事情:
序列化方法的使用影响了这一做法的灵活性,标准的Java序列化很简单,但是不能很好地处理变更的类,XML可以是一种选择,但是会带来其本身的版本问题,选择合适的序列化机制这一问题就作为一个留给读者的练习。
虽然blob已经存在了一段时间,一些数据库仍对它们有所争执,例如,在Hibernate和Oracle中使用blob可能就会非常棘手。
在上面介绍的方法中,在对象已被序列化之后,任何对该Command对象的更改都不会被存储,巧妙地利用@PrePersist和@PreUpdate这些生命周期钩子就可以解决该问题。
这个半对象数据库/半关系数据库的持久方法对我们来说效果相当好,我很想听听其他人是否尝试过同样的做法,以及进展如何。或者,你考虑过这些问题的其他解决方案吗?
分享到:
相关推荐
总结,JPA提供了强大的关联和继承映射机制,让开发者能更便捷地处理数据库操作。正确理解和运用这些特性,可以显著提升Java应用程序的数据管理效率和代码质量。在设计实体模型时,应根据业务需求灵活选择合适的映射...
### JPA映射关系详解 Java Persistence API (JPA) 是一种用于管理关系型数据库中的数据的标准 Java 技术。JPA 提供了一种对象关系映射 (ORM) 方法来处理数据库,允许开发者以面向对象的方式操作数据库。本文将详细...
接下来,我们需要创建一个继承自`JpaRepository`的接口,例如`UserService`。这个接口会自动提供一些基本的CRUD操作。为了实现分页查询,可以使用`Pageable`接口作为方法参数,Spring Data JPA会自动处理分页逻辑。...
JPA映射关系,多种映射关系的图解,适合EJB初学者,对JPA映射关系的了解.
这些注解可以用来指定扫描的表映射实体 Entity 的目录、表 repository 目录、开启 JPA 审核能力等。 在 application.properties 文件中,可以配置一些数据库连接信息,例如数据库 URL、用户名、密码等。同时,也...
在处理关联关系时,Spring Data JPA提供了懒加载和急加载(Eager vs Lazy Fetching)机制,通过配置实体类的属性映射,可以在需要时按需加载关联的数据,避免了N+1查询问题。同时,它还支持级联操作(Cascade ...
在这个**“JPA视频教程_使用jpa映射关联和继承”**中,我们将深入探讨如何利用JPA来处理实体之间的关联和类的继承关系。 **1. JPA 注解** JPA 注解是实现ORM的主要手段,它们可以直接在实体类上声明,用于定义...
在Java世界中,Java Persistence API(JPA)是一种标准的ORM(对象关系映射)框架,用于管理和持久化Java应用程序中的数据。它允许开发者用面向对象的方式操作数据库,而无需直接编写SQL语句。本教程重点讲解如何...
在Java Persistence API (JPA) 中,实体映射关系是数据库关系模型与Java对象模型之间的桥梁,用于在ORM(对象关系映射)框架下管理数据。JPA 提供了多种映射关系,使得开发者能够方便地处理不同类型的关联。下面我们...
一共有三个分卷。全部下载才能解压。 这本书不错,值得一看。
1:JPA: : 2:对象数据库: ://www.objectdb.com/ 3:JPA 性能基准: ://www.jpab.org/All/All/All.html 支持功能 支持所有蓝图功能, 除外 支撑 支持 支持 Java 5、6 或 7 支持 JPA 你需要哪一个取决于你想用...
《精通Hibernate:Java对象持久化技术详解》这本书深入剖析了Hibernate这一流行的Java对象关系映射(ORM)框架,旨在帮助开发者全面理解并熟练掌握Hibernate的使用。Hibernate是Java开发中的重要工具,它简化了...
**JPA(Java Persistence API)**是Java平台上的一个标准,用于管理关系数据库中的数据,它简化了ORM(对象关系映射)的过程。本课程"05_传智播客JPA详解_日期_枚举等字段类型的JPA映射"深入讲解了JPA在处理特定字段...
在JPA中,继承同样被支持,以便将数据库表结构映射到类的继承层次结构。JPA提供了三种继承策略来处理继承关系: 1. **单一表继承(Single Table Inheritance,STI)**: 在这种策略下,所有继承类的数据都存储在一...
【标题】"notes_JPA_JSF:使用JSF和JPA实施来重建Notes项目"涉及的是在Java开发环境中,利用JavaServer Faces (JSF) 和 Java Persistence API (JPA) 技术重构建一个名为Notes的项目。JSF是Java EE平台上的一个用户...
jpa复杂查询,映射到具体的DTO上就比较复杂,需要类型完全匹配,还要求要建造构造函数,而且只能使用jpql查询,非常麻烦,querydsl也不方便,代码太长,后期维护的时候看上去也不直观。复杂sql还是采用原生sql比较...
刚学完了hibernate和ejb,自己又自学了下jpa,看了黎活明老师的视频,自己做了个多对多的例子,希望可以对你学习有所帮助,有心人可以联系我QQ770256061!
使用Hibernate和JPA进行单表继承的操作,还需要理解如何配置实体映射、如何进行CRUD操作以及如何执行复杂的查询。例如,你可以使用`Criteria API`或`JPQL`(Java Persistence Query Language)来编写查询,以根据...