Hibernate.orgCommunity Documentation
Table of Contents
The term domain model comes from the realm of data modeling. It is the model that ultimately describes the problem domain you are working in. Sometimes you will also hear the term "persistent classes".
Ultimately the application's domain model is the central character in an ORM. They make up the classes
you wish to map. Hibernate works best if these classes follow the Plain Old Java Object (POJO) / JavaBean
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).
Even though Hibernate does not consider all of these rules as hard requirements, JPA does specify some of them. Therefore, if you are concerned about JPA provider portability it is best to stick to the strict POJO model. We will point out these concerns where applicable.
This chapter will describe the characteristics of a persistable domain model. However, it will not discuss defining the mapping for the domain model. That is a massive topic in its own right and is the subject of an entire dedicated manual. See the Hibernate Domain Model Mapping Guide from the documentation site.
This section explores domain models defined as POJOs.
The POJO should have a no-argument constructor. Both Hibernate and JPA require this.
JPA requires that this constructor be defined as public or protected. Hibernate for the most part does note care about the visibility as long as the system's SecurityManager allows overriding the visibility. That said, the constructor should be defined with at least package visibility if you wish to leverage runtime proxy generation.
Historically this was considered optional. However, not defining identifier attribute(s) on the entity should be considered a deprecated feature that will be removed in an upcoming release.
The identifier attribute does not necessarily need to be mapped to the column(s) that physically define the primary key. However, it should map to column(s) that can uniquely identify each row.
We recommend that you declare consistently-named identifier attributes on persistent classes and that you use a nullable (i.e., non-primitive) type.
A central feature of Hibernate is the ability to lazy load an entity's data via runtime proxies. This feature depends upon the entity class being non-final or else implementing an interface that declares all the attribute getters/setters. You can still persist final classes that do not implement such an interface with Hibernate; you just will not be able to use proxies for lazy association fetching which will ultimately limit your options for performance tuning.
Starting in 5.0 Hibernate offers a more robust version of bytecode enhancement as another means for handling lazy loading. Hibernate had some bytecode re-writing capabilities prior to 5.0 but they were very rudimentary.
You should also avoid declaring persistent attribute getters and setters as final for the reasons already mentioned.
Although not required, it is recommended to follow JavaBean conventions by defining getters and setters for you entities persistent attributes. Hibernate can also directly access the entity's fields.
Attributes (whether fields or getters/setters) need not be declared public. Hibernate can deal with attributes declared with public, protected, package or private visibility.
Much of the discussion in this section deals with the relation of an entity to a Hibernate Session; whether the entity is managed or transient or detached. These topics are explained in ??? if you are unfamiliar with them.
Whether to implement equals()
and hashCode()
methods in your domain model, let alone how to implement them, is a surprisingly tricky discussion
when it comes to ORM.
There is really just one absolute case: a class that acts as an identifier must implement equals/hashCode based on the id value(s). Generally this is pertinent for user classes used as composite identifiers. Beyond this one absolute case and the few others we will discuss below, you may want to consider not implementing equals/hashCode.
So what's all the fuss? Normally, most Java objects provide a built-in equals() and hashCode() based
on the object's identity, so each new object will be different from all others. This is generally what
you want in ordinary Java programming. Conceptually however this starts to break down when you start
to think about the possibility multiple instances of a class representing the same data which is in
fact the case when we start dealing with data from a database. Every time we load a specific
Person
from the database we would naturally get a unique instance.
Hibernate, however, works hard to make sure that does not happen within a given Session. In fact
Hibernate guarantees equivalence of persistent identity (database row) and Java identity inside a
particular session scope. So if we ask a Hibernate Session to load that specific Person multiple
times we will actually get back the same instance:
Example 2.1. Scope of identity
Session session = ...; Person p1 = session.get( Person.class, 1 ); Person p2 = session.get( Person.class, 1); // this evaluates to true assert p1 == p2;
Consider another example using a persistent java.util.Set
:
Example 2.2. Set usage with Session-scoped identity
Session session = ...; Club club = session.get( Club.class, 1 ); Person p1 = session.get( Person.class, 1 ); Person p2 = session.get( Person.class, 1); club.getMembers().add( p1 ); club.getMembers().add( p2 ); // this evaluates to true assert club.getMembers.size() == 1;
However, the semantic changes when we mix instances loaded from different Sessions:
Example 2.3. Mixed Sessions
Session session1 = ...; Session session2 = ...; Person p1 = session1.get( Person.class, 1 ); Person p2 = session2.get( Person.class, 1); // this evaluates to false assert p1 == p2;
Session session1 = ...; Session session2 = ...; Club club = session1.get( Club.class, 1 ); Person p1 = session1.get( Person.class, 1 ); Person p2 = session2.get( Person.class, 1); club.getMembers().add( p1 ); club.getMembers().add( p2 ); // this evaluates to ... well it depends assert club.getMembers.size() == 1;
Specifically the outcome in this last example will depend on whether the Person class implemented equals/hashCode, and if so how.
Consider yet another case:
Example 2.4. Sets with transient entities
Session session = ...; Club club = session.get( Club.class, 1 ); Person p1 = new Person(...); Person p2 = new Person(...); club.getMembers().add( p1 ); club.getMembers().add( p2 ); // this evaluates to ... again, it depends assert club.getMembers.size() == 1;
In cases where you will be dealing with entities outside of a Session (whether they be transient or detached), especially in cases where you will be using them in Java collections, you should consider implementing equals/hashCode.
A common initial approach is to use the entity's identifier attribute as the basis for equals/hashCode calculations:
Example 2.5. Naive equals/hashCode implementation
@Entity public class Person { @Id @GeneratedValue private Integer id; ... @Override public int hashCode() { return id != null ? id.hashCode() : 0; } @Override public boolean equals() { if ( this == o ) { return true; } if ( !( o instanceof Person ) ) { return false; } if ( id == null ) { return false; } final Person other = (Person) o; return id.equals( other.id ); } }
It turns out that this still breaks when adding transient instance of Person to a set as we saw in the last example:
Example 2.6. Still trouble
Session session = ...; session.getTransaction().begin(); Club club = session.get( Club.class, 1 ); Person p1 = new Person(...); Person p2 = new Person(...); club.getMembers().add( p1 ); club.getMembers().add( p2 ); session.getTransaction().commit(); // will actually resolve to false! assert club.getMembers().contains( p1 );
The issue here is a conflict between (1) the use of generated identifier and
(2) the contract of Set and (3) the equals/hashCode implementations. Set says that the
equals/hashCode value for an object should not change while it is part of the Set.
But that is exactly what happened here because the equals/hasCode are based on the (generated) id,
which was not set until the session.getTransaction().commit()
call.
Note that this is just a concern when using generated identifiers. If you are using assigned identifiers this will not be a problem, assuming the identifier value is assigned prior to adding to the Set.
Another option is to force the identifier to be generated and set prior to adding to the Set:
Example 2.7. Forcing identifier generation
Session session = ...; session.getTransaction().begin(); Club club = session.get( Club.class, 1 ); Person p1 = new Person(...); Person p2 = new Person(...); session.save( p1 ); session.save( p2 ); session.flush(); club.getMembers().add( p1 ); club.getMembers().add( p2 ); session.getTransaction().commit(); // will actually resolve to false! assert club.getMembers().contains( p1 );
But this is often not feasible.
The final approach is to use a "better" equals/hashCode implementation, making use of a natural-id or business-key.
Example 2.8. Better equals/hashCode with natural-id
@Entity public class Person { @Id @GeneratedValue private Integer id; @NaturalId private String ssn; protected Person() { // ctor for ORM } public Person(String ssn) { // ctor for app this.ssn = ssn; } ... @Override public int hashCode() { assert ssn != null; return ssn.hashCode(); } @Override public boolean equals() { if ( this == o ) { return true; } if ( !( o instanceof Person ) ) { return false; } final Person other = (Person) o; assert ssn != null; assert other.ssn != null; return ssn.equals( other.ssn ); } }
As you can see the question of equals/hashCode is not trivial. Nor is there a one-size-fits-all solution.
Persistent entities do not necessarily have to be represented as
POJO/JavaBean classes. Hibernate also supports dynamic models (using Map
s of
Map
s at runtime). With this approach, you do not write persistent classes,
only mapping files.
The mapping of dynamic models is beyond the scope of this document. We will discuss using such models with Hibernate, but for mapping see the Hibernate Domain Model Mapping documentation.
A given entity has just one entity mode within a given SessionFactory. This is a change from previous versions which allowed to define multiple entity modes for an entity and to select which to load. Entity modes can now also be mixed within a domain model; a dynamic entity might reference a POJO entity, and vice versa.
Example 2.9. Working with Dynamic Domain Models
Session s = openSession(); Transaction tx = s.beginTransaction(); // Create a customer entity Map david = new HashMap(); david.put("name", "David"); // Create an organization entity 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();
The main advantage of dynamic models is quick turnaround time for prototyping without the need for entity class implementation. The main down-fall is that you lose compile-time type checking and will likely deal with many exceptions at runtime. However, 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.
It is also interesting to note that dynamic models are great for certain integration use cases as well. Envers, for example, makes extensive use of dynamic models to represent the historical data.