Hibernate.orgCommunity Documentation
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). Not all instances of a persistent class are considered to be in the persistent state. For example, an instance can instead be transient or detached.
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, Hibernate3 assumes very little about
the nature of your persistent objects. You can express a domain model in other
ways (using trees of Map
instances, for example).
Most Java applications require a persistent class representing felines. For example:
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); } }
The four main rules of persistent classes are explored in more detail in the following sections.
Cat
has a no-argument constructor. All persistent classes must
have a default constructor (which can be non-public) so that Hibernate can instantiate
them using Constructor.newInstance()
. It is recommended that you have a
default constructor with at least package visibility for runtime proxy
generation in Hibernate.
Cat
has a property called id
. This property
maps to the primary key column of a database table. The property might have been called
anything, and its type might have been any primitive type, any primitive "wrapper"
type, java.lang.String
or java.util.Date
. If
your legacy database table has composite keys, you can use a user-defined class
with properties of these types (see the section on composite identifiers later in the chapter.)
The identifier property is strictly optional. You can leave them off and let Hibernate keep track of object identifiers internally. We do not recommend this, however.
In fact, some functionality is available only to classes that declare an identifier property:
Transitive reattachment for detached objects (cascade update or cascade merge) - see Section 10.11, “Transitive persistence”
Session.saveOrUpdate()
Session.merge()
We recommend that you declare consistently-named identifier properties on persistent classes and that you use a nullable (i.e., non-primitive) type.
A central feature of Hibernate, proxies, 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.
You should also avoid declaring public final
methods on the
non-final classes. If you want to use a class with a public final
method, you must explicitly disable proxying by setting lazy="false"
.
Cat
declares accessor methods for all its persistent fields.
Many other ORM tools directly persist instance variables. It is
better to provide an indirection between the relational schema and internal
data structures of the class. By default, Hibernate persists JavaBeans style
properties and recognizes method names of the form getFoo
,
isFoo
and setFoo
. If required, you can switch to direct
field access for particular properties.
Properties need not be declared public - Hibernate can
persist a property with a default, protected
or
private
get / set pair.
A subclass must also observe the first and second rules. It inherits its
identifier property from the superclass, Cat
. For example:
package eg; public class DomesticCat extends Cat { private String name; public String getName() { return name; } protected void setName(String name) { this.name=name; } }
You have to override the equals()
and hashCode()
methods if you:
intend to put instances of persistent classes in a Set
(the recommended way to represent many-valued associations);
and
intend to use reattachment of detached instances
Hibernate guarantees equivalence of persistent identity (database row) and Java identity
only inside a particular session scope. When you mix instances retrieved in
different sessions, you must implement equals()
and
hashCode()
if you wish to have meaningful semantics for
Set
s.
The most obvious way is to implement equals()
/hashCode()
by comparing the identifier value of both objects. If the value is the same, both must
be the same database row, because they are equal. If both are added to a Set
,
you will only have one element in the Set
). Unfortunately, you cannot use that
approach with generated identifiers. Hibernate will only assign identifier values to objects
that are persistent; a newly created instance will not have any identifier value. Furthermore,
if an instance is unsaved and currently in a Set
, saving it will assign
an identifier value to the object. If equals()
and hashCode()
are based on the identifier value, the hash code would change, breaking the contract of the
Set
. See the Hibernate website for a full discussion of this problem. This is not
a Hibernate issue, but normal Java semantics of object identity and equality.
It is recommended that you implement equals()
and hashCode()
using Business key equality. Business key equality means that the
equals()
method compares only the properties that form the business
key. It is a key that would identify our instance in the real world (a
natural candidate key):
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 Section 11.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.
Persistent entities do not necessarily have to be represented as POJO classes
or as JavaBean objects at runtime. Hibernate also supports dynamic models
(using Map
s of Map
s at runtime) and the
representation of entities as DOM4J trees. With this approach, you do not
write persistent classes, only mapping files.
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
Table 3.3, “Hibernate Configuration Properties”).
The following examples demonstrate the representation using Map
s.
First, in the mapping file an entity-name
has to be declared
instead of, or in addition to, a class 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>
Even though associations are declared using target class names, the target type of associations can also be a dynamic entity instead of a POJO.
After setting the default entity mode to dynamic-map
for the SessionFactory
, you can, at runtime, work with
Map
s of Map
s:
Session s = openSession(); Transaction tx = s.beginTransaction(); Session s = openSession(); // 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();
One of the main advantages of dynamic mapping is quick turnaround time for prototyping, without the need for entity class implementation. However, you lose compile-time type checking and will likely deal with many exceptions at runtime. As a result of the Hibernate mapping, the database schema can easily be normalized and sound, allowing to add a proper domain model implementation on top later on.
Entity representation modes can also be set on a per Session
basis:
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
Please note that the call to getSession()
using an
EntityMode
is on the Session
API, not the
SessionFactory
. That way, the new Session
shares the underlying JDBC connection, transaction, and other context
information. This means you do not have to call flush()
and close()
on the secondary Session
, and
also leave the transaction and connection handling to the primary unit of work.
More information about the XML representation capabilities can be found in Chapter 18, XML Mapping.
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
and how to extract values from and 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, represented by the
org.hibernate.tuple.entity.EntityTuplizer
and org.hibernate.tuple.component.ComponentTuplizer
interfaces. EntityTuplizer
s are responsible for managing the above mentioned
contracts in regards to entities, while ComponentTuplizer
s do the same for
components.
Users can also plug in their own tuplizers. Perhaps you require that a 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:
<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(); } } }
The org.hibernate.EntityNameResolver
interface 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 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; } ... }
In order to register an org.hibernate.EntityNameResolver
users must either:
Implement a custom Tuplizer, implementing
the getEntityNameResolvers
method.
Register it with the org.hibernate.impl.SessionFactoryImpl
(which is the
implementation class for org.hibernate.SessionFactory
) using the
registerEntityNameResolver
method.
Copyright © 2004 Red Hat Middleware, LLC.