Entity
A Hibernate entity is, typically, a sophisticated Plain Old Java Object (POJO). It is sophisticated because it represents a business model whose data is assumed to be persistent. It's always decorated with various annotations, which enable additional characteristics, among other things. Or, it is configured using an hbm
Hibernate mapping XML file. When an entity contains other entities, or a collection of other entities, this implies a database association for which you have to declare the proper mapping configuration to define the relationship type.
An entity can also embed other POJOs that are not entities. In such cases, the other entities are considered value objects. They have no identity, and have little business significance on their own. (We will discuss this further when we talk about the @Embedded
and @Embeddable
annotations in Chapter 2, Advanced Mapping).
Entity lifecycle
You should already be familiar with entity lifecycle. However, here is a different perspective of the different phases of the lifecycle.
Before discussing the lifecycle of an entity, it is important to not think of an entity as a POJO. Instead, if you keep reminding yourself that an entity is the persistent model of business data, you will easily understand the lifecycle.
The lifecycle begins when you instantiate an entity class. At this point, the entity has no presence in the persistence context; therefore, no data has been inserted in the database and no unique ID is assigned to the new entity. At this phase of the lifecycle, the entity is said to be in the Transient state.
Once you save your new entity by calling session.save()
, your entity is now in the Persistent state, because at this point the session is managing it.
What happens to the entities after the session is closed? In this case, your entity has no presence in the persistence context, but it has a presence in the database. This state is called Detached.
There is another state, which is rarely mentioned, and this is the Deleted state. When you call session.delete()
on an entity, it will fire off a Delete
event and internally sets the entity state to DELETED
. As long as the session is open, you can still undelete the entity by calling session.persist()
.
There are certain lifecycle events that change the entity state, and those are well documented.
Types of entities
As mentioned earlier, you can declare a POJO class as your persistent class. There is another type of entity in Hibernate that is rarely used and perhaps not widely known, and this is map. This is known as a dynamic entity. You can use any implementation of the java.util.Map
interface as a Hibernate entity. This is useful to implement a dynamic business model, which is great for the creation of a quick prototype. Ultimately, you are best off with POJO entities. If you need to implement a dynamic entity, first you can set the default entity mode to MAP
:
Configuration configuration = new Configuration() .configure() .setProperty(Environment.DEFAULT_ENTITY_MODE, EntityMode.MAP.toString());
Then, add a new mapping configuration. Hibernate uses the property name as a map key to get the value, for example, <property name="firstname" …/>
. So, if your map contains other properties which are not included in the named map, they will be ignored by Hibernate:
<hibernate-mapping> <class entity-name="DynamicEntity"> <id name="id" type="long" column="MAP_ID"> <generator class="sequence" /> </id> <property name="firstname" type="string" column="FIRSTNAME" /> <property name="lastname" type="string" column="LASTNAME" /> </class> </hibernate-mapping>
Make sure that you add the new map to your Hibernate configuration. Now, you can use this as an entity. Note that when you call session.save()
, you are passing the name of the entity as the first argument:
Map<String, String> myMap = new HashMap<String, String>(); myMap.put("firstname", "John"); myMap.put("lastname", "Smith"); Session session = HibernateUtil .getSessionFactory() .getCurrentSession(); Transaction transaction = session.beginTransaction(); try { session.save("DynamicEntity", myMap); // notice entity name transaction.commit(); } catch (Exception e) { transaction.rollback(); // log error } finally { if (session.isOpen()) session.close(); }
Note
This used to be different in version 3.6. You didn't need to set the default entity mode on the configuration. The Session
interface provided an API, which would return another session that supported dynamic entity. This was session.getSession(EntityMode.MAP)
, and this returned a new session, which inherited the JDBC connection and the transaction. However, this was removed in Hibernate 4.
Identity crisis
Each entity class has a property that is marked as the unique identifier of that entity. This could be a primitive data type or another Java class, which would represent a composite ID. (You'll see this in Chapter 2, Advanced Mapping, when we talk about mapping) For now, having an ID is still optional, but in future releases of Hibernate, this will no longer be the case and every entity class must have an ID attribute.
Furthermore, as Hibernate is responsible for generating and setting the ID, you should always protect it by making sure that the setter method for the ID is private so that you don't accidentally set the ID in your code. Hibernate can access private fields using reflection. Hibernate also requires entities to have a no-arg
constructor.
In some cases, you have to override the equals()
and hashCode()
methods, especially if you are keeping the detached objects around and want to reattach them or need to add them to a set. This is because outside of the persistence context the Java equality may fail even though you are comparing two entity instances that represent the same row. The default implementation of the equals()
method only checks whether the two instances are the same reference.
If you are sure that both objects have an ID assigned, then, in addition to reference equality check, you can compare their identifiers for equality. If you can't rely on the ID property but an entity can be uniquely identified by a business key, such as user ID or social security number, then you can compare the business keys.
It's not a good idea to compare all properties for equality check. There are several reasons, as follows:
- First, if you keep a detached object around and then retrieve it again in another session, Hibernate can't tell that they are the same entities if you modify one of them.
- Second, you may have a long list of properties and your code will look messy, and if you add a new property, you may forget to modify the
equals()
method. - Finally, this approach will lead to equal entities if you have multiple database rows with the same values, and they should be treated as different entities because they represent different rows. (For example, if two people live at the same address and one person moves, you may accidentally change the address for both.)
Beyond JPA
When you decorate your class with annotations, you are empowering your objects with additional features. There are certain features that are provided by Hibernate, which are not available in JPA.
Most of these features are provided through Hibernate annotations, which are packaged separately. Some annotations affect the behavior of your entity, and some are there to make mapping easier and more powerful.
The behavior modifying annotations that are worth noting here are as follows:
@Immutable
: This makes an entity immutable.@SelectBeforeUpdate
: This is great for reducing unnecessary updates to the database and reducing contention. However, it does make an extra call through JDBC.@BatchSize
: This can be used to limit the size of a collection on fetch.@DynamicInsert
and@DynamicUpdat
: These prevent null properties from being included in the dynamic SQL generation for both insert and update.@OptimisticLocking
: This is used to define the type of optimistic lock.@Fetch
: This can be used to define@FetchMode
. You can instruct Hibernate to useJoin
,Select
, orSub Select
. TheJoin
query uses outer join to load the related entities, theSelect
query issues individual SQL select statements, andSub Select
is self-explanatory. (This is different from JPA's@FetchType
, which is used to decide to perform lazy fetch or not.)@Filter
: This is used to limit the entities that are returned.
It's worth exploring both the org.hibernate.annotations
JavaDocs and the JPA annotations. We will cover Hibernate annotations (beyond JPA) that modify mappings and associations in the next chapter, when we discuss mappings. We will also return to @Fetch
and @Filter
when we discuss Fetching in Chapter 4, Advanced Fetching.