The core operations of Hibernate occur in the session. This is where connection to the database is obtained, Structured Query Language (SQL) statements are composed, type conversions are made, and where transactions and the persistence context are managed. Let's start by getting to know the internals of a Hibernate session.
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.
Furthermore, the session is designed to be a short-lived object. This is an important constraint that is typically imposed by the database and the application server, and this is because there is always a timeout setting on connections and transactions. (There is even timeout setting at the Java Database Connectivity (JDBC) level. Furthermore, you have to worry about TCP Socket timeout.) Even though these settings are set to some number of seconds, you should still avoid long-running logics while the session is open because you may create contention in the database and impact the system performance altogether. Hibernate tries to protect you by not allocating any resources, such as database connections, unless they are absolutely needed. However, you still have to be mindful of the work that you do within the unit of persistence work. As long as you limit the code to persistence-related tasks while you have an open session, you will be fine.
When you create a session factory, Hibernate reads the configuration file and, at first, loads the SQL
dialect class. This class includes a mapping for the database types and operations for the specific Relational Database Management System (RDBMS) server, which you work with. Hibernate then adds all these types, as well as the user-defined types to a type resolver. (You will learn about creating custom types in Chapter 2, Advanced Mapping.)
Additionally, when a session factory is created, Hibernate loads all the entities that are added to the configuration. You can add annotated entities to the configuration in the code, or create an entity mapping file and add the entity map to the configuration file. For each entity, Hibernate precomposes the JDBC-prepared SQL statements for
delete. (This also creates one to support entity versioning, refer to the Envers section, in Chapter 6, Events, Interceptors, and Envers). This is called a static SQL. Some entity types require a dynamic SQL, for example, dynamic entities.
Hibernate also creates internal maps for each entity property and its association with other entities and collections. This is because Hibernate uses reflection to access properties or to invoke methods on your entity. In addition to the entity properties, Hibernate also keeps track of all the annotations that are associated with properties and methods so that it can perform certain operations when needed, such as cascade operations.
When you obtain a session object from the factory, you may get a different implementation of the
CurrentSessionContext interface, depending on the session context class. There are three session contexts that are natively supported: Java Transaction API (JTA), thread, and managed contexts. This is set using the
current_session_context_class configuration parameter, and Hibernate has reserved the shortcuts,
thread, to expand to the corresponding internal classes. However, you can replace this with any class, which implements
Starting with version 4, Hibernate has repackaged the classes to follow the OSGi model for more modular, pluggable, and extensible applications and frameworks. Many of the named resources, such as dialect and connection provider, are now managed through services. Services have a lifecycle (initialize, start, and stop), a rich callback API, and provide support for JMX and CDI, among others. There are three main packages in Hibernate, the API packages, the SPI packages, and the internal packages. The classes in the API packages are the ones that we utilize to use Hibernate. The classes in the Service Provider Interface (SPI) are pluggable modules that are typically replaced or provided by vendors who want to implement or override certain components of Hibernate. Finally, the classes in the internal packages are used internally by Hibernate. We will come back to this in Chapter 6, Events, Interceptors, and Envers, when we discuss events.
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,
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.
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
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.)
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:
All of these implement the
CurrentSessionContext interface. Simply put, the context defines the scope of the current session.
The scope of JTA session context is defined by the transaction that is being managed by JTA. In this case, the current session is bound to the JTA transaction and, therefore, the cleanup is triggered by JTA when lifecycle events are fired off. Once the transaction is committed, the session is flushed, cleared, and then closed. If your application runs in an environment where a transaction manager is deployed, you should always use this context.
The scope of thread local session context is defined by the current thread. This context is best suitable for unit tests or standalone applications, as it is not meant for usage in an enterprise application. In this case, the current session is bound to the current thread and the transaction that you start comes straight from JDBC. If you use the Hibernate transaction API (that is
Transaction transaction = session.beginTransaction();), and you should, it will perform the cleanup for you.
The scope of the managed session context is somewhat defined by the current thread, but the scope can expand over multiple threads. In this case, the session outlives the thread that created it and may be flushed and closed by a subsequent thread. In other words, you are defining the scope of the session, and you have to manually handle cleanup work. You are managing the session.
What does flush do?
When you modify the persistence context by adding new entities, or updating the existing one, the database and persistence context are not synchronized until the end of persistence work. Only after
flush() is called, changes to Hibernate entities are propagated to the corresponding tables and rows. Hibernate offers very powerful capabilities to manage flush behavior when this synchronization actually occurs. You will see more on this later.
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.
managed: You'll have to use
ManagedSessionContextto obtain the correct 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.
In this design pattern, all the persistence work for each client request is accomplished within one session. If all the business transactions within a unit of work can be encapsulated in one Data Access Object (DAO) implementation, then you can start a session and a transaction at the beginning of your method, and commit at the end. (Don't forget proper exception handling!)
If your business unit of work spans multiple DAO classes, then you have to make sure that they all use the same session. Hibernate makes this easy for you by providing the
sessionFactory.getCurrentSession() API. This will allow you to access the session object from anywhere within the same thread.
However, here's the catch. You need to make sure that somebody has started the transaction whose commit and rollback is also delegated appropriately. This can be orchestrated in a service method, where you can start a Hibernate session, begin transaction, and store the session object in a static
ThreadLocal session, or pass the session object to each DAO instance, either as constructor argument or passed directly to each method. Once the orchestration is completed, you can commit the transaction.
If you use EJBs, you are in luck! You simply wire
EntityManager and declare a DAO method transactional using the
@TransactionAttribute annotation, and the EJB container will take care of the rest for you. We will demonstrate this, and another elegant solution using Spring, in Chapter 9, EJB and Spring Context.
This pattern is used when the business transaction spans over multiple units of persistence work, and the business data is exchanged over multiple consecutive requests with allowed think time in between.
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 long-running 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.
This is considered an anti-pattern. It's true that the instantiation of the session object is not expensive, but managing the persistence context and allocating or obtaining connection and transaction resources is expensive. If your business transaction is comprised of multiple persistence operations, you need to ensure that they all happen within the scope of one session. Try not to call multiple DAO methods when each of them creates their own session and start and commit their own transactions. If you are being forced to do this, perhaps it's time to refactor.
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.
No delayed DML (also known as write-behind). Every save or update will be executed right away. (Refer to the earlier discussion on action queues.)
No cascade operation. You have to handle the associated entities.
No proxy object; hence, no lazy fetching.
No event notification or interceptors.
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.