Java 高手速成 | 对象 - 关系的映射、映射对象标识符与 JPA API 的级联操作
01、对象-关系的映射概念
Java 对象和关系数据库存在一些简单的映射关系,比如 Customer 类与 CUSTOMERS 表映射,一个 Customer 对象与 CUSTOMERS 表中的一条记录映射,Customer 类的 name 属性与 CUSTOMERS 表的 NAME 字段映射。
但是,毕竟对象模型与数据库是按照不同的思路建立起来的,因此,在不少情况下,不存在一一对应的关系。比如 Java 对象之间可以双向关联,而数据库的表之间只有单向的参照关系,而且总是 many 方参照 one 方。表与表之间如果存在双向参照,需要通过连接表来建立对应关系。
补充
对象模型与数据库的建立思路到底有啥区别?对象模型需要提高代码的可重用,避免重复编码。而数据库需要减少数据的冗余,节省存储空间。
还有 Java 类有继承关系,关系数据库不存在继承关系。
JPA 与 Hibernate 会通过各种各样的映射注解来建立对象对数据库中记录的映射。以前 Hibernate3 以下的版本流行用 XML 格式的映射文件来建立映射,现在这个方式不那么流行了。更为普遍的是用注解进行映射。
以下是用 JPA 映射注解对 Customer 类与 CUSTOMERS 表进行映射。
02、映射对象标识符的基本原理
Java 语言按内存地址来识别或区分同一个类的不同对象,而关系数据库按主键值来识别或区分同一个表的不同记录。Hibernate 使用 OID 来统一两者之间的矛盾,OID 是关系数据库中的主键(通常为代理主键)在 Java 对象模型中的等价物。
在运行时,Hibernate 根据 OID 来维持 Java 对象和数据库表中记录的对应关系。例如:
在以上程序中,三次调用了 Session 的 get()方法,分别加载 OID 为 1 或 3 的 Customer 对象。以下是 Hibernate 三次加载 Customer 对象的流程:
(1)第一次加载 OID 为 1 的 Customer 对象时,先从数据库的 CUSTOMERS 表中查询 ID 为 1 的记录,再创建相应的 Customer 实例,把它保存在 Session 缓存中,最后把这个对象的引用赋值给变量 c1。
(2)第二次加载 OID 为 1 的 Customer 对象时,直接把 Session 缓存中 OID 为 1 的 Customer 对象的引用赋值给 c2,因此 c1 和 c2 引用同一个 Customer 对象。
(3)当加载 OID 为 3 的 Customer 对象时,由于在 Session 缓存中还不存在这样的对象,所以必须再次到数据库中查询 ID 为 3 的记录,再创建相应的 Customer 实例,把它保存在 Session 缓存中,最后把这个对象的引用赋值给变量 c3。
因此,表达式 c1== c2 的结果为 true,表达式 c1==c3 的结果为 false。
与表的代理主键对应,OID 也是整数类型,Hibernate 允许在持久化类中把与代理主键对应的 OID 定义为以下类型:
●short(或包装类 Short):2 个字节,取值范围是:-2^15 ~ 2^15-1
●int(或包装类 Integer):4 个字节,取值范围是:-2^31 ~ 2^31-1
●long(或包装类 Long):8 个字节,取值范围是:-2^63 ~ 2^63-1
●java.math.BigInteger 类:大整数类型。
●java.math.BigDecimal 类:大浮点数类型。尽管它是浮点数,实际上 Hibernate 的内置标识符生成器仍然按照整数递增的方式为 OID 赋值。
为了保证持久化对象的 OID 的唯一性和不可变性,通常由 Hibernate 或底层数据库来给 OID 赋值。因此,可以把持久化类的 OID 的 setId()方法设为 private 类型,以禁止 Java 应用程序随便修改 OID。而把 getId()方法设为 public 类型,这使得 Java 应用程序可以读取持久化对象的 OID:
在持久化类中,用来自 JPA API 的 @Id 注解和 @GeneratedValue 注解来映射对象标识符,例如:
以上 @Id 注解表明 id 属性是 OID,@GeneratedValue 注解设定如何为 OID 赋值,它的 strategy 属性指定标识符生成策略。JPA API 通过 GenerationType 枚举类型定义了四种标识符生成策略:
● GenerationType.AUTO:根据标识符的数据类型以及数据库对自动生成标识符的支持方式,来选择具体的标识符生成器,如 identity、uuid 或 sequence 等。
● GenerationType.IDENTITY:由数据库自动生成标识符。
● GenerationType.SEQUENCE:由数据库中的特定序列来生成标识符。
● GenerationType.TABLE:由用户自定义的表来生成标识符。
对于以上标识符生成策略,Hibernate 会通过相应的标识符生成器来实现这些标识符生成策略。例如以下代码通过 @SequenceGenerator 注解设置了具体的序列化标识符生成器:
03、JPA API 的级联操作
在 JPA API 中,javax.persistence.CascadeType 类中定义了一些常量,分别表示特定的级联操作:
●CascadeType.PERSIST :当通过 EntityManager 的 persist()方法来保存当前对象时,会级联保存所有关联的新建的临时对象。
●CascadeType.REMOVE :当通过 EntityManager 的 remove()方法来删除当前持久化对象时,会级联删除所有关联的持久化对象。
●CascadeType.DETACH :当通过 EntityManager 的 detach()方法来从持久化缓存中清除当前对象时,会级联清除所有关联的对象。
●CascadeType.MERGE :当通过 EntityManager 的 merge()方法来融合当前对象时,会级联融合所有关联的对象。
●CascadeType.REFRESH :当通过 EntityManager 的 refresh()方法刷新当前对象时,会级联刷新所有关联的对象。
●CascadeType.ALL 包含了以上所有的级联操作行为。
当通过注解来映射持久化类时,如果希望使用底层 Hibernate 的一些级联特性,那么还可以使用 org.hibernate.annotations. CascadeType 类的一些常量,例如:
●org.hibernate.annotations.CascadeType.LOCK:当通过底层 Session 的 lock()方法把当前游离对象加入到持久化缓存中时,会把所有关联的游离对象也加入到持久化缓存中。
●org.hibernate.annotations.CascadeType.REPLICATE:当通过底层 Session 的 replicate()方法复制当前对象时,会级联复制所有关联的对象。
●org.hibernate.annotations.CascadeType.SAVE_UPDATE:当通过底层 Session 的 save()、update()及 saveOrUpdate()方法来保存或更新当前对象时,会级联保存所有关联的新建的临时对象,并且级联更新所有关联的游离对象。
例如以下 @OneToMany 注解的 cascade 属性的取值为“org.hibernate.annotations.CascadeType.SAVE_UPDATE”:
Category 类是具有自身双向关联的类,它的 childCategories 属性以及 parentCategory 属性,进行了如下映射:
对于以上两个属性,它们的级联操作都是 CascadeType.ALL,这意味着对当前的 Category 对象进行特定操作时,会对所关联的父类别 Category 对象,以及所关联的所有子类别 Category 对象进行同样的级联操作。
另外,为了保证从数据库中加载一个 Category 对象时,会立即加载所关联的父类别和子类别 Category 对象,采用了立即检索策略:FetchType.EAGER。
版权声明: 本文为 InfoQ 作者【TiAmo】的原创文章。
原文链接:【http://xie.infoq.cn/article/27513ea77178c788c0e8098a2】。文章转载请联系作者。
评论