Hibernate.orgCommunity Documentation
在应用程序中,用来实现业务问题实体的(如,在电子商务应用程序中的 Customer 和 Order)类就是持久化类。不能认为所有的持久化类的实例都是持久的状态 - 一个实例的状态也可能是瞬时的或脱管的。
如果这些持久化类遵循一些简单的规则,Hibernate 能够工作得更好,这些规则也被称作简单传统 Java 对象(POJO:Plain Old Java Object)编程模型。但是这些规则并不是必需的。 实际上,Hibernate3 对于你的持久化类几乎不做任何设想。你可以用其他的方法来表达领域模型:比如,使用 Map
实例的树型结构。
大多数 Java 程序需要用一个持久化类来表示猫科动物。例如:
package eg;
import java.util.Set;
import java.util.Date;
public class Cat {
private Long id; // identifier
private Date birthdate;
private Color color;
private char sex;
private float weight;
private int litterId;
private Cat mother;
private Set kittens = new HashSet();
private void setId(Long id) {
this.id=id;
}
public Long getId() {
return id;
}
void setBirthdate(Date date) {
birthdate = date;
}
public Date getBirthdate() {
return birthdate;
}
void setWeight(float weight) {
this.weight = weight;
}
public float getWeight() {
return weight;
}
public Color getColor() {
return color;
}
void setColor(Color color) {
this.color = color;
}
void setSex(char sex) {
this.sex=sex;
}
public char getSex() {
return sex;
}
void setLitterId(int id) {
this.litterId = id;
}
public int getLitterId() {
return litterId;
}
void setMother(Cat mother) {
this.mother = mother;
}
public Cat getMother() {
return mother;
}
void setKittens(Set kittens) {
this.kittens = kittens;
}
public Set getKittens() {
return kittens;
}
// addKitten not needed by Hibernate
public void addKitten(Cat kitten) {
kitten.setMother(this);
kitten.setLitterId( kittens.size() );
kittens.add(kitten);
}
}
在后续的章节里我们将介绍持久性类的 4 个主要规则的更多细节。
Cat
有一个无参数的构造方法。所有的持久化类都必须有一个默认的构造方法(可以不是 public 的),这样的话 Hibernate 就可以使用 Constructor.newInstance()
来实例化它们。 我们强烈建议,在 Hibernate 中,为了运行期代理的生成,构造方法至少是包(package)内可见的。
Cat
有一个属性叫做 id
。这个属性映射数据库表的主 键字段。这个属性可以叫任何名字,其类型可以是任何的原始类型、原始类型的包装类型、 java.lang.String
或者是 java.util.Date
。(如果你的遗留数据库表有联合主键,你甚至可以用一个用户自定义的类,该类拥有这些类型的属性。参见后面的关于联合标识符的章节。)
标识符属性是可选的。可以不用管它,让 Hibernate 内部来追踪对象的识别。 但是我们并不推荐这样做。
实际上,一些功能只对那些声明了标识符属性的类起作用:
Transitive reattachment for detached objects (cascade update or cascade merge) - see 第 10.11 节 “传播性持久化(transitive persistence)”
Session.saveOrUpdate()
Session.merge()
我们建议你对持久化类声明命名一致的标识属性。我们还建议你使用一个可以为空(也就是说,不是原始类型)的类型。
代理(proxies)是 Hibernate 的一个重要的功能,它依赖的条件是,持久化类或者是非 final 的,或者是实现了一个所有方法都声明为 public 的接口。
你可以用 Hibernate 持久化一个没有实现任何接口的 final
类,但是你不能使用代理来延迟关联加载,这会限制你进行性能优化的选择。
你也应该避免在非 final 类中声明 public final
的方法。如果你想使用一个有 public final
方法的类,你必须通过设置 lazy="false"
来明确地禁用代理。
Cat
为它的所有持久化字段声明了访问方法。很多其他 ORM 工具直接对实例变量进行持久化。我们相信,在关系数据库 schema 和类的内部数据结构之间引入间接层(原文为"非直接",indirection)会好一些。默认情况下 Hibernate 持久化 JavaBeans 风格的属性,认可 getFoo
,isFoo
和 setFoo
这种形式的方法名。如果需要,你可以对某些特定属性实行直接字段访问。
属性不需要要声明为 public 的。Hibernate 可以持久化一个有 default
、protected
或 private
的 get/set 方法对的属性进行持久化。
子类也必须遵守第一条和第二条规则。它从超类 Cat
继承了标识属性。例如:
package eg;
public class DomesticCat extends Cat {
private String name;
public String getName() {
return name;
}
protected void setName(String name) {
this.name=name;
}
}
如果你有如下需求,你必须重载 equals()
和 hashCode()
方法:
想把持久类的实例放入 Set
中(当表示多值关联时,推荐这么做),而且
想重用脱管实例
Hibernate 保证,仅在特定会话范围内,持久化标识(数据库的行)和 Java 标识是等价的。因此,一旦我们混合了从不同会话中获取的实例,如果希望 Set
有明确的语义,就必须实现 equals()
和 hashCode()
。
实现 equals()
/hashCode()
最显而易见的方法是比较两个对象 标识符的值。如果值相同,则两个对象对应于数据库的同一行,因此它们是相等的(如果都被添加到 Set
,则在 Set
中只有一个元素)。不幸的是,对生成的标识不能 使用这种方法。Hibernate 仅对那些持久化对象赋标识值,一个新创建的实例将不会有任何标识值。此外, 如果一个实例没有被保存(unsaved),并且它当前正在一个 Set
中,保存它将会给这个对象赋一个标识值。如果 equals()
和 hashCode()
是基于标识值 实现的,则其哈希码将会改变,这违反了 Set
的契约。建议去 Hibernate 的站点阅读关于这个问题的全部讨论。注意,这不是 Hibernate 的问题,而是一般的 Java 对象标识和 Java 对象等价的语义问题。
我们建议使用业务键值相等(Business key equality)来实现 equals()
和 hashCode()
。业务键值相等的意思是,equals()
方法仅仅比较形成业务键的属性,它能在现实世界里标识我们的实例(是一个自然的候选码)。
public class Cat {
...
public boolean equals(Object other) {
if (this == other) return true;
if ( !(other instanceof Cat) ) return false;
final Cat cat = (Cat) other;
if ( !cat.getLitterId().equals( getLitterId() ) ) return false;
if ( !cat.getMother().equals( getMother() ) ) return false;
return true;
}
public int hashCode() {
int result;
result = getMother().hashCode();
result = 29 * result + getLitterId();
return result;
}
}
A business key does not have to be as solid as a database primary key candidate (see 第 12.1.3 节 “关注对象标识(Considering object identity)”). Immutable or unique properties are usually good candidates for a business key.
The following features are currently considered experimental and may change in the near future.
运行期的持久化实体没有必要一定表示为像 POJO 类或 JavaBean 对象那样的形式。Hibernate 也支持动态模型 (在运行期使用 Map
的 Map
)和象 DOM4J 的树模型那样的实体表示。使用这种方法,你不用写持久化类,只写映射文件就行了。
By default, Hibernate works in normal POJO mode. You can set a default entity representation mode for a particular SessionFactory
using the default_entity_mode
configuration option (see 表 3.3 “Hibernate 配置属性”).
下面是用 Map
来表示的例子。首先,在映射文件中,要声明 entity-name
来代替一个类名(或作为一种附属)。
<hibernate-mapping>
<class entity-name="Customer">
<id name="id"
type="long"
column="ID">
<generator class="sequence"/>
</id>
<property name="name"
column="NAME"
type="string"/>
<property name="address"
column="ADDRESS"
type="string"/>
<many-to-one name="organization"
column="ORGANIZATION_ID"
class="Organization"/>
<bag name="orders"
inverse="true"
lazy="false"
cascade="all">
<key column="CUSTOMER_ID"/>
<one-to-many class="Order"/>
</bag>
</class>
</hibernate-mapping
>
注意,虽然是用目标类名来声明关联的,但是关联的目标类型除了是 POJO 之外,也可以是一个动态的实体。
在使用 dynamic-map
为 SessionFactory
设置了默认的实体模式之后,可以在运行期使用 Map
的 Map
:
Session s = openSession();
Transaction tx = s.beginTransaction();
// Create a customer
Map david = new HashMap();
david.put("name", "David");
// Create an organization
Map foobar = new HashMap();
foobar.put("name", "Foobar Inc.");
// Link both
david.put("organization", foobar);
// Save both
s.save("Customer", david);
s.save("Organization", foobar);
tx.commit();
s.close();
动态映射的好处是,变化所需要的时间少了,因为原型不需要实现实体类。然而,你无法进行编译期的类型检查,并可能由此会处理很多的运行期异常。幸亏有了 Hibernate 映射,它使得数据库的 schema 能容易的规格化和合理化,并允许稍后在此之上添加合适的领域模型实现。
实体表示模式也能在每个 Session
的基础上设置:
Session dynamicSession = pojoSession.getSession(EntityMode.MAP);
// Create a customer
Map david = new HashMap();
david.put("name", "David");
dynamicSession.save("Customer", david);
...
dynamicSession.flush();
dynamicSession.close()
...
// Continue on pojoSession
请注意,用 EntityMode
调用 getSession()
是在 Session
的 API 中,而不是 SessionFactory
。 这样,新的 Session
共享底层的 JDBC 连接,事务,和其他的上下文信息。这意味着,你不需要在第二个 Session
中调用 flush()
和 close()
,同样的,把事务和连接的处理交给原来的工作单元。
More information about the XML representation capabilities can be found in 第 19 章 XML 映射.
org.hibernate.tuple.Tuplizer
,以及其子接口,负责根据给定的org.hibernate.EntityMode
,来复现片断数据。如果给定的片断数据被认为其是一种数据结构,"tuplizer" 就是一个知道如何创建这样的数据结构,以及如何给这个数据结构赋值的东西。比如说,对于 POJO 这种 Entity Mode,对应的 tuplizer 知道通过其构造方法来创建一个 POJO,再通过其属性访问器来访问 POJO 属性。有两大类高层 Tuplizer,分别是org.hibernate.tuple.entity.EntityTuplizer
和 org.hibernate.tuple.entity.ComponentTuplizer
接口。EntityTuplizer
负责管理上面提到的实体的契约,而 ComponentTuplizer
则是针对组件的。
有两种高层类型的 Tuplizer,分别由 org.hibernate.tuple.entity.EntityTuplizer
和 org.hibernate.tuple.component.ComponentTuplizer
接口代表。EntityTuplizer
负责管理和实体相关的上述合约,而ComponentTuplizer
则负责组件。
用户也可以插入其自定义的 tuplizer。或许您需要一种不同于 dynamic-map entity-mode 中使用的 java.util.HashMap
的 java.util.Map
实现;或许您需要与默认策略不同的代理生成策略(proxy generation strategy)。通过自定义 tuplizer 实现,这两个目标您都可以达到。Tuplizer 定义被附加到它们期望管理的 entity 或者 component 映射中。回到我们的 customer entity 例子:
<hibernate-mapping>
<class entity-name="Customer">
<!--
Override the dynamic-map entity-mode
tuplizer for the customer entity
-->
<tuplizer entity-mode="dynamic-map"
class="CustomMapTuplizerImpl"/>
<id name="id" type="long" column="ID">
<generator class="sequence"/>
</id>
<!-- other properties -->
...
</class>
</hibernate-mapping>
public class CustomMapTuplizerImpl
extends org.hibernate.tuple.entity.DynamicMapEntityTuplizer {
// override the buildInstantiator() method to plug in our custom map...
protected final Instantiator buildInstantiator(
org.hibernate.mapping.PersistentClass mappingInfo) {
return new CustomMapInstantiator( mappingInfo );
}
private static final class CustomMapInstantiator
extends org.hibernate.tuple.DynamicMapInstantitor {
// override the generateMap() method to return our custom map...
protected final Map generateMap() {
return new CustomMap();
}
}
}
org.hibernate.EntityNameResolver
接口是一个解析给定实体实例的实体名称的合约。这个接口定义了一个单一的方法 resolveEntityName
,它传递实体实例并预期返回合适的实体名称(null 指明解析器不知道如何解析给定实体实例的实体名称)。一般说来,org.hibernate.EntityNameResolver
在动态模型里最为有用。其中的例子是把代理接口用作你的域模型。Hibernate Test Suite 在 org.hibernate.test.dynamicentity.tuplizer2 下有具有完全相同风格的例子。下面是该包里的一些代码:
/**
* A very trivial JDK Proxy InvocationHandler implementation where we proxy an interface as
* the domain model and simply store persistent state in an internal Map. This is an extremely
* trivial example meant only for illustration.
*/
public final class DataProxyHandler implements InvocationHandler {
private String entityName;
private HashMap data = new HashMap();
public DataProxyHandler(String entityName, Serializable id) {
this.entityName = entityName;
data.put( "Id", id );
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if ( methodName.startsWith( "set" ) ) {
String propertyName = methodName.substring( 3 );
data.put( propertyName, args[0] );
}
else if ( methodName.startsWith( "get" ) ) {
String propertyName = methodName.substring( 3 );
return data.get( propertyName );
}
else if ( "toString".equals( methodName ) ) {
return entityName + "#" + data.get( "Id" );
}
else if ( "hashCode".equals( methodName ) ) {
return new Integer( this.hashCode() );
}
return null;
}
public String getEntityName() {
return entityName;
}
public HashMap getData() {
return data;
}
}
/**
*
*/
public class ProxyHelper {
public static String extractEntityName(Object object) {
// Our custom java.lang.reflect.Proxy instances actually bundle
// their appropriate entity name, so we simply extract it from there
// if this represents one of our proxies; otherwise, we return null
if ( Proxy.isProxyClass( object.getClass() ) ) {
InvocationHandler handler = Proxy.getInvocationHandler( object );
if ( DataProxyHandler.class.isAssignableFrom( handler.getClass() ) ) {
DataProxyHandler myHandler = ( DataProxyHandler ) handler;
return myHandler.getEntityName();
}
}
return null;
}
// various other utility methods ....
}
/**
* The EntityNameResolver implementation.
* IMPL NOTE : An EntityNameResolver really defines a strategy for how entity names should be
* resolved. Since this particular impl can handle resolution for all of our entities we want to
* take advantage of the fact that SessionFactoryImpl keeps these in a Set so that we only ever
* have one instance registered. Why? Well, when it comes time to resolve an entity name,
* Hibernate must iterate over all the registered resolvers. So keeping that number down
* helps that process be as speedy as possible. Hence the equals and hashCode impls
*/
public class MyEntityNameResolver implements EntityNameResolver {
public static final MyEntityNameResolver INSTANCE = new MyEntityNameResolver();
public String resolveEntityName(Object entity) {
return ProxyHelper.extractEntityName( entity );
}
public boolean equals(Object obj) {
return getClass().equals( obj.getClass() );
}
public int hashCode() {
return getClass().hashCode();
}
}
public class MyEntityTuplizer extends PojoEntityTuplizer {
public MyEntityTuplizer(EntityMetamodel entityMetamodel, PersistentClass mappedEntity) {
super( entityMetamodel, mappedEntity );
}
public EntityNameResolver[] getEntityNameResolvers() {
return new EntityNameResolver[] { MyEntityNameResolver.INSTANCE };
}
public String determineConcreteSubclassEntityName(Object entityInstance, SessionFactoryImplementor factory) {
String entityName = ProxyHelper.extractEntityName( entityInstance );
if ( entityName == null ) {
entityName = super.determineConcreteSubclassEntityName( entityInstance, factory );
}
return entityName;
}
...
}
为了注册 org.hibernate.EntityNameResolver
,用户必须:
实现自定义的 Tuplizer 并实现 getEntityNameResolvers
方法。
用 registerEntityNameResolver
方法注册到 org.hibernate.impl.SessionFactoryImpl
(它是 org.hibernate.SessionFactory
的实现类)。
版权 © 2004 Red Hat, Inc.