Hibernate实战_笔记23(映射持久化)

理解实体和值类型

      实体是表述一级业务对象的持久化类型。换句话说,你在一个应用程序中必须处理的有些类和类型更为重要,它一般使得其他的类和类型变得比较不重要。你可能会认同这种说法:在CaveatEmptor中,Item是比String更重要的类。User可能比Address更重要。是什么使得有些东西变得重要呢?让我们换个角度来看这个问题。

细粒度的领域模型

      Hibernate的一个主要目标是支持细粒度的领域模型,过去我们把它分离出来作为领域模型最重要的必要条件。这是我们使用POJO的一个原因。粗略地说,细粒度意味着类比表更多。
      在领域模型中,可以使用相同的方法,把两个地址表示为User类的6个字符串值属性。但是用Address类给它建立模型会更好,在这个模型中User有billingAddress和homeAddress属性,从而给一个表使用三个类。
      这个领域模型实现了更高的凝聚力和更好的代码重用,并且它比使用固定类型系统的SQL系统更容易理解。
      Hibernate强调实现类型安全和行为的细粒度类的有效性。例如,许多人把电子邮件地址作为User的一个字符串值的属性建立模型。一种更成熟的方法是定义EmailAddress类,它添加更高级的语义和行为——它可以提供sendEmail()方法。
      这个粒度问题把我们引向了ORM中极其重要的一个特征。在Java中,所有的类都是平等的——所有的对象都有它们自己的同一性和生命周期。

定义概念

      两个人住在同一幢公寓里,他们都在CaveatEmptor注册了用户帐号。每个帐号一般都由User的一个实例表示,因此你有两个实体实例。在CaveatEmptor模型中,User类与Address类有一个homeAddress关联。两个User实例都在运行时引用同一个Address实例,还是每个User实例都引用它自己的Address?如果Address假定要支持共享的运行时引用,它就是个实体类型。如果不支持,它就可能是个值类型,因此通过一个自己的实体实例(它也提供同一性)依赖于单个引用。
Hibernate产生了下列本质的特征:
      实体类型的对象有它自己的数据库同一性(主键值)。对实体实例的对象引用被持久化为数据库中的引用(一个外键值)。实体有它自己的生命周期;它可以独立于任何其他的实体而存在。CaveatEmptor中的例子是User、Item和Category。
      值类型的对象没有数据库同一性;它属于一个实体实例,并且它的持久化状态被嵌入到自身实体的表行中。值类型没有标识符或者标识符属性。值类型实例的寿命受它自己的实体实例的寿命限制。值类型不支持共享引用,最明显的值类型是类,比如String和Integer,但是所有的JDK类都被当作值类型。用户自定义的类也可以被映射为值类型;例如,CaveatEmptor有Address和MonetaryAmount。

识别实体和值类型

      你可能发现给UML类图添加固定的信息很有帮助,以便可以立即看到和区分实体和值类型。这个实践也迫使你对所有的类考虑这种区别,这是可选映射和执行良好的持久化层的第一步。


      Item和User类是明显的实体。它们各自都有自己的同一性,它们的实例有来自许多其他实例的引用(共享引用),并有独立的生命周期。
把Address视同一个值类型也很容易:一个特定的Address实例只被单个User实例引用。你知道,因为关联已经作为一个复合来创建,其中的User实例已经被设计成完全负责被引用Address实例的生命周期。因此,Address对象不能被任何其他的实例引用,也不需要它们自己的同一性。
      Bid类是个问题。在面向对象的模型中,你表达一个组合(带有钻石符号的Item和Bid之间的关联),且Item管理所有Bid对象生命周期,并且Item有着对所有Bid对象的一个引用(一个引用集合)。这看起来似乎有道理,因为如果Item不再存在了,出价也就没用了。但是同时,还有另一个对Bid的关联:Item可能持有对它sucessfulBid的一个引用。这个成功的出价也必须是集合引用的其中一个出价,但是这在UML中没有表达出来。不管怎样,你必须处理对Bid实例可能的共享引用,因此Bid类必须是一个实体。它有一个依赖的生命周期,但是必须有自己的同一性来支持共享引用。
你会经常发现这种混合行为;然而,你的第一反应应该是使所有的东西都成为值类型的类,并且只有当绝对必要时才把它提升为实体。试着简化关联:例如,集合有时候增加了无益的复杂性。不要映射Bid引用的一个持久化集合,而是可以编写一个查询,来获得Item的所有出价(后面将再次回到这个话题)。
随着下一个步骤,把领域模型图拿走,并对所有实体和值类型实现POJO。你必须小心3件东西:
1)共享引用——编写POJO类,以一种避免对值类型实例共享引用的方式。
2)生命周期依赖——如前所述,一个值类型实例的生命周期受它自己的实体实例限制。如果User对象被删除了,它的Address依赖对象也必须被删除。
3)同一性——实体类几乎在任何情况下都需要标识符属性。用户自定义的值类型类(和JDK类)没有标识符属性,因为实例通过自己的实体被标识。

