细粒度的模型和映射
JDK带来的值类型的类(例如String或者基本类型),以及由应用程序的开发员定义的值类型的类(例如Address和MontaryAmount)。首先,映射使用JDK类型的持久化类属性,并学习基础的映射元素和属性。然后着手定制的值类型类,并把它们映射为可嵌入的组件。
映射基础属性
如果映射持久化类,无论它是实体还是值类型,所有持久化属性都必须在XML映射文件中被显式地映射。另一方面,如果用注解映射类,它的所有属性都默认为是持久化的。可以用@javax.persistence.Transient注解给属性进行标识来所它们排除,或者使用transient的Java关键字(通常只给Java序列化排除字段)。在一个JPA XML描述符中,可以排除一个特定的字段或者属性:
<entity class="auction.model.User" access="FIELD"> <attributes> ... <transient name="age"/> </attributes> </entity>一个典型的Hibernate属性映射定义了一个POJO的属性名称、一个数据库列名和一个Hibernate类型的名称,并且它可能经常省略类型。因此,如果description是(Java)类型java.lang.String的一个属性,Hibernate就默认使用Hibernate类型string。
Hibernate用反射决定属性的Java类型。因而,下列映射是等价的:
<property name="description" column="DESCRIPTION type="string"/> <property name="description" column="DESCRIPTION"/>如果列名与属性名相同(忽略大小写),那么列名甚至可以省略。
JPA是基于异常模型的一个配置,因此可以依赖默认。如果持久化类的一个属性没有被注解,就应用下列规则:
1)如果属性是JDK类型,它自动就是持久化的。换句话说,它在Hibernate XML映射文件中像<property name="propertyName"/>一样处理。
2)如果属性的类被注解为@Embeddable,它就被映射为自己的类的组件。
3)如果属性的类型是Serializable,它的值以序列化的形式保存。
如果不想依赖这些默认,就在一个特定的属性上应用@Basic注解。@Column注解相当于XML的<column>元素。以下是如何按要求声明一个属性值的例子:
@Basic(optional = false) @Column(nullable = false) public BigDecimal getInitialPrice { return initialPrice; }@Basic注解把属性标识为在Java对象级上不可选。第二个设置,列映射上的nullable = false,只负责NOT NULL(非空)数据库约束的生成。
在JPA XML描述符中,这个映射看起来是相同的:
<entity class="auction.model.Item" access="PROPERTY"> <attributes> <basic name="initialPrice" optional="false"> <column nullable="false"/> </basic> </attributes> </entity>用于属性的注解永远都在访问方法中吗?
1、定制属性访问
类的属性由持久化引擎直接(通过字段)或者间接(通过获取方法和设置方法属性访问方法)访问。在XML映射文件中,用hibernate-mapping的default-access="field|property|noop"属性给类控制默认的访问策略。被注解的实体从强制的@Id注解的位置继承默认的访问策略。如果@Id已经在字段中(而不是获取方法中)声明, 那么所有其他的属性映射注解(如货品description属性的列名)也在字段中声明:@Column(name = "ITEM_DESCR") private String description; public String getDescription() { return description; }这是JPA规范定义的默认行为。然而,Hibernate允许利用@org.hibernate.annotations.AccessType(<strategy>)注解灵活地定制访问策略:
1)如果在类/实体级中设置AccessType,类的所有属性都根据选中的策略访问。属性级注解是在字段还是获取方法上,这取决于策略。
2)如果给属性访问默认或者显式地设置一个实体,字段中的AccessType("property")注解就把这个特定的属性转换为通过属性获取方法/设置方法的运行时访问。AccessType注解的位置仍然为字段。
如果给方法访问默认或者显式地设置一个实体,获取方法中的AccessType("field")注解就把这个特定的属性转换为通过同名字段的运行时访问。AccessType注解的位置仍然为获取方法。
3)任何@Embedded类都继承默认的或者显式声明的自己根实体类的访问策略。
4)任何@MappedSuperclass属性都通过被映射实体类的默认或者显式声明的访问策略而被访问。
也可以用access属性在Hibernate XML映射中控制属性级上的访问策略:
<property name="description" column="DESCR" access="field"/>或者,可以用default-access属性给一个根<hibernate-mapping>元素内部的所有类映射设置访问策略。
除了字段和属性访问之外,另一个可能有用的策略是noop。它映射Java持久化类中不存在的属性。(Java类中没有属性,实体映射文件含有property access="noop",数据库有对应的属性)。
如果没有合适的内建访问策略,也可以通过实现接口org.hibernate.property.PropertyAccessor定义自己的定制属性访问策略。
有些属性根本不映射到列。特别是衍生属性(derived property),它从SQL表达式中取值。
2、使用衍生属性
衍生属性的值在运行时计算,通过对利用formula属性定义的表达式求值。例如,可以把totalIncludingTax属性映射到SQL表达式:<property name="totalIncludingTax" formula="TOTAL + TAX_RATE * TOTAL" type="big_decimal"/>这个给定的SQL公式在每次从数据库获取实体时求值(并且在任何其他时间不求值,因此如果其他的属性被修改,这个结果就可能过时)。属性没有列属性(或者子元素),并且永远不会出现SQL的INSERT或者UPDATE中,而只在SELECT中。公式可能指向数据库表的列,它们可以调用SQL函数,甚至包括SQL子查询。
公式也可使用Hibernate注解:
@org.hibernate.annotations.Formula("TOTAL + TAX_RATE * TOTAL") public BigDecimal getTotalIncludingTax(){ return totalIncludingTax; }下列示例使用一个关联的子查询来计算一件货品所有出价的平均值:
<property name="averageBidAmount" type="big_decimal" formula="(select AVG(b.AMOUNT) from BID b where b.ITEM_ID = ITEM_ID"/>另一种特殊的属性依赖数据库生成的值。
3、生成的和默认的属性值
想象类的一个特定属性有着数据库生成的值,通常在第一次插入实体行的时候。典型的数据库生成的值是创建的时间戳、对一件货品的默认价格,以及每次修改时运行的触发器。典型的Hibernate应用程序需要刷新包含由数据库为其生成值的任何属性的对象。然而,标识属性为已生成(generated),让应用程序把这个责任委托给了Hibernate。本质上,每当Hibernate给定义了已生成属性的实体执行SQL INSERT或者UPDATE时,它在获取已生成的值之后立即执行SELECT。使用property映射中的generated开关启用这个自动刷新:
<property name="lastModified" column="LAST_MODIFIED" update="false" insert="false" generated="always"/>标记为数据库生成的属性还必须是非可插入和非可更新的,用insert和update属性进行控制它们。如果两者都设置为false,属性的列就永远不会出现在INSERT或者UPDATE语句中——属性值是只读的。而且,通常不在类中给不可变的属性添加公有的设置方法(并切换到字段访问)。
利用注解,通过@Generated的Hibernate注解声明不可变性(和自动刷新):
@Column(updatable = false, insertable = false) @org.hibernate.annotations.Generated( org.hibernate.annotations.GenerationTime.ALWAYS ) private Date lastModified;数据库生成的属性值的一个特殊情况是默认值。例如,你可能想要实现一种规则,即每件拍卖货品的成本至少为$1。首先,得把它作为INITIAL_PRICE列的默认值添加到数据库目录:
create table ITEM( ... INITIAL_PRICE number(10,2) default ‘1‘, ... );如果使用Hibernate的Schema导出工具hbm2ddl,则可以通过把default属性添加到属性映射来启用这项输出:
<class name="Item" table="TBL_ITEM" dynamic-insert="true" dynamic-update="true"> ... <property name="initialPrice" type="big_decimal"> <column name="INITIAL_PRICE" default="‘1‘" generated="insert"/> </property> </class>注意,你还必须启用动态的插入并更新语句的生成,以便包含默认值的列不会被包括在每个语句中,如果它的值为null(否则将插入NULL,而不是默认值)。此外,已经被变成持久化但尚未清除到数据库,将不在对象属性上设置默认值。换句话说,你需要执行一个显式的清除:
Item newItem = new Item(...); session.save(newItem); newItem.getInitialPrice(); //is null session.flush(); //Hibernate does a SELECT automatically newItem.getInitialPrice(); //is $1因为设置了generated="insert",Hibernate知道读取数据库生成的属性值立即需要一个额外的SELECT。
可以用注解把默认的列值映射为列的DDL定义的一部分:
@Column( name="INITIAL_PRICE", columnDefinition="number(10,2) default ‘1‘") @org.hibernate.annotations.Generated( org.hibernate.annotations.GenerationTime.INSERT ) private BigDecimal initialPrice;columnDefinition属性包括列DDL的完整属性,包含数据类型和所有约束。记住,一个实际上不可移植的SQL数据类型可能把注解映射绑定到一个特定的数据库管理系统。