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{...}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第一部分 从Hibernate和EJB 3.0开始  第1章 理解对象/关系持久化    1.1 什么是持久化     1.1.1 关系数据库     1.1.2 理解SQL     1.1.3 在Java中使用SQL     1.1.4 面向对象应用程序中的持久化    1.2 范式不匹配     1.2.1 粒度问题     1.2.2 子类型问题     1.2.3 同一性问题     1.2.4 与关联相关的问题     1.2.5 数据导航的问题     1.2.6 不匹配的代价    1.3 持久层和其他层 显示全部信息第一部分 从Hibernate和EJB 3.0开始  第1章 理解对象/关系持久化    1.1 什么是持久化     1.1.1 关系数据库     1.1.2 理解SQL     1.1.3 在Java中使用SQL     1.1.4 面向对象应用程序中的持久化    1.2 范式不匹配     1.2.1 粒度问题     1.2.2 子类型问题     1.2.3 同一性问题     1.2.4 与关联相关的问题     1.2.5 数据导航的问题     1.2.6 不匹配的代价    1.3 持久层和其他层     1.3.1 分层架构     1.3.2 用SQL/JDBC手工编写持久层     1.3.3 使用序列化     1.3.4 面向对象的数据库系统     1.3.5 其他选项    1.4 ORM     1.4.1 什么是ORM     1.4.2 一般的ORM问题     1.4.3 为什么选择ORM     1.4.4 Hibernate、EJB 3和JPA简介    1.5 小结   第2章 启动项目    2.1 启动Hibernate项目     2.1.1 选择开发过程     2.1.2 建立项目     2.1.3 Hibernate配置和启动     2.1.4 运行和测试应用程序    2.2 启动Java Persistence项目     2.2.1 使用Hibernate Annotations     2.2.2 使用Hibernate EntityManager     2.2.3 引入EJB组件     2.2.4 切换到Hibernate接口    2.3 反向工程遗留数据库     2.3.1 创建数据库配置     2.3.2 定制反向工程     2.3.3 生成Java源代码    2.4 与Java EE服务整合     2.4.1 与JTA整合     2.4.2 JNDI绑定的SessionFactory     2.4.3 JMX服务部署    2.5 小结   第3章 领域模型和元数据    3.1 CaveatEmptor应用程序     3.1.1 分析业务领域     3.1.2 CaveatEmptor领域模型    3.2 实现领域模型     3.2.1 处理关注点渗漏     3.2.2 透明和自动持久化     3.2.3 编写POJO和持久化实体类     3.2.4 实现POJO关联     3.2.5 把逻辑添加到访问方法    3.3 ORM元数据     3.3.1 XML中的元数据     3.3.2 基于注解的元数据     3.3.3 使用XDoclet     3.3.4 处理全局的元数据     3.3.5 运行时操作元数据    3.4 其他实体表示法     3.4.1 创建动态的应用程序     3.4.2 表示XML中的数据    3.5 小结  第部分 映射概念和策略  第4章 映射持久化类    4.1 理解实体和值类型     4.1.1 细粒度的领域模型     4.1.2 定义概念     4.1.3 识别实体和值类型    4.2 映射带有同一性的实体     4.2.1 理解Java同一性和等同性     4.2.2 处理数据库同一性     4.2.3 数据库主键    4.3 类映射选项     4.3.1 动态的SQL生成     4.3.2 使实体不可变     4.3.3 给查询命名实体     4.3.4 声明包名称     4.3.5 用引号把SQL标识符括起来     4.3.6 实现命名约定    4.4 细粒度的模型和映射     4.4.1 映射基础属性     4.4.2 映射组件    4.5 小结   第5章 继承和定制类型    5.1 映射类继承     5.1.1 每个带有隐式多态的具体类一张表     5.1.2 每个带有联合的具体类一张表     5.1.3 每个类层次结构一张表     5.1.4 每个子类一张表     5.1.5 混合继承策略     5.1.6 选择策略    5.2 Hibernate类型系统     5.2.1 概述实体和值类型     5.2.2 内建的映射类型     5.2.3 使用映射类型    5.3 创建定制的映射类型     5.3.1 考虑定制的映射类型     5.3.2 扩展点     5.3.3 定制映射类型的案例     5.3.4 创建UserType     5.3.5 创建CompositeUserType     5.3.6 参数化定制类型     5.3.7 映射枚举    5.4 小结   第6章 映射集合和实体关联    6.1 值类型的set、bag、list和map     6.1.1 选择集合接口     6.1.2 映射set     6.1.3 映射标识符bag     6.1.4 映射list     6.1.5 映射map     6.1.6 排序集合和有序集合  6.2 组件的集合     6.2.1 编写组件类     6.2.2 映射集合     6.2.3 启用双向导航     6.2.4 避免非空列    6.3 用注解映射集合     6.3.1 基本的集合映射     6.3.2 排序集合和有序集合     6.3.3 映射嵌入式对象的集合    6.4 映射父/子关系     6.4.1 多样性     6.4.2 最简单的可能关联     6.4.3 使关联双向     6.4.4 级联对象状态    6.5 小结   第7章 高级实体关联映射    7.1 单值的实体关联     7.1.1 共享的主键关联     7.1.2 一对一的外键关联     7.1.3 用联结表映射    7.2 多值的实体关联     7.2.1 一对多关联     7.2.2 多对多关联     7.2.3 把列添加到联结表     7.2.4 映射map    7.3 多态关联     7.3.1 多态的多对一关联     7.3.2 多态集合     7.3.3 对联合的多态关联     7.3.4 每个具体类一张多态表    7.4 小结   第8章 遗留数据库和定制SQL    8.1 整合遗留数据库     8.1.1 处理主键     8.1.2 带有公式的任意联结条件     8.1.3 联结任意的表     8.1.4 使用触发器    8.2 定制SQL     8.2.1 编写定制CRUD语句     8.2.2 整合存储过程和函数    8.3 改进Schema DDL     8.3.1 定制SQL名称和数据类型     8.3.2 确保数据一致性     8.3.3 添加领域约束和列约束     8.3.4 表级约束     8.3.5 数据库约束     8.3.6 创建索引     8.3.7 添加辅助的DDL    8.4 小结  第三部分 会话对象处理  第9章 使用对象    9.1 持久化生命周期     9.1.1 对象状态     9.1.2 持久化上下文    9.2 对象同一性和等同性     9.2.1 引入对话     9.2.2 对象同一性的范围     9.2.3 脱管对象的同一性     9.2.4 扩展持久化上下文    9.3 Hibernate接口     9.3.1 保存和加载对象     9.3.2 使用脱管对象     9.3.3 管理持久化上下文    9.4 JPA     9.4.1 保存和加载对象     9.4.2 使用脱管的实体实例    9.5 在EJB组件中使用Java Persistence     9.5.1 注入EntityManager     9.5.2 查找EntityManager     9.5.3 访问EntityManagerFactory    9.6 小结   第10章 事务和并发    10.1 事务本质     10.1.1 数据库和系统事务     10.1.2 Hibernate应用程序中的事务     10.1.3 使用Java Persistence的事务    10.2 控制并发访问     10.2.1 理解数据库级并发     10.2.2 乐观并发控制     10.2.3 获得额外的隔离性保证    10.3 非事务数据访问     10.3.1 揭开自动提交的神秘面纱     10.3.2 使用Hibernate非事务地工作     10.3.3 使用JTA的可选事务    10.4 小结   第11章 实现对话    11.1 传播Hibernate Session     11.1.1 Session传播的用例     11.1.2 通过线程局部传播     11.1.3 利用JTA传播     11.1.4 利用EJB传播    11.2 利用Hibernate的对话     11.2.1 提供对话保证     11.2.2 利用脱管对象的对话     11.2.3 给对话扩展Session    11.3 使用JPA的对话     11.3.1 Java SE中的持久化上下文传播     11.3.2 在对话中合并脱管对象     11.3.3 在Java SE中扩展持久化上下文    11.4 使用EJB 3.0的对话     11.4.1 使用EJB的上下文传播     11.4.2 利用EJB扩展持久化上下文    11.5 小结   第12章 有效修改对象    12.1 传播性持久化     12.1.1 按可到达性持久化     12.1.2 把级联应用到关联     12.1.3 使用传播性状态     12.1.4 利用JPA的传播性关联    12.2 大批量和批量操作     12.2.1 使用HQL和JPA QL的大批量语句     12.2.2 利用批量处理     12.2.3 使用无状态的会话    12.3 数据过滤和拦截     12.3.1 动态数据过滤     12.3.2 拦截Hibernate事件     12.3.3 内核事件系统     12.3.4 实体监听器和回调    12.4 小结   第13章 优化抓取和高速缓存    13.1 定义全局抓取计划     13.1.1 对象获取选项     13.1.2 延迟的默认抓取计划     13.1.3 理解代理     13.1.4 禁用代理生成     13.1.5 关联和集合的即时加载     13.1.6 通过拦截延迟加载    13.2 选择抓取策略     13.2.1 批量预抓取数据     13.2.2 通过子查询预抓取集合     13.2.3 通过联结即时抓取     13.2.4 给级表优化抓取     13.2.5 优化指导方针    13.3 高速缓存基本原理     13.3.1 高速缓存策略和范围     13.3.2 Hibernate高速缓存架构    13.4 高速缓存实践     13.4.1 选择并发控制策略     13.4.2 理解高速缓存区域     13.4.3 设置本地的高速缓存提供程序     13.4.4 设置重复的高速缓存     13.4.5 控制级高速缓存    13.5 小结   第14章 利用HQL和JPA QL查询    14.1 创建和运行查询     14.1.1 准备查询     14.1.2 执行查询     14.1.3 使用具名查询    14.2 基本的HQL和JPA QL查询     14.2.1 选择     14.2.2 限制     14.2.3 投影    14.3 联结、报表查询和子查询     14.3.1 联结关系和关联     14.3.2 报表查询     14.3.3 利用子查询    14.4 小结   第15章 高级查询选项    15.1 利用条件和示例查询     15.1.1 基本的条件查询     15.1.2 联结和动态抓取     15.1.3 投影和报表查询     15.1.4 按示例查询    15.2 利用原生的SQL查询     15.2.1 自动的结果集处理     15.2.2 获取标量值     15.2.3 Java Persistence中的原生SQL    15.3 过滤集合    15.4 高速缓存查询结果     15.4.1 启用查询结果高速缓存     15.4.2 理解查询高速缓存     15.4.3 什么时候使用查询高速缓存     15.4.4 自然标识符高速缓存查找    15.5 小结   第16章 创建和测试分层的应用程序    16.1 Web应用程序中的Hibernate     16.1.1 用例简介     16.1.2 编写控制器     16.1.3 OSIV模式     16.1.4 设计巧妙的领域模型    16.2 创建持久层     16.2.1 泛型的数据访问对象模式     16.2.2 实现泛型CRUD接口     16.2.3 实现实体DAO     16.2.4 利用数据访问对象    16.3 命令模式简介     16.3.1 基础接口     16.3.2 执行命令对象     16.3.3 命令模式的变形    16.4 利用EJB 3.0设计应用程序     16.4.1 利用有状态的bean实现会话     16.4.2 利用EJB编写DAO     16.4.3 利用依赖注入    16.5 测试     16.5.1 理解不同种类的测试     16.5.2 TestNG简介     16.5.3 测试持久层     16.5.4 考虑性能基准    16.6 小结   第17章 JBoss Seam简介    17.1 Java EE 5.0编程模型     17.1.1 JSF详解     17.1.2 EJB 3.0详解     17.1.3 用JSF和EJB 3.0编写Web应用程序     17.1.4 分析应用程序    17.2 用Seam改善应用程序     17.2.1 配置Seam     17.2.2 将页面绑定到有状态的Seam组件     17.2.3 分析Seam应用程序    17.3 理解上下文组件     17.3.1 编写登录页面     17.3.2 创建组件     17.3.3 给上下文变量起别名     17.3.4 完成登录/注销特性    17.4 验证用户输入     17.4.1 Hibernate Validator简介     17.4.2 创建注册页面     17.4.3 用Seam实现国际化    17.5 利用Seam简化持久化     17.5.1 实现对话     17.5.2 让Seam管理持久化上下文    17.6 小结  附录A SQL基础知识  附录B 映射快速参考
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值