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

TiAmozhang
全栈领域优质创作者
博客专家认证
2023-02-09 09:34:23
加精

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。
 

...全文
99 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

18,532

社区成员

发帖
与我相关
我的任务
社区描述
分享开发工作中的心得,记录编程过程中的所想所悟!
harmonyos华为云微信小程序 个人社区 江苏省·常州市
社区管理员
  • TiAmo zhang
  • 小威要向诸佬学习呀
  • bit..
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