Mastering Hibernate - Sample Chapter
Mastering Hibernate - Sample Chapter
ee
P U B L I S H I N G
C o m m u n i t y
E x p e r i e n c e
D i s t i l l e d
$ 29.99 US
19.99 UK
pl
Mastering Hibernate
Ramin Rad
Mastering Hibernate
Mastering Hibernate
Sa
m
Ramin Rad
Preface
Object-Relational Mapping is a difficult problem, and the Hibernate team has solved
that problem for us. Some developers have a love-hate relationship with Hibernate
because it has made life easier for many developers, but at times, unexpected
behavior is encountered and it quickly becomes a mystery. This book was written to
uncover these mysteries. It does so by looking under the hood; it first discusses the
internals of Hibernate, and later it covers advanced topics on mapping techniques,
annotations, fetching, caching, and versioning. It also addresses other topics that are
of interest to technical leads and software architects, such as statistics and metrics,
concurrency, and transaction management.
Although, it is recommended that you read this book in its entirety, the topics are
carefully outlined so that it can also be used as a reference if you wish to learn about
a specific feature.
Preface
Quick Hibernate
Hibernate session:
Session internals
Contextual sessions
Stateless sessions
Hibernate entities:
Entity lifecycle
Types of entities
Identity crisis
Beyond JPA
Proxy objects
[1]
Batch processing:
[2]
Chapter 1
The main objectives of this book are to help you understand Hibernate beyond the
basics, make you appreciate the ORM problem, and show you why Hibernate is
one of the best solutions that exists today. We focus more on the Hibernate API and
occasionally explore the JPA counterpart. This book assumes that you have a basic
understanding of Hibernate and have used it in the past, or you are currently using
it. If this is not the case for you, please visit the Hibernate documentation online,
as it offers guides to get started with Hibernate and more.
Quick Hibernate
In this section, we take a glance at a typical Hibernate application and its
components. Hibernate is designed to work in a standalone application as well as a
Java Enterprise application, such as a Web or EJB application. All topics discussed
here are covered in detail throughout this book.
The standalone version of a Hibernate application is comprised of the components
that are shown in the following figure:
We, the application developers, create the components that are depicted by the white
boxes, namely the data access classes, entity classes, and configuration and mapping.
Everything else is provided in form of a JAR or a runtime environment.
The Hibernate session factory is responsible for creating a session when your
application requests it. The factory is configured using the configuration files that
you provide. Some of the configuration settings are used for JDBC parameters, such
as database username, password, and connection URL. Other parameters are used to
modify the behavior of a session and the factory.
[3]
You may already be familiar with the configuration file that looks like the following:
<hibernate-configuration>
<session-factory>
<property name="connection.driver_class">
org.postgresql.Driver
</property>
<property name="connection.url">
jdbc:postgresql://localhost:5432/packtdb
</property>
<property name="connection.username">user</property>
<property name="connection.password">pass</property>
<property name="connection.pool_size">1</property>
<property name="dialect">
org.hibernate.dialect.PostgreSQLDialect
</property>
<property name="current_session_context_class">
thread
</property>
<property name="show_sql">true</property>
<property name="format_sql">true</property>
</session-factory>
</hibernate-configuration>
The initialization of the session factory has changed slightly in the newer versions of
Hibernate. You now have to use a service registry, as shown here:
private static SessionFactory buildSessionFactory() {
try {
// Create the SessionFactory from hibernate.cfg.xml
// in resources directory
Configuration configuration = new Configuration()
.configure()
.addAnnotatedClass(Person.class);
StandardServiceRegistryBuilder builder =
new StandardServiceRegistryBuilder()
.applySettings(configuration.getProperties());
serviceRegistry = builder.build();
return configuration
.buildSessionFactory(serviceRegistry);
}
catch (Throwable ex) {
// do something with the exception
throw new ExceptionInInitializerError(ex);
}
}
[4]
Chapter 1
The data objects are represented by Hibernate entities. A simple Hibernate entity,
which uses annotation, is shown here:
@Entity
public class Person {
@Id
@GeneratedValue
private long id;
private String firstname;
private String lastname;
private String ssn;
private Date birthdate;
// getters and setters
}
The last component that we have to create is the data access class, which is the
service that provides the Create, Read, Update, Delete (CRUD) operations for
entities. An example of storing an entity is shown here:
Session session = HibernateUtil.getSessionFactory()
.getCurrentSession();
Transaction transaction = session.beginTransaction();
try {
Person person = new Person();
person.setFirstname("John");
person.setLastname("Williams");
person.setBirthdate(randomBirthdate());
person.setSsn(randomSsn());
session.save(person);
transaction.commit();
} catch (Exception e) {
transaction.rollback();
e.printStackTrace();
} finally {
if (session.isOpen())
session.close();
}
That's it!
[5]
The Java Enterprise application doesn't look very different from the standalone
version. The difference is mainly in the application stack and where each
component resides, as shown in the following diagram:
This example provides a context of what we will discuss in detail throughout this
book. Let's begin by taking a closer look at a Hibernate session.
Session internals
If you have written at least one Hibernate application in the past, you already know
what an entity and a session are. However, most of the time, developers don't think
about the session, the entity, and their lifecycles, or about what occurs inside a
session when an entity is saved or updated. There are fundamental concepts
that one must understand in order to utilize Hibernate effectively.
The Hibernate session is where persistence work is performed for each thread of
execution, and it manages the persistence context. Therefore, it's not thread-safe; this
means that multiple threads should not access or use the same session at the same
time. As you may know, sessions are created by calling the session factory, and there
is only one factory per storage unit, although you can have multiple session factories
pointing to different databases.
[6]
Chapter 1
Transaction management means different things for the internal session contexts.
This is an important architectural discussion, which will be covered in detail in
Chapter 8, Addressing Architecture. However, in the next section, we will discuss
contextual sessions, and for this, we need to define session scope and transaction
boundaries.
The persistence unit of work begins when you start a new session. Hibernate will
not allow modification to the persistence context without an active transaction. You
either begin a local transaction (in the JDBC session context), or one is started by
JTA or the managed context. The unit of persistence work ends when you commit or
rollback a transaction. This also closes the session automatically, assuming that the
default is not overridden. If you start a local transaction and don't commit it or roll
back, Hibernate quietly clears the persistence context when you close the session. If
you don't close the session after your work is done, you will most definitely have a
connection leak. (This behavior varies for different session contexts.)
When you call various methods on the session object, these methods are translated
into a corresponding event. For example, the session.save() method is translated
into an instance of the SaveOrUpdateEvent class, and the actual operations are
managed by event listeners. Each session has a list of event listeners, which perform
certain operations for each event that is fired off in the execution path. As another
example, when you check to see whether the session is dirty, session.isDirty(),
a DirtyCheckEvent event, is fired off to check the action queue to see whether any
actions are queued up, and if so, it marks the session as dirty.
[8]
Chapter 1
So what is the action queue? Most Hibernate events correspond to one or more
actions. Each session has an instance of the ActionQueue class that holds a list
of various actions. These actions are simple insert, delete, and update actions
on entities and collections. While you are working within a session and updating
the persistence context, actions get queued up as various events are fired. Finally,
at the end of the transaction, on commit, these actions are translated into Data
Manipulation Language (DML) statements by the corresponding entity
persister classes (for example, SingleTableEntityPersister), which are then
executed in the database. (The composition of the SQL statements is managed by
classes in the org.hibernate.sql package, which use the dialect classes to form
a syntactically correct SQL statement.)
This is basically what happens inside a Hibernate session from the time it is created
until it is closed. Next, we will discuss various session contexts and how they differ.
What is the difference between a session and an entity manager?
Session is the Hibernate API to manage the persistence context.
Entity manager is its counterpart in the JPA world. Although
new versions of Hibernate implement the JPA specifications,
you still have a choice to use Hibernate or JPA APIs. Your code
is fully portable if you choose the JPA APIs, regardless of the
implementation. On the other hand, you will have more control
if you choose the Hibernate API.
Isn't the session the same as the persistence context?
No. Besides doing a lot of other things, the session also manages the
persistence context, which happens to be its main job. Think of the
persistence context as your copy of the database rows in memory,
managed by the session. (Obviously, these are only the rows that
you are working with.)
Contextual session
The session behaves differently in various contexts. This behavior is defined in
terms of session scope, transaction boundaries, and the cleanup work. As mentioned
earlier, there are three types of contextual sessions that are natively supported by
Hibernate. These are as follows:
JTASessionContext
ThreadLocalSessionContext
ManagedSessionContext
[9]
In other words, when you call the getCurrentSession of the Hibernate session
factory API, the behavior is as follows:
thread: This session factory API returns the current session that is associated
with the current thread. (If one doesn't exist, this will create one and associate
it with the current thread.)
jta: This session factory API returns the current session that is associated
with the current global transaction. In case of none, one is created and
associated with the current global transaction through JTA.
[ 10 ]
Chapter 1
session for the current thread. This is useful when you want to call multiple
data access classes and don't want to pass the session object to each class.
Refer to the following discussion on session per operation.
The JTA and threadlocal session contexts might be what you are used to and
are easier to understand. The Managed session context is best for long-running
conversations that represent a business unit of work, which spans multiple requests.
If this sounds a bit cryptic, do not worry; we will come back to this later on.
[ 11 ]
In a sense, the DAO orchestration that we discussed earlier implements this pattern.
However, in that case, everything occurred in one client request (one thread of
execution): the session was opened, the transaction started, DAO methods were
called, the session was flushed and cleared, the transaction was committed, the
response was sent to the client, and the thread ended. This is not considered a longrunning conversation.
When implementing session per conversation, as the name indicates, the session
scope goes beyond a single thread and a single database transaction. This is why a
managed session context is best for this pattern. You can control flush behavior so
that synchronization doesn't occur until you are ready to perform it.
In order to understand how this works, we need an in-depth understanding of entity
lifecycle and transactions. There are various ways of implementing this pattern, and
we will cover these later in this book.
Stateless session
There is another type of session that is supported by Hibernate, and this is stateless
session. The reason this is called stateless is because there is no persistence context
and all entities are considered detached. Additionally, there is the following:
No automatic dirty checking. This means that you have to call session.
update() before closing the session; otherwise, no update statement
will be executed.
[ 12 ]
Chapter 1
You should think of stateless sessions as direct calls to JDBC because this is essentially
what occurs behind the scenes.
One good reason to use stateless sessions is to perform bulk operations. The memory
footprint will be far less and, in some cases, it performs better.
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.
[ 13 ]
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");
[ 14 ]
Chapter 1
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();
}
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.
[ 15 ]
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:
the database and reducing contention. However, it does make an extra call
through JDBC.
[ 16 ]
Chapter 1
@Fetch: This can be used to define @FetchMode. You can instruct Hibernate
to use Join, Select, or Sub Select. The Join query uses outer join to
load the related entities, the Select query issues individual SQL select
statements, and Sub Select is self-explanatory. (This is different from
JPA's @FetchType, which is used to decide to perform lazy fetch or not.)
Proxy objects
If you are familiar with Java reflection, you have heard of the java.lang.reflect.
Proxy class. In a nutshell, you can wrap any object with a proxy and intercept
calls to methods of that object using an invocation handler. Many Java frameworks
use proxy objects, or manipulate bytecode (also called instrumentation) to modify
the behavior of an object. Hibernate uses both ways for different purposes. More
importantly, Hibernate implements its own set of wrapper classes for collection
types. (Refer to classes in org.hibernate.collection.internal.)
If you fetch an entity, Hibernate doesn't fetch the associated collections if they are
marked as lazy fetch. Instead, it waits until you actually try to access the associated
collection. As soon as you access an entity in the associated collection, Hibernate will
fetch the associated entities from the persistence store and will then populate the
wrapped collection for you to access. Hibernate accomplishes this using the internal
collection wrappers. You can actually examine this yourself by writing a simple check,
as follows:
parent = (Parent) session.get(Parent.class, new Long(1));
Set<Child> children = parent.getChildren();
if (children instanceof PersistentSet) {
System.out.println("**** Not java.util.Set");
}
// PersistentSet is located in org.hibernate.collection.internal
[ 17 ]
When working with Hibernate, it is important that you keep in mind how Hibernate
uses proxy objects.
Batch processing
When you interact with the session by saving entities or fetching them from the DB,
Hibernate keeps them around in the persistent context until the session is closed, or
until you evict the object or clear the session. This is Hibernate's first-level cache.
Care must be taken when executing queries that load many objects or when trying to
save a large set of entities. If you don't perform some cleanup work, your JVM will
run out of memory in the middle of the work unit.
There are certain things you can do to avoid such situations. Some of them are
manual work, and others are managed by Hibernate if you provide enough hints or
if you use the right session type.
How does Hibernate know whether it should call JDBC executeBatch?
This decision is made in the entity persister, which is responsible for
persisting an entity via JDBC. Hibernate keeps track of all the DML
statements for each entity type, and when the statement count is more
than 1 for a particular entity type, it will use batch execution.
[ 18 ]
Chapter 1
[ 19 ]
You should use the same mechanism when you are fetching entities. There is a
slight performance hit when you flush and clear the session. However, this is not
significant. Your JDBC connection is still open, and the transaction is still active, and
these are the expensive resources whose lifecycle you need to be concerned with in
your design. (Refer to the earlier discussion on contextual session.)
[ 20 ]
Chapter 1
Summary
The purpose of this chapter was to expose the internals of Hibernate as they relate
to session and entity. This should help you understand Hibernate better, design
and write more elegant code, and when you encounter errors, understand the error
messages clearly.
You will see a lot more sample code in later chapters, but in this chapter, we focused
more on understanding Hibernate, and less on samples.
We learned about session and its behavior in various contexts. We also learned about
entity, explored its lifecycle, and also learned about map types, which is a dynamic
entity. Furthermore, the discussion on entity identity is very important and deserves
careful attention. As discussed in this chapter, Hibernate offers a rich set of features,
which are not available in JPA. We will see more of them in future chapters. Finally,
we talked about proxy objects and batch processing.
This chapter is the heaviest in content. In the following chapters, we will focus on
specific topics and explore different ways to accomplish certain objectives using
Hibernate. The next chapter covers mapping concepts, and you will take a look at
how basic and advanced mapping can fill the gap between objects and relations.
[ 21 ]
www.PacktPub.com
Stay Connected: