Hibernate.orgCommunity Documentation

第 4 章 持久化类(Persistent Classes)

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

Persistent classes are classes in an application that implement the entities of the business problem (e.g. Customer and Order in an E-commerce application). The term "persistent" here means that the classes are able to be persisted, not that they are in the persistent state (see 第 11.1 节 “Hibernate 对象状态(object states)” for discussion).

Hibernate works best if these classes follow some simple rules, also known as the Plain Old Java Object (POJO) programming model. However, none of these rules are hard requirements. Indeed, Hibernate assumes very little about the nature of your persistent objects. You can express a domain model in other ways (using trees of java.util.Map instances, for example).

例 4.1. Simple POJO representing a cat

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 has a property named id. This property maps to the primary key column(s) of the underlying database table. The type of the identifier property can be any "basic" type (see ???). See 第 9.4 节 “组件作为联合标识符(Components as composite identifiers)” for information on mapping composite (multi-column) identifiers.

注意

Identifiers do not necessarily need to identify column(s) in the database physically defined as a primary key. They should just identify columns that can be used to uniquely identify rows in the underlying table.

我们建议你对持久化类声明命名一致的标识属性。我们还建议你使用一个可以为空(也就是说,不是原始类型)的类型。

A central feature of Hibernate, proxies (lazy loading), depends upon the persistent class being either non-final, or the implementation of an interface that declares all public methods. You can persist final classes that do not implement an interface with Hibernate; you will not, however, be able to use proxies for lazy association fetching which will ultimately limit your options for performance tuning. To persist a final class which does not implement a "full" interface you must disable proxy generation. See 例 4.2 “Disabling proxies in hbm.xml” and 例 4.3 “Disabling proxies in annotations”.



If the final class does implement a proper interface, you could alternatively tell Hibernate to use the interface instead when generating the proxies. See 例 4.4 “Proxying an interface in hbm.xml” and 例 4.5 “Proxying an interface in annotations”.



You should also avoid declaring public final methods as this will again limit the ability to generate proxies from this class. If you want to use a class with public final methods, you must explicitly disable proxying. Again, see 例 4.2 “Disabling proxies in hbm.xml” and 例 4.3 “Disabling proxies in annotations”.

子类也必须遵守第一条和第二条规则。它从超类 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 第 13.1.3 节 “关注对象标识(Considering object identity)”). Immutable or unique properties are usually good candidates for a business key.

运行期的持久化实体没有必要一定表示为像 POJO 类或 JavaBean 对象那样的形式。Hibernate 也支持动态模型 (在运行期使用 MapMap)和象 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-mapSessionFactory 设置了默认的实体模式之后,可以在运行期使用 MapMap

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 第 20 章 XML 映射.

org.hibernate.tuple.Tuplizer and its sub-interfaces are responsible for managing a particular representation of a piece of data given that representation's org.hibernate.EntityMode. If a given piece of data is thought of as a data structure, then a tuplizer is the thing that knows how to create such a data structure, how to extract values from such a data structure and how to inject values into such a data structure. For example, for the POJO entity mode, the corresponding tuplizer knows how create the POJO through its constructor. It also knows how to access the POJO properties using the defined property accessors.

There are two (high-level) types of Tuplizers:

Users can also plug in their own tuplizers. Perhaps you require that java.util.Map implementation other than java.util.HashMap be used while in the dynamic-map entity-mode. Or perhaps you need to define a different proxy generation strategy than the one used by default. Both would be achieved by defining a custom tuplizer implementation. Tuplizer definitions are attached to the entity or component mapping they are meant to manage. Going back to the example of our Customer entity, 例 4.6 “Specify custom tuplizers in annotations” shows how to specify a custom org.hibernate.tuple.entity.EntityTuplizer using annotations while 例 4.7 “Specify custom tuplizers in hbm.xml” shows how to do the same in hbm.xml



org.hibernate.EntityNameResolver is a contract for resolving the entity name of a given entity instance. The interface defines a single method resolveEntityName which is passed the entity instance and is expected to return the appropriate entity name (null is allowed and would indicate that the resolver does not know how to resolve the entity name of the given entity instance). Generally speaking, an org.hibernate.EntityNameResolver is going to be most useful in the case of dynamic models. One example might be using proxied interfaces as your domain model. The hibernate test suite has an example of this exact style of usage under the org.hibernate.test.dynamicentity.tuplizer2. Here is some of the code from that package for illustration.

/**

 * 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 implementations as is
 */
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. Implement a custom tuplizer (see 第 4.5 节 “元组片断映射(Tuplizers)”), implementing the getEntityNameResolvers method

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