Hibernate.orgCommunity Documentation

第 4 章 持久化类(Persistent Classes)

4.1. 一个简单的 POJO 例子
4.1.1. 实现一个默认的(即无参数的)构造方法(constructor)
4.1.2. 提供一个标识属性(identifier property)(可选)
4.1.3. 使用非final的类(可选)
4.1.4. 为持久化字段声明访问器(accessors)和是否可变的标志(mutators)(可选)
4.2. 实现继承(Inheritance)
4.3. 实现 equals() 和 hashCode() 方法:
4.4. 动态模型(Dynamic models)
4.5. 元组片断映射(Tuplizers)
4.6. EntityNameResolvers

在应用程序中,用来实现业务问题实体的(如,在电子商务应用程序中的 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 继承了标识属性。例如:

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() 方法:

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.

运行期的持久化实体没有必要一定表示为像 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,用户必须:

  1. 实现自定义的 Tuplizer 并实现 getEntityNameResolvers 方法。

  2. 用 registerEntityNameResolver 方法注册到 org.hibernate.impl.SessionFactoryImpl(它是 org.hibernate.SessionFactory 的实现类)。