写点什么

Java 高手速成 | 对象 - 关系的映射、映射对象标识符与 JPA API 的级联操作

作者:TiAmo
  • 2023-02-13
    江苏
  • 本文字数:3597 字

    阅读完需:约 12 分钟

Java高手速成 | 对象-关系的映射、映射对象标识符与JPA API的级联操作

01、对象-关系的映射概念

Java 对象和关系数据库存在一些简单的映射关系,比如 Customer 类与 CUSTOMERS 表映射,一个 Customer 对象与 CUSTOMERS 表中的一条记录映射,Customer 类的 name 属性与 CUSTOMERS 表的 NAME 字段映射。

但是,毕竟对象模型与数据库是按照不同的思路建立起来的,因此,在不少情况下,不存在一一对应的关系。比如 Java 对象之间可以双向关联,而数据库的表之间只有单向的参照关系,而且总是 many 方参照 one 方。表与表之间如果存在双向参照,需要通过连接表来建立对应关系。

补充

对象模型与数据库的建立思路到底有啥区别?对象模型需要提高代码的可重用,避免重复编码。而数据库需要减少数据的冗余,节省存储空间。

还有 Java 类有继承关系,关系数据库不存在继承关系。

JPA 与 Hibernate 会通过各种各样的映射注解来建立对象对数据库中记录的映射。以前 Hibernate3 以下的版本流行用 XML 格式的映射文件来建立映射,现在这个方式不那么流行了。更为普遍的是用注解进行映射。

以下是用 JPA 映射注解对 Customer 类与 CUSTOMERS 表进行映射。

@Entity@Table(name="CUSTOMERS") //Customer类和CUSTOMERS表映射public class Customer implements java.io.Serializable {  @Id  @GeneratedValue(generator="increment")  @GenericGenerator(name="increment", strategy = "increment")  @Column(name="ID") //Customer类的id属性和CUSTOMERS表的ID字段映射  private Long id;   //Customer类的name属性和CUSTOMERS表的NAME字段映射  @Column(name="NAME")  private String name;   //Customer类的age属性和CUSTOMERS表的AGE字段映射  @Column(name="AGE")  private int age;  //Customer类与Order类一对多关联  @OneToMany(mappedBy="customer",             targetEntity=Order.class,             orphanRemoval=true,             cascade=CascadeType.ALL)  private Set<Order> orders = new HashSet<Order>();  ……}
复制代码

02、映射对象标识符的基本原理

Java 语言按内存地址来识别或区分同一个类的不同对象,而关系数据库按主键值来识别或区分同一个表的不同记录。Hibernate 使用 OID 来统一两者之间的矛盾,OID 是关系数据库中的主键(通常为代理主键)在 Java 对象模型中的等价物。

在运行时,Hibernate 根据 OID 来维持 Java 对象和数据库表中记录的对应关系。例如:

Transaction tx = session.beginTransaction();Customer c1=session.get(Customer.class, Long.valueOf(1));Customer c2=session.get(Customer.class, Long.valueOf(1));Customer c3=session.get(Customer.class, Long.valueOf(3));System.out.println(c1==c2);System.out.println(c1==c3); tx.commit();
复制代码

在以上程序中,三次调用了 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:

private Long id;private void setId(Long id){  this.id=id;}public Long getId(){  return id;}
复制代码

 在持久化类中,用来自 JPA API 的 @Id 注解和 @GeneratedValue 注解来映射对象标识符,例如:

@Id@GeneratedValue(strategy = GenerationType.IDENTITY)@Column(name="ID")private Long id;
复制代码

以上 @Id 注解表明 id 属性是 OID,@GeneratedValue 注解设定如何为 OID 赋值,它的 strategy 属性指定标识符生成策略。JPA API 通过 GenerationType 枚举类型定义了四种标识符生成策略:

● GenerationType.AUTO:根据标识符的数据类型以及数据库对自动生成标识符的支持方式,来选择具体的标识符生成器,如 identity、uuid 或 sequence 等。

● GenerationType.IDENTITY:由数据库自动生成标识符。

● GenerationType.SEQUENCE:由数据库中的特定序列来生成标识符。

● GenerationType.TABLE:由用户自定义的表来生成标识符。

对于以上标识符生成策略,Hibernate 会通过相应的标识符生成器来实现这些标识符生成策略。例如以下代码通过 @SequenceGenerator 注解设置了具体的序列化标识符生成器:

@Id  @GeneratedValue(    strategy = GenerationType.SEQUENCE,    generator = "sequence-generator"  )  @SequenceGenerator( //具体的序列化标识符生成器    name = "sequence-generator",    sequenceName = "hibernate_sequence"  )  @Column(name="ID")  private Long id;
复制代码

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”:

@OneToMany(mappedBy="parentCategory",  targetEntity=Category.class) @org.hibernate.annotations.Cascade(  org.hibernate.annotations.CascadeType.SAVE_UPDATE) private Set<Category> childCategories = new HashSet<Category>(0);
复制代码

Category 类是具有自身双向关联的类,它的 childCategories 属性以及 parentCategory 属性,进行了如下映射:

@OneToMany(mappedBy="parentCategory",             targetEntity=Category.class,             cascade=CascadeType.ALL,             fetch=FetchType.EAGER)private Set<Category> childCategories = new HashSet<Category>(0); //子商品类别 @ManyToOne(targetEntity =Category.class,            cascade=CascadeType.ALL,            fetch=FetchType.EAGER)@JoinColumn(name="CATEGORY_ID")private Category parentCategory; //父商品类别
复制代码

对于以上两个属性,它们的级联操作都是 CascadeType.ALL,这意味着对当前的 Category 对象进行特定操作时,会对所关联的父类别 Category 对象,以及所关联的所有子类别 Category 对象进行同样的级联操作。

另外,为了保证从数据库中加载一个 Category 对象时,会立即加载所关联的父类别和子类别 Category 对象,采用了立即检索策略:FetchType.EAGER。


发布于: 刚刚阅读数: 6
用户头像

TiAmo

关注

有能力爱自己,有余力爱别人! 2022-06-16 加入

CSDN全栈领域优质创作者;阿里云创作者社区专家博主、技术博主、星级博主、阿里云ACE;华为云享专家;

评论

发布
暂无评论
Java高手速成 | 对象-关系的映射、映射对象标识符与JPA API的级联操作_Java_TiAmo_InfoQ写作社区