映射带有同一性的实体

      在讨论术语(比如数据库同一性和Hibernate管理同一性的方式)之前,理解对象同一性和对象等同性之间的区别很重要。接下来探讨对象同一性和等同性如何与数据库(主键)的同一性要关联。
理解Java同一性和等同性
      Java开发人员理解Java对象同一性和等同性之间的区别。对象同一性(==)是通过Java虚拟机定义的一个概念。如果两个对象引用指向相同的内存位置,它们就是同一的。
      另一方面,对象等同性则通过实现equals()方法的类定义的一个概念,有时候也被认为等值。等值意味着两个不同(非同一)的对象有着相同的值。如果String的两个不同实例表示顺序相同的字符,它们就是等值的。
      持久化使事情变得复杂起来。利用对象/关系持久化,持久化对象是数据库表的一个特定行的内存表示。连同Java同一性(内存位置)和对象等同性一起,你还要选择数据库同一性(在持久化数据仓库中的位置)。现在你有3种识别对象的方法:
1)如果对象在JVM中占据着相同的内存位置,它们就是同一的。这可以通过使用==操作符进行检查。这个概念称作对象同一性。
2)如果对象有着相同的值,它们就是相等的,如equals(object o)方法定义的一样。不显式覆盖这个方法的类,继承了由java.lang.Object定义的实现,它比较对象同一性。这个概念称作等同性。
3)如果存储在一个关系数据库中的对象表示相同的行,或者它们共享相同的表和主键值,它们就是同一的。这个概念称作数据库同一性。
现在要看看数据库同一性如何与Hibernate中的对象同一性相关联,以及数据库同一性在映射元数据中如何表达。

处理数据库同一性

Hibernate以两种方式把数据库同一性公开给应用程序:
1)持久化实例的标识符属性值
2)Session.getIdentifier(Object entity)返回的值
1、给实体添加标识符属性
给Category类实现一个标识符属性:
public class Category {

	private Long id;
	// ...
	public Long getId() {
		return id;
	}

	@SuppressWarnings("unused")
	private void setId(Long id) {
		this.id = id;
	}
}
标识符属性的Java类型,如此例子中的java.lang.Long,取决于TBL_CATEGORY表的主键类型,以及它在Hibernate元数据中如何被映射。
2、映射标识符属性
用<id>元素在Hibernate XML文件中映射一个普通的(非复合的)标识符属性:
<class name="Category" table="TBL_CATEGORY">
    <id name="id" column="CATEGORY_ID" type="long">
        <generator class="native"/>
    </id>
</class>
对于JPA实体类,你在Java源代码中用注解映射标识符属性:
@Entity
@Table(name = "TBL_CATEGORY")
public class Category {

	private Long id;
	// ...
	@Id
	@GeneratedValue(strategy=GenerationType.AUTO)
	@Column(name="CATEGORY_ID")
	public Long getId() {
		return id;
	}
	
	public void setId(Long id) {
		this.id = id;
	}
}
注意,即使你没有定义strategy,默认也是GenerationType.AUTO,因此完全可以省略这个属性。你还指定了一个数据库列——否则Hibernate会使用属性名称。Java属性类型暗指的映射类型是java.lang.Long。
当然,也可以对所有属性使用直接的字段访问,包括数据库标识符:
@Entity
@Table(name = "TBL_CATEGORY")
public class Category {

	@Id
	@Column(name = "CATEGORY_ID")
	private Long id;

	// ...
	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}
}
就像标准所定义的,当直接的字段访问被启用时,映射注解就被放在字段声明中。
给实体启用字段还是属性访问,这取决于强制的@Id注解的位置。在前面的例子中,它出现在字段中,因此这个类的所有属性都由Hibernate通过字段访问。在那之前的例子,在getId()方法上被注解的,使得能通过获取方法和设置方法访问所有的属性。
另一种方法,可以使用JPA XML描述符创建标识符映射:
<entity class="auction.model.Category" access="FIELD">
    <table name="TBL_CATEGORY"/>
        <attributes>
            <id name="id">
                <generated-value strategy="AUTO"/>
            </id>
        </attributes>
</entity>
除了用来测试Java对象同一性(a==b)和对象等同性(a.equals(b))的操作之外,现在可以用a.getId().equals(b.getId())测试数据库同一性。
在Hibernate中使用数据库同一性很简单且容易理解。选择一个好的主键(和键生成策略)可能就比较困难了。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值