一对一关系
在面向对象的世界里,类 A 和类 B 之间形成一对一关系必须满足如下条件:
1. 对象 A1 引用了对象 B1;
2. 类 A 的其它对象 An 不能引用同样的对象 B1.
在关系数据库中,我们通常使用唯一外键的方式来实现一对一关系,下面这个图说明了这种的情况。
图 1. 关系数据库中的一对一关系
下面开始介绍 OpenJPA 中实现实体之间一对一关联关系的相关知识,为了说明的需要,我们首先定义一个简单的应用场景。
模拟场景
假定开发者要完成一个图书馆管理系统,我们需要记录书的基本信息如编号、书名、出版日期等基本信息,还需要记录书的前言,序等信息。
为了说明实体之间的一对一关系,我们将书设计成一个类(Book),包括书的编号和名称两个属性,同时将书的前言设计成另外一个类(BookExtend),它包括书的编号和前言两个属性。由于一本书有前言而且也不可能有其它书的前言部分会和它一样,所以类 Book 和 BookExtend 之间很自然的形成了一对一的关系。这两个类的属性以及类之间的关系如下图所示。
图 2. 类之间的一对一关系
[注]:为了说明的简单,本例子设计时每个对象只选择了必要的属性。
描述一对一关系
在 OpenJPA 中,开发者用来描述实体之间一对一关系时可选择的注释包括 javax.persistence.OneToOne 和 javax.persistence.JoinColumn.其中 javax.persistence.OneToOne 注释是必须使用的,它被用来声明类和类之间存在着一对一关系,javax.persistence.JoinColumn 注释是可选的,开发者使用 JoinColumn 注释来声明两个类在数据库中对应的表之间关联时的细节,包括主表中关联字段的名称、从表中使用什么字段来进行关联等。
javax.persistence.OneToOne
javax.persistence.OneToOne 注释支持如下 5 个属性,它们可以被开发者用来定义实体和实体之间一对一关联关系的细节内容。
target Entity
targetEntity 属性是 Class 类型的属性。定义实体一对一关系中处于从属地位的实体类的类型。如果没有为该属性设置值,OpenJPA 容器默认 targetEntity 属性的值是该成员属性对应的类类型,所以实体关系定义时通常不需要为 targetEntity 属性设置值。
mappedBy
mappedBy 属性是 String 类型的属性。mappedBy 属性的值是当前实体在关联实体中的属性名称,使用 mappedBy 可以定义实体类之间的双向关系。如果类之间是单向关系,不需要提供定义,如果类和类之间形成双向关系,我们就需要使用这个属性进行定义,否则可能引起数据一致性的问题。
以演示场景中 Book 和 BookExtend 实体为例,假设我们只定义 Book 类有 BookExtend 类型的属性,而 BookExtend 并没有 Book 类型的属性,那么说明 Book 和 BookExtend 实体之间是单向关系;如果 BookExtend 中也定义了 Book 属性,那么 Book 和 BookExtend 实体之间就构成了双向关系。
cascade
cascade 属性的类型是 CascadeType[] 类型。cascade 属性定义实体和实体之间的级联关系。使用 cascade 属性定义的级联关系将被容器视为对当前类对象及其关联类对象采取相同的操作,而且这种关系是递归调用的。
以演示场景中 Book 和 BookExtend 实体为例:如果设置 Book 和 BookExtend 存在级联关系,那么删除 Book 时将同时删除它所对应的 BookExtend 对象。而如果 BookExtend 还和其它的对象之间有级联关系,那么这样的操作会一直递归执行下去。
cascade 的值只能从 CascadeType.PERSIST(级联新建)、CascadeType.REMOVE(级联删除)、CascadeType.REFRESH(级联刷新)、CascadeType.MERGE(级联更新)中选择一个或多个。还有一个更方便的选择是使用 CascadeType.ALL,表示选择上面全部四项。
fetch
fetch 属性是 FetchType 类型的属性。可选择项包括:FetchType.EAGER 和 FetchType.LAZY.前者表示关联关系的从类在主类加载的时候同时加载,后者表示关联关系的从类在自己被访问时才加载。默认值是 FetchType.EAGER.
optional
optional 属性是 boolean 类型的属性。optional 属性用于定义关联关系的从类对象是否必须存在。如果设置为 false,那么该属性就不能设置为 null.默认值是 true.
javax.persistence.OneToOne 用法举例
public class Book { // 其它实体映射内容… /* * 使用 OneToOne 注释表示该属性和 Book 类形成一对一关系,OneToOne * 注释的 option 属性设为 True 表示该对象可以不存在,cascade 属性 * 设置为 CascadeType.ALL,表示 Book 和 BookExtend 对象级联新建、 更新、删除、刷新 */ @OneToOne(optional=true,cascade=CascadeType.ALL) public BookExtend bookExtend; } |
javax.persistence.JoinColumn
javax.persistence.JoinColumn 注释可以和 javax.persistence.OneToOne 注释一起使用,用于定义关联关系中的主类在数据库中对应的表通过什么字段和关联关系中的从类的主键进行关联,这个注释是可选的,如果不提供该注释,OpenJPA 会默认使用“对象名_ID”和关联表的主键字段进行关联。
以演示场景中 Book 和 BookExtend 实体为例:如果 Book 的 bookExtend 属性没有使用 javax.persistence.JoinColumn 注释进行声明,我们使用 OpenJPA 提供的 Mapping Tool 工具生成表格的时候,Book 类对应的表 Book 中将自动加入列 bookExtend_ID,它的类型将和 BookExtend 对应表的主键字段id类型保持一致。
JoinColumn 注释支持两个重要属性:name 和 referencedColumnName 属性。
name
name 属性的类型是 String 类型。name 属性用于指定关联关系中的主类对应的表中和关联关系中的从类的主键进行关联的字段的名称。以演示场景中 Book 和 BookExtend 实体的关系为例:如果 Book 实体对应的表使用“beID”字段和 BookExtend 实体对应表的主键进行对应,我们可以在 Book 类中为 bookExtend 属性提供 javax.persistence.JoinColumn 注释,设置它的 name 属性为“beID”。
referencedColumnName
referencedColumnName 属性的类型是 String 类型。referencedColumnName 属性指定关联关系中的从类与关联关系中的主类对应的表之间形成关联关系的字段名称,通常用于关联关系中的从类的关联字段不是自己的主键的情况。以演示场景中 Book 和 BookExtend 实体的关系为例:BookExtend 表中默认使用 Id 字段和 Book 类的某个字段进行关联,但如果实际情况下 BookExtends 表需要使用“myID”字段和 Book 表进行关联,我们就可以设置 javax.persistence.JoinColumn 注释的属性值为“myID”。
javax.persistence.JoinColumn 用法举例
public class Book { /* |
根据模拟场景的需求,结合我们前面学习到的描述实体之间一对一关联关系的知识,我们可以采用如下设计:
1. Book 类和 BookExtend 之间存在一对一关联关系;
2. Book、BookExtend 对应的表的主键字段由 MySQL 自动生成;
3. Book 表中参与关联关系的字段名为“beID”;
4. BookExtend 表中参与关联关系的字段使用默认字段“ID”;
5. Book 类和 BookExtend 类之间存在全部级联关系;
6. 不是每一个 Book 对象都需要有对应的 BookExtend 对象。
根据这样的设计,我们可以开始编写实体 Book 和 BookExtend 对应的持久化类代码,下面是作者编写的两个实体类的全部代码,大家可以参考代码中加入的大量注释学习如何使用注释来描述实体和实体之间的一对一关联关系。
Book 类
1. package org.vivianj.openjpa.beans; 2. 3. import javax.persistence.Basic; 4. import javax.persistence.CascadeType; 5. import javax.persistence.Column; 6. import javax.persistence.Entity; 7. import javax.persistence.GeneratedValue; 8. import javax.persistence.GenerationType; 9. import javax.persistence.Id; 10. import javax.persistence.Inheritance; 11. import javax.persistence.InheritanceType; 12. import javax.persistence.JoinColumn; 13. import javax.persistence.OneToOne; 14. 15. /** 16. * Book 用于表征系统中的书籍对象,它有三个属性 id - 书籍编号, * 书籍编号将由 MySQL 数据库自动生成 name - 书名 bookExtend – 17. * 书的扩展信息,和 BookExtend 是一对一(OneToOne)关系 18. */ 19. 20. @Entity(name = "Book") 21. public class Book { 22. /* Id 注释表示该字段是标识字段 */ 23. @Id 24. /* 25. * GeneratedValue 注释定义了该标识字段的产生方式,我们的演示系统中 26. * id 由 MySQL 数据库字段自动生成,因此选择 GenerationType.IDENTITY 27. */ 28. @GeneratedValue(strategy = GenerationType.IDENTITY) 29. /* 30. * Column 注释的 name 属性定义了该类属性对应的数据字段的名称, * 为了最大限度保持系统和数据库之前的独立性,建议使用大写字符 31. */ 32. @Column(name = "ID") 33. public int id; 34. 35. /* Basic 注释表示该属性是基本属性 */ 36. @Basic 37. /* 38. * Column 注释的 name 属性定义了该类属性对应的数据字段的名称, * 为了最大限度保持系统和数据库之前的独立性,建议使用大写字符 39. */ 40. @Column(name = "NAME") 41. public String name = null; 42. 43. /* 44. * 使用 OneToOne 注释表示该属性和 Book 类形成一对一关系,OneToOne 45. * 注释的 option 属性设为 True 表示该对象可以不存在,cascade 属性 46. * 设置为 CascadeType.ALL,表示 Book 和 BookExtend 对象级联新建、 更新、删除、刷新 47. */ 48. @OneToOne(optional = true, cascade = CascadeType.ALL) 49. /* 使用 JoinColumn 注释设置两个对象对应数据库表之间的关联字段 */ 50. @JoinColumn(name = "extendID") 51. public BookExtend bookExtend; 52. } |
BookExtend 类
1. package org.vivianj.openjpa.beans; 16. @Entity |