Hibernate实战(第2版)学习笔记二

51.Hibernate也理解type="java.lang.String",因此它不必使用反射。这种方法不太适用的最重要案例是java.util.Date属性。默认情况下,Hibernate认为java.util.Date是一个timestamp映射。如果不希望日期和时间信息都持久化,就要显式地指定type=“time”或者type="date"。
利用JPA注解,自动侦测属性的映射类型,就像在Hibernate中一样。对于java.util.Date或者java.util.Calendar属性,Java Persistence标准要求用@Temporal注解选择精确度。另一方面,Hibernate Annotations放松了标准的规则,默认为TemporalType.TIMESTAMP选项是TemporalType.TIME和TemporalType.DATE.
52.Hibernate提供定义定制的映射类型时应用程序可能使用的几个接口。这些接口减少了创建新映射所涉及的工作,并使定制的类型免受Hibernate核心变化的影响。
扩展点如下:
(1)org.hibernate.usertype.UserType——基础的扩展点,用于多种情况。它为定制值类型实例的加载和存储提供基础的方法。
(2)org.hibernate.usertype.CompositeUserType——包含比基础的UserType更多方法的一个接口,通常把有关值类型的类的内部信息公开给Hibernate,例如单独的属性。然后可以在Hibernate查询中引用这些属性。
(3)org.hibernate.usertype.UserCollectionType——很少被用来实现定制集合的接口。实现这个接口的定制映射类型不是在属性映射中声明,而是只对定制的集合映射有用。如果想要持久化一个非JDK的集合,并持久保持额外的语义时,就必须实现这个类型。
(4)org.hibernate.usertype.EnhanceUserType——扩展了UserType并提供额外方法的接口,这些方法用来把值类型封送到XML表示法(或者从XML表示法中封送值类型),或者启用一个定制的映射类型,在标识符和辨别标志映射中使用。
(5)org.hibernate.usertype.UserVersionType——扩展了UserType并提供额外方法的接口,这些方法启用用于实体版本映射的定制映射类型。
(6)org.hibernate.usertype.ParameterizedType——一个有用的接口,可以与所有其他的接口合并,来提供配置设置——也就是说,原数据中定义的参数。
53.值类型(value type)的对象不具备数据库同一性,它属于一个实体实例,其持久化状态被嵌入到所拥有实体的表行中——至少,在实体有一个对值类型的单个实例的引用的情况下。如果实体类有一个值类型的集合(或者对值类型实例的引用的集合),就需要一张额外的表,即所谓的集合表。
在将值类型的集合映射到集合表之前,要记住,值类型的类没有标识符或者标识符属性。值类型实例的生命期限由所拥有的实体实例的生命期限决定。值类型不支持共享的引用。
54.开箱即用,Hibernate支持最重要的JDK集合接口。换句话说,它知道如何以持久化的方式保存JDK集合、映射和数组的语义。每个接口都有一个Hibernate支持的匹配实现,并且使用正确的组合很重要。Hibernate只包装已经在字段的声明中初始化的集合对象(或者如果不是正确的对象,有时就替换它)。
不扩展Hibernate,而是从下列集合中选择:
(1)使用<set>元素映射java.util.Set。使用java.util.HashSet初始化集合。它的元素顺序没有保存,并且不允许重复元素。这在典型的Hibernate应用程序中时最常见的持久化集合。
(2)可以使用<set>映射java.util.SortedSet,且sort属性可以设置成比较器或者用于内存排序的自然顺序。使用java.util.TreeSet实例初始化集合。
(3)可以使用<list>映射java.util.List,在集合表中用一个额外的索引列保存每个元素的位置。使用java.util.ArrayList初始化。
(4)可以使用<bag>或者<idbag>映射java.util.Collection。Java没有Bag接口或者实现;然而,java.util.Collection允许包语义(可能的重复,不报错元素顺序)。Hibernate支持持久化的包(它内部使用列表,但是忽略元素的索引)。使用java.util.ArrayList初始化包集合。
(5)可以使用<map>映射java.util.Map,保存键值对。使用java.util.HashMap初始化属性。
(6)可以使用<map>元素映射java.util.SortedMap,且sort属性可以设置为比较器或者用于内存排序的自然顺序。使用java.util.TreeMap实例初始化该集合。
(7)Hibernate使用<primitive-array>(对于Java基本的值类型)和<array>(对于其他的一切)支持数组(array)。但是他们何绍用在领域模型中,因为Hibernate无法包装数组属性。没有自己吗基础设施(BCI),就失去了延迟加载,以及为持久化集合优化过的脏检查、基本的便利和性能特性。
55.如果想要映射Hibernate不直接支持的集合接口和实现,就要告诉Hibernate定制集合的语义。Hibernate中的扩展点称作PersistentCollection;通常扩展其中现有的PersistentSet、PersistentBag或者PersistentList类中的一个。
56.允许重复元素的无序集合被称作包(bag)。奇怪的是,Java Collections框架没有包括包实现。然而,java.util.Collection接口有包语义,因此只需要一种相匹配的实现。你有两种选择:
(1)用java.util.Collection接口编写集合属性,并在声明中用JDK的一个ArrayList对它进行初始化。在Hibernate中用标准的<bag>或者<idbag>元素映射集合。Hibernate有一个内建的PersistentBag可以处理列表;但与包的约定一致,它忽略元素在ArrayList中的位置。换句话说,你得到了一个持久化的Collection。
(2)用java.util.List接口编写集合属性,并在声明中用JDK的一个ArrayList把它初始化。像前一个选项一样映射它,但是在领域模型类中公开了一个不同的集合接口。这种方法有效,但不建议使用,因为使用这个集合的客户端可能认为元素的顺序会始终被保存着,其实如果把它作为<bag>或者<idbag>映射,就并非如此了。
注意,主键的native生成器不支持<idbag>映射,必须制定一种具体的策略。
57.<list>映射需要把一个索引列(index column)新增到集合表。索引列定义元素在集合中的位置。因而,Hibernate能够保存集合元素的顺序。
持久化列表中的索引从0开始,可以改变它,例如在映射中使用<list-index base="1">。注意,如果数据库中的索引数字不连续,Hibernate就会把空元素添加到Java列表中。
58.虽然英语单词遭到惊人的滥用,单词sorted和ordered对于Hibernate持久化集合却是指不同的东西。排序集合(sorted collection)是指用一个Java比较器在内存中进行排序。有序集合(ordered collection)则指用一个包含order by子句的SQL查询在数据库级中排列。
如果把它映射为排序,Hibernate会相应地处理这个集合:
<map name="images" table="ITEM_IMAGE" sort="natural">
   <key column="ITEM_ID"/>
  <map-key column="IMAGENAME" type="string"/>
  <element type="string" column="FILENAME" not-null="true"/>
</map>
通过指定sort="natural",告诉Hibernate使用SortedMap,并根据java.lang.String的compareTo()方法对图片名称进行排序。如果需要一些其他的排序算法(例如反向字母顺序),可以在sort属性中指定实现java.util.Comparator的类名称。
像下面这样映射java.util.SortedSet(包含一个java.util.TreeSet实现):
<set name="images" table="ITEM_IMAGE" sort="natural">
<key column="ITEM_ID"/>
<element type="string" column="FILENAME" not-null="true"/>
</set>
包不可能被排序(可惜没有TreeBag),列表(list)也一样;列表元素的顺序由列表索引定义。
另一种方法,不转换到Sorted*接口(和Tree*实现),你或许想要使用一个链接映射(Linked Map),并在数据库端而不是内存中给元素排序。在Java类中保留Map/HashMap声明,并创建下列映射:
<map name="image" table="ITEM_IMAGE" order-by="IMAGENAME asc">
<key column="ITEM_ID"/>
<map-key column="IMAGENAME" type="string"/>
<element type="string" column="FILENAME" not-null="true"/>
</map>
order-by属性中的表达式是SQL order by 子句的一个片段。在这个例子中,在集合的加载期间,Hibernate按IMAGENAME列的升序排列集合元素。甚至可以在order-by属性中包括SQL函数调用;
可以按照集合表的任何列进行排列,Hibernate内部使用LinkedHashMap,它是保存关键元素的插入顺序的一个映射的变形。换句话说,就是在集合的加载期间,Hibernate把元素添加到集合的顺序,就是你在应用程序中见到的迭代顺序。用Set也可以完成同样的工作:Hibernate内部使用LinkedHashSet。在Java类中,属性是一般的Set/HashSet,但是Hibernate内部包含LinkedHashSet的包装再次通过order-by属性启用了排列.
<set name="images" table="ITEM_IMAGE" order-by="FILENAME asc">
<key column="ITEM_ID"/>
<element type="string" column="FILENAME" not-null="true"/>
</set>
也可以让Hibernate在集合的加载期间为你排列包的元素。Java集合属性是Collection/ArrayList或者List/ArrayList。Hibernate内部使用ArrayList来实现一个保存了插入迭代顺序的包:
<idbag name="images" table="ITEM_IMAGE" order-by="ITEM_IMAGE_ID desc">
<collection-id type="long" column="ITEM_IMAGE_ID">
<generator class="sequence"/>
</collection-id>
<key column="ITEM_ID"/>
<element type="string" column="FILENAME" not-null="true"/>
</idbag>
Hibernate内部用于集合映射的链接集合只在JDK1.4或者更高的版本中可用;更早的JDK没有LinkedHashMap和LinkedHashSet。有序包在所有的JDK版本中都可用;内部使用ArrayList。
59.组件的集合被类似地映射到JDK值类型的集合。唯一的区别是用<composite-element>代替<element>标签。有序的图片集(内部使用LinkedHashSet)可以像这样映射:
<set name="images" table="ITEM_IMAGE" order-by="IMAGENAME asc">
<key column="ITEM_ID"/>
<composite-element class="Image">
<property name="name" column="IMAGENAME" not-null="true"/>
<property name="filename" column="FILENAME" not-null="true"/>
<property name="sizeX" column="SIZEX" not-null="true"/>
<property name="sizeY" column="SIZEY" not-null="true"/>
</composite-element>
</set>
Hibernate没有提供<idset>或者除了<idbag>之外的任何代理标识符集合。
60.Hibernate Annotations包对包含值类型元素的集合映射支持非标准的注解,主要是org.hibernate.annotations.CollectionOfElements。
61.CMP关联被称作容器托管的关系(container managed relationsheip,CMR)是有理由的。CMP中的关联天生就是双向的。对关联的一侧所做的改变,会立即影响到另一侧。Hibernate和JPA关联天生都是单向的(unidirectional)。
在对象持久化的上下文中,我们不关注多个(many)是否意味着两个或者最大五个或者没有限制;只关注大多数关联的选择性(opeionality);不特别关系是否需要关联实例,或者关联的另一侧是否可以为空(意味着0对多和0对0的关联)。但是,这些是影响你在关系数据Schema中选择完整性规则和在SQL DDL中定义约束的重要方面。
61.public class Bid{
@ManyToOne(targerEntity=auction.model.Item.class)
@JoinColumn(name="ITEM_ID",nullable=false)
private Item item;
}
在这个映射中有两个可选的元素。首先,不一定要包括关联的targetEntity;它对于字段的类型来说是隐式的。显式的targetEntity属性在更复杂的领域模型中很有用。例如,在返回委托类(delegate class)——它模仿一个特定的目标实体接口——的一个获取方法中映射@ManyToOne时。
第二个可选的元素是@JoinColumn。如果没有什么外键列的名称,Hibernate会自动使用目标名称和目标实体的数据库标识符属性名称的一个组合。换句话说,如果没有添加@JoinColumn注解,外键列的默认名称则为item加id,用一条下划线隔开。然而,因为要使外键列为NOT NULL,所以无论如何都始终要用注解设置nullable=false。如果用Hibernate Tools生成Schema,@ManyToOne中的optional="false"属性也会在生成的列中导致一个NOT NULL约束。
在管理映射中还需要做的一件事,就是使它成为真正的双向关联映射。inverse属性告诉Hibernate,集合是<mang-to-one>关联在另一侧的一个镜像:
<class name="Item" table="ITEM">
<set name="bids" inverse="true">
<key column="ITEM_ID"/>
<one-to-many class="Bid"/>
</set>
</class>
在操作两个实例之间的链接时,如果没有inverse属性,Hibernate会视图执行两个不同的SQL语句,这两者更新同一个外键列。通过指定Inverse="true",显式地告诉Hibernate链接的哪一端不应该与数据库同步。在这个例子中,告诉Hibernate它应该把在关联的Bid端所做的变化传播到数据库,忽略只对bids集合所做的变化。
注意,Hibernate Schema导出工具始终对SQL DDL的生成忽略关联映射的Inverse侧。在这种情况下,BID表中的ITEM_ID外键列就获取了一个NOT NULL约束,因为已经对它做了与非反向的(noniverse)<many-to-one>映射中一样的声明。
<many-to-one>元素没有inverse属性,但是可以用update="false"和insert="false"映射它,以有效地忽略掉任何UPDATE或者INSERT语句。然后集合端就是非反向的,考虑把它用于外键列的插入或者更新。
再次通过JPA注解映射这个反向集合侧:
public class Item
{
@OneToMany(mappedBy="item")
private Set<Bid> bids=new HashSet<Bid>();
}
mappedBy属性相当于XML映射中的inverse属性;但是,它必须指定目标实体的反向属性。注意,这里不比再次指定外键列(它通过另一侧映射),因此不像XML那么冗长。
为了启用跨关联的这个传播性状态,得把cascade选项添加到XML映射:
<class name="Item" table="ITEM">
<set name="bids" inverse="true" cascade="save-update">
<key column="ITEM_ID"/>
<one-to-many class="Bid"/>
</set>
</class>
如果特定的Bid在集合中被持久化的Item引用,cascade="save-update"属性便为Bid实例启用了传播性持久化。
cascade属性是有方向性的:它只应用到关联的一端。也可以把cascade=“save-update”添加到Bid映射中的<many-to-one>关联,但是由于出价是在货品之后创建的,这么做并没有什么意义。
JPA也在关联中支持级联实体实例状态:
public class Item{
@OneToMany(cascade={CascadeType.PERSIST,CascadeType.MERGE},mappedBy="item")
private Set<Bid> bids=new HashSet<Bid>();
}
级联选项是你想要它变成传播性的每一个操作(per operation)。对于原生的Hibernate,用cascade="save-update"把save和update操作级联到被关联的实体。Hibernate的对象状态关联始终把这两个东西绑在一起。在JPA中,(几乎)相当的操作是persist和merge。
Hibernate(和JPA)为此提供了一个级联选项。可以给delete操作启用级联:
<set name="bids" inverse="true" cascade="save-update,delete">
62.由主键关联而相关的两张表中的行共享相同的主键值。这种方法的主要困难在于,确保被关联的实例在保存对象时分配到了相同的主键值。
把实体关联映射到共享主键实体的XML映射元素是<one-to-one>.首先在User类中需要一个新属性:
public class User
{
private Address shippingAddress;
}
接下来,在User.hbm.xml中映射关联:
<one-to-one name="shippingAddress" class="Address" cascade="save-update">
你给这个模型添加了一个固有的级联选项:如果User实例变成持久化,通常它的shippingAddress也要变成持久化。
63.现在可以给Address对象使用特殊的foreign标识符生成器了:
<class name="Address" table="ADDRESS">
<id name="id" column="ADDRESS_ID">
<generator class="foreign">
<param name="property">user</param>
</generator>
</id>
<one-to-one name="user" class="User" constrained="true"/>
</class>
64.JPA用@OneToOne注解支持一对一的实体关联。要映射User类中shippingAddress的关联为共享主键关联,还需要@PrimaryKeyJoinColumn注解:
@OneToOne
@PrimaryKeyJoinColumn
private Address shippingAddress;
在共享主键上创建单向的一对一关联就只需要这些。注意,如果通告复合主键映射,就要用@PrimaryKeyJoinColumns(复数)代替。在JPA XML描述符中,一对一的映射看起来像这样:
<entity-mappings>
<entity class="auction.model.User" access="FIELD">
<one-to-one name="shippingAddress">
<primary-key-join-column/>
</one-to-one>
</entity>
</entity-mappings>
65.不共享主键,而是两行可以有一个外键关系。一张表有着引用被关联表的主键的一个外键列。【这个外键约束的源和目标设置可以是相同的表:称作自引用(self-referencing)关系。】
<class name="User" table="USERS">
<many-to-one name="shippingAddress" class="Address" column="SHIPPING_ADDRESS_ID"
   cascade="save-update" unique="true"/>
</class>
66.JPA映射注解也支持基于外键列的实体之间的一对一关系。与本章前面的映射相比,主要区别在于@JoinColumn代替了@PrimaryKeyJoinColumn。
首先,这是User到Address的对一映射,在SHIPPING_ADDRESS_ID外键列中包含唯一约束。但是,它不用@ManyToOne注解,而是需要@OneToOne注解:
public class User{
@OneToOne
@JoinColumn(name="SHIPPING_ADDRESS_ID")
private Address shippingAddress;
}
Hibernate现在将用唯一约束强制多样性。如果要使这个关联变成双向的,Address中还需要@OneToOne映射:
public class Address{
@OneToOne(mappedBy="shippingAddress")
private User user;
}
mappedBy属性的作用与XML映射中的property-ref一样:关联的一个简单的反向声明,就在目标实体端指定了一种属性。
JPA XML描述器中与之相当的映射如下:
<entity-mappings>
<entity class="auction.model.User" access="FIELD">
<one-to-one name="shippingAddress">
<join-column name="SHIPPING_ADDRESS_ID"/>
</one-to-one>
</entity>
<entity class="auction.model.Address" access="FIELD">
<one-to-one name="user" mapped-by="shippingAddress"/>
</entity>
</entity-mappings>
66.<class name="Shipment" table="SHIPMENT">
<id name="id" column="SHIPMENT_ID"></id>
<join table="ITEM_SHIPMENT" optional="true"
<key column="SHIPMENT_ID"/>
<many-to-one name="auction" column="ITEM_ID"  not-null="true" unique="true"/>
</join>
通过在<join>映射中设置optional="true",告诉Hibernate它应该只有当这个映射分组的属性为空时,才把行插入到联结表中。
可以通过注解把可选的一对一关联映射到一个中间的联结表:
public class Shipment
{
    @OneToOne
    @JoinTable( name="ITEM_SHIPMENT",
                         joinColumns=@JoinColumn(name="SHIPMENT_ID") ,
                         inverseJoinColumns=@JoinColumn(name="ITEM_ID")
   )
private Item auction;
}
67.声明二级表:
@Entity
@Table(name="SHIPMENT")
@SecondaryTable(name="ITEM_SHIPMENT")
public class Shipment
{
  @Id @GeneratedValue
  @Column(name="SHIPMENT_ID")
   private Long id;
}
注意,@SecondaryTable注解也支持用属性声明外键列名——相当于前面用XML的<key column="..."/>和@JoinTable中的joinColumn(s)。如果没有指定,就使用实体的主键列名——在这个例子中,还是SHIPMENT_ID。
如果两个实例中有一个似乎更加重要,并且可以表现的像主键源医药,就是用共享的主键关联。在所有其他的情况下都使用外键关联,并且当一对一关联为可选时,就使用被隐藏的中间联结表。
67.多值(many-valued)的实体关联就其定义而言,是指实体引用的一个集合。父实体实例有一个对多个子对象的引用集合——因而,是一对多。
一对多关联是涉及集合的一种最重要的实体关联。如果简单的双向多对一或一对多能够完成任务时,目前为止我们并不鼓励使用更加冠以的关联方式。多对多关联始终可以表示为对中间雷的两个多对一关联。这个模型通常更易于扩展,因此我们趋向于不再应用中使用多对多关联。也要记住:如果不想的话,你不必映射实体的任何集合;你可以始终编写显式查询来代替通过迭代的直接访问。
每当有实体引用的非方向集合(大多数时候是包含list、map或者array的一对多关联),并且目标表中的外键列不可为空时,就需要让Hibernate知道这些。Hibernate需要这个提示,以便正确地处理INSERT和UPDATE语句,避免约束冲突。
如果通过被索引的集合映射双向的一对多实体关联(映射和数组也是这样),就必须转换反向端。无法时被索引的集合变成inverse="true"。集合变成了负责状态同步,并且一(one)端Bid必须反向。然而,多对一映射没有inverse="true",因此需要在<many-to-one>中模拟这一属性:
<class name="Bid" table="BID">
<many-to-ome name="item" column="ITEM_ID" class="Item" not-null="true" insert="false" update="false"/>
</class>
设置insert和update为false具有与其的效果。如前所述,这两个属性一起使用,实际上使属性变成了只读(read-only)。关联的这一段因此被任何写操作忽略,当内存状态与数据库同步时,集合的状态(包括元素的索引)就是香港的状态。你已经转换了关联的方向/非反向端,如果从set或者bag转换到list(或者任何其他被索引的集合),这是个必要的条件。
JPA中的等价物(双向的一对多映射中的被索引集合)如下:
public class Item{
@OneToMany
@JoinColumn(name="ITEM_ID",nullable=false)
@org.hibernate.annotations.IndexColumn(name="BID_POSITION")
private List<Bid> bids=new ArrayList<Bid>();
}
这个映射为非方向的,因为没有出现mappedBy属性。由于JPA不支持持久化的被索引列表(只有再加载时用@OrderBy进行排序),需要给索引支持添加一个Hibernate扩展注解。
以下是Bid关联的另一端
public class Bid{
@ManyToOne
@JoinColumn(name="ITEM_ID",nullable=false,updatable=false,insertable=false)
private Item item;
}
68.在真实的系统中,可能没有多对多关联。依我们的经验,通常有其他信息必须被附加到被关联实例之间的每一个链接,标示这一信息的最好方式是通过中间的关联类(association class)。在Hibernate中,可以把这个关联类映射为实体,并给任何一端映射两个一对多的关联。可能更方便的做法是,也可以映射一个符合的元素类,这是我们后面要介绍的方法。
双向关联的一端必须被映射为反向,因为你已经对(一个或多个)外键列命名了两次。给双向的多对多关联应用同样的原则:链接表的每一行都由两个集合元素表示,关联的两端各一个元素。
如往常一样,双向关联(无论什么多样性)要求关联的两端都要设置。
映射双向的多对多关联时,必须用inverse="true"声明关联的一端,来定义哪一端的状态用来更新联结表。可以选择哪一端应该为反向。
级联选项all、delete和delete-orphans对于多对多的关联都是没有意义的。
给管理的非反向端映射使用<list>、给反向端映射使用<bag>是合理的。对于反向端,可以接受<set>,bag映射也一样。
没有其他的映射可以用于多对多关联的反向端。被索引的集合(list和map)不行,因为如果集合为反向,Hibernate将不会初始化或者维持索引列。换句话说,无法通过被索引的集合在两端映射多对多的关联。
69.可以用两种常用的策略把这样一种结构映射到Java类。第一种策略需要一个用于联结表中的中间实体类,并通过一对多的关联而被映射。第二种策略通过给联结表使用一个值类型的类来利用组件的集合。
70.Map的键和值都是值类型,它们是简单的字符串。可以创建更复杂的map;不仅键可以是对实体的引用,值也可以。因此结果可以是三重关联。
JPA的@MapKey元素——它把目标实体的一个属性映射为该映射的键。如果省略name属性,则默认为目标实体的标识符属性(因此这里的名称是多余的)。
71.多态关联(polymorphic association)可以引用在映射元数据中显式指定的类的子类实例。
不必做任何特别的事情来启用Hibernate中的多态关联;在关联映射中指定任何被映射的持久化类的名称(或者让Hibernate利用反射发现它),然后,如果该类声明了任何<union-subclass>、<subclass>或者<joined-subclass>元素,该关联就自然地成为多态。
只有一件事情必须小心:如果BillingDetails通过lazy="true"映射(这是默认的),Hibernate就会代理defaultBillingDetails关联目标。在这种情况下,就不能在运行时执行类型转换到具体的类CreditCard,甚至instanceof操作符也会表现得很奇怪。为了执行代理安全的类型转换,要使用load();
如果想要启用多态的联合特性,这个多态关联的必要条件是它为反向;在对面端必须有一个映射。
72.自然键经常使得在业务需求改变时很难重构数据模型。在极端的情况下,它们甚至可能影响性能。不幸的是,许多遗留Schema大量使用(自然的)复合键,并且由于我们反对使用复合键的缘故,可能很难改变遗留Schema来使用非复合的自然键或者代理键。
因此,Hibernate支持自然键的使用。如果自然键是一个复合键,支持就是通过<composite-id>映射。
73.几种策略避免SELECT:
(1)把<version>或者<timestamp>映射和一个属性添加到实体。Hibernate在内部通过乐观并发控制来管理这两个值。作为一项附带作用,空的时间戳(或者0或者NULL)版本表明实例时新的,并且必须插入而不是更新。
(2)实现Hibernate Intercept,并把它钩入到Session里面。这个扩展接口允许你实现方法isTransient(),它包含着你区分旧对象和新对象时可能需要的任何定制过程。
另一方面,如果喜欢显示地使用save()和update()而不是saveOrUpdate(),Hibernate就不必区分瞬时实例和脱管实例——你通过选择要调用的正确方法来进行。(实际上,这就是始终不用saveOrUpdate()的唯一原因)。
用JPA注解映射自然主键很简单:
@Id
private String username;
如果没有声明标识符生成器,Hibernate就会假设它必须应用一般的select-to-determine-state-unless-versioned策略,并期待应用程序负责分配主键值。可以通过扩展包含拦截器的应用或者添加版本控制属性(版本号或者时间戳),再次避免SELECT。
Hibernate执行一个SELECT来确定saveOrUpdate()应该做什么——除非你启用版本控制或者定制的Interceptor。
正确地实现equals()和hashCode()很重要,因为Hibernate的高速缓存查找依赖这些方法。标识符类还需要实现Serializable接口。
74.如果需要应用程序分配标识符(而非Hibernate来生成),可以使用assigned生成器。该生成器会使用已经分配给对象的标识符属性的标识符值。它使用自然键作为主键。这是没有指定<generator>元素时的默认行为。当选择assigned生成器时,除非有version或timestamp属性,或者你定义了Interceptor.isUnsaved(),否则需要让Hibernate使用unsavedvalue="undefined",强制Hibernate查询数据库来确定一个实例是瞬时的,还是脱管的。
建议你映射一个也是复合主键一部分的外键列,通过一般的<many-to-one>元素,并用insert="false" update="false"禁用该列的任何Hibernate插入或者更新。
75.JPA规范涵盖了处理复合键的策略。你有3种选择:
(1)把标识符属性封装在一个单独的类里面,并把它标识为@Embeddable,就像一般的组件一样。在实体类中包括这个组件类型的一个属性,并利用@Id给一个应用分配的策略映射它。
(2)把标识符属性封装在一个没有任何注解的单独类里面。在实体类中包括这个类型的一个属性,并用@EmbeddedId映射它。
(3)把标识符属性封装在单个的类里面。现在——这与你通常在原生的Hibernate中所做的不同——复制实体类中所有标识符属性。然后,用@IdClass注解实体类,并指定被封装的标识符类的名称。
第一种选择很简单。需要使UserId类编程是可嵌入的:
@Embeddable
public class UserId implements Serializable
{
      private String username;
      private String departmentNr;
}
至于所有的组件映射,你可以在这个类的字段(或者获取方法)中定义额外的映射属性。为了映射User的复合键,通过省略@GenerateValue注解而对分配的应用设置生成策略:
@Id
@AttributeOverrides({
     @AttributeOverride(name = "username", column=@Column(name="USERNAME")),
     @AttributeOverride(name = "departmentNr",column=@Column(name="DEP_NR"))
)
})
private UserId userId;
第二种复合键映射策略不要钱你给UserId主键类作标记。因此,这个类不需要@Embeddable和其他注解。在自己的实体中,你通过@EmbeddedId映射复合的标识符属性,仍然通过可选的覆盖:
@EmbeddedId
@AttributeOverrides({
     @AttributeOverride(name = "username", column=@Column(name="USERNAME")),
     @AttributeOverride(name = "departmentNr",column=@Column(name="DEP_NR"))
)
})
private UserId userId;
第三种复合键映射策略则更难理解一点,尤其对于那些经验丰富的Hibernate用户而言。首先,把所有标识符属性封装在一个单独的类里面——如前一种策略一样,这个类不需要额外的注解。现在复制实体类中的所有标识符属性:
@Entity
@Table(name="USERS")
@IdClass(UserId.class)
public class User{
      @Id
       private String username;
      @Id
      private String departmentNr;
}
Hibernate检查@IdClass,并挑出所有重复属性(通过比较名称和类型),作为标识符属性和主键的一部分。所有主键属性都通过@Id注解,并根据这些元素(字段或者获取方法)的位置,实体默认为字段或者属性访问。
76.复合外键也可能使用注解。先映射从Item到User的关联:
@ManyToOne
@JoinColumns({
    @JoinColmun(name="USERNAME",referencedColumnName="USERNAME"),
    @JoinColumn(name="DEP_NR",referencedColumnName="DEF_NR")
})
private User seller;
一般的@ManyToOne和这个映射之间的主要区别在于涉及的列数——这个顺序还是很重要,并且应该与主键列的顺序一致。然而,如果你对每个列声明referencedColumnName,顺序就不重要了,并且外键约束的源表和目标表都可以有不同的列名称。
77.通常,外键约束引用主键。外键约束是一个完整性规则,它保证被引用的表有一行所包含的键值与引用表和给定行中的键值相匹配。注意,外键约束可以自引用;换句话说,包含外键约束的列可以引用通一张表的主键列。
遗留Schema有时候会有不遵循简单的“外键引用主键”规则的外键约束。有时候,外键引用非主键:一个简单的唯一列,一个自然的非主键。
unique属性是Hibernate中的其中一个SQL定制选项;不在运行时使用它(Hibernate不做任何唯一性验证),而是通过hbm2dbl导出数据库Schema。如果你有个包含自然键的现有Schema,就假设它是唯一的。为了完整起见,你可以并且应该在映射元数据中重复如此重要的约束——或许有一天你会用它导出一个新的Schema。
78.在更加怪异的Hibernate映射中你会遇到property-ref属性。它用来告诉Hibernate“这是具名属性的一个镜像”。在前一个例子中,Hibernate现在知道外键引用的目标了。你要进一步注意的是,property-ref要求目标属性要唯一,因此如前所述,这个映射需要unique="true"。
如你所见,<properties>元素不仅可以用于给几个属性命名,而且定义了一个多列的unique约束,或者使几个属性变成不可变。对于关联映射,列的顺序仍然很重要。
79.Hibernate可以把类、属性,甚至关联的几个部分映射到一个简单的SQL语句或者表达式。我们称这种映射为公式映射(formula mapping)。
type="true_false"属性在Java boolean基本(或者它的包装)属性和包含T/F文字值的一个简单的CHAR(1)列之间创建一个映射——它是个内建的Hibernate映射类型。你可以在其他映射中引用的名称下使用<properties>再次对若干属性进行组合。
80.使用ORM软件的触发器和对象状态管理通常是个问题,因为触发器可能在不合时机的时候运行,或者可能修改不与内存状态同步的数据。
涉及触发器的大部分问题都可能以这个方式解决,用一个显式的flush()强制立即执行触发器,可能接着调用refresh()来获取触发器的结果。
refresh()更为真实的定义是“利用数据库中的当前值,刷新处于持久化状态的内存实例”。
<property name="createed" type="timestamp" column="CREATED" generated="insert" insert="false" update="false"/>
通过注解,使用Hibernate扩展:
@Temporal(TemporalType.TIMESTAMP)
@org.hibernate.annotations.Generated
(
    org.hibernate.annotations.GenerationTime.INSERT
)
@Column(name="CREATED",insertable=false,updatebale=false)
private Date created;
利用generated="insert",Hibernate在插入之后自动执行SELECT,来获取更新过的状态。
当数据库执行触发器时,要进一步注意的问题是:脱管对象图的再关联和在每次UPDATE时运行的触发器。
<property name="lastModified" type="timestamp" column="LAST_MODIFIED" generated="always"  insert="false" update="false"/>
@Temporal(TempralType.TIMESTAMP)
@org.hibernate.annotations.Generated(
    org.hibernate.annotations.GenerationTime.ALWAYS
)
@Column(name="LAST_MODIFIED",insertable=false,updatable=false)
利用always,启用Hibernate的自动刷新,不仅针对行的插入还针对行的更新。换句话说,每当一个版本、时间戳、或者任何属性值由在UPDATE SQL语句中运行的触发器生成时,都需要启用这个选项。
由于当托管对象被重赋到新的Session(通过update()或saveOrUpdate())时,没有快照可用,Hibernate可能执行不必要的SQL UPDATE语句,确保数据库状态与持久化的上下文状态同步,这可能导致不合时宜地触发一个UPDATE触发器。通过在映射中被持久化到带有触发器的表的类启用select-before-update,可以避免着这种行为。如果ITEM表有一个更新触发器,就把下列属性添加到映射中:
<class name="Item" table="ITEM" select-before-update="true">...</class>
这项设置强制Hibernate利用SQL SELECT来获取当前数据库状态的快照,如果内存Item的状态相同,则避免后面的UPDATE。你用额外的SELECT避免了不合时宜的UPDATE。
Hibernate注解启用同样的行为:
@Entity
@org.hibernate.annotations.Entity(selectBeforeUpdate=true)
publiu class Item{...}

猜你喜欢

转载自bsr1983.iteye.com/blog/2093835
今日推荐