In this article, we describe our modularization and transaction management approach with Spring and Hibernate. Similar to the OSGi JPA Service Specification and JDBC, it allows each persistent module of an enterprise application to have its own individually configured, module-specific SessionFactory.
Consequently, both entity classes and the associated SessionFactory configuration of a module are isolated from other modules. On one hand, modularization enforces isolation between modules, but on the other, it increases the need for coordination between them from within the scope of a transaction.
New Spring inversion of control (IoC) tutorials and Java dependency injection examples
TheServerSide has been updating its resources in terms of the Spring framework, Java EE contexts and various different approaches to inversion of control (IoC) and dependency injection (DI). Check out these newer resources on the topic:
- Hibernate vs JPA: What is the difference between JPA and Hibernate
- Hibernate vs JDBC: What is the difference between JDBC and Hibernate
- JDBC vs ODBC: What is the difference between ODBC and JDBC
- Five JDBC Facts
- Inversion of control explained fast with a Spring IoC example
- Spring MVC Java web development with Spring Boot
- Drawbacks to IoC and how to solve inversion of control problems
- Spring IoC vs. Google Guice's inversion of control approach
- Getting started with Google Guice
- Getting started with the Spring IoC container
- The beauty of Spring without XML: Annotation based IoC
To avoid the cost and complexity of distributed transaction processing with an XA protocol, we extended Spring’s transaction management with two concepts: SessionFactory swapping and transaction synchronization. A single, non-JTA transaction manager traverses the session factories that are involved in a logical transaction which crosses module boundaries. With the concept of transaction synchronization, we implemented the “Shared Transaction Resource” pattern to guarantee the atomicity property of a logical transaction by using a shared database connection.
After modularizing the global SessionFactory, we measured a higher throughput of transactions (3-5%) despite the overhead caused by SessionFactory swapping and transaction synchronization. Furthermore, the module internal session factories can now be configured individually and optimized for module-specific needs, which was not feasible in the past. We also briefly explain the modularization approach proposed by OSGi JPA Service Specification and OSGi JTA Transaction Service Specification and point out what it has in common with our approach.
The intent of this article is to provide insights into the approach we have chosen in order to receive feedback and comments. We believe in the strength of the approach with regards to domain-driven concepts and would like to initiate a discussion on this topic.
Modularization and Transaction Management
Modularization is a widely adopted concept for building large-scale applications. Typically, such an application is decomposed into a number of modules or bundles (the term module and bundle are used interchangeably here), with each of these modules being responsible for a certain domain-specific functionality. This type of modularization is also referred to as vertical modularization, in contrast to (rather coarse-grained) horizontal modularization, where the application is split by its technical layers (e.g. the persistence layer).
Transactions are a key component of enterprise applications. When multiple modules are involved in a transaction, the outcome in each module must be coordinated such that the combined outcome fulfills the atomicity property of a transaction. A transaction either succeeds when actions in all involved modules succeed, or rolls back when one of the modules fail. We define a cross-module transaction as a logical transaction which spans multiple persistent modules.
Modularization and transaction management are tightly related. On one hand, modularization enforces isolation between modules, but on the other hand, it increases the cost and complexity of coordination between them in the scope of a transaction. In this article, we describe our modularization and transaction management approach with Spring and Hibernate. We briefly describe the approach proposed by the OSGi Service Platform Enterprise Specification  and point out where the two approaches are similar.
Modularization with Spring and Hibernate
Despite the increasing support for enterprise applications, migrating an existing enterprise application to Spring DM or OSGi can still be a very challenging task due to numerous, unforeseeable issues troubleshooting class loading problems such as ClassNotFoundExceptions or LinkageErrors . Hence, we decided to stick with plain Spring and Hibernate and enable a long-term migration path to an OSGi-based deployment.
Figure 1 A vertical modularization using module-specific session factories
To enforce isolation among modules, both the entity classes and the associated persistence configuration of each module stay inside its module boundary. This is also in line with domain-driven design concepts . Since session factories serve as persistence configurations in Hibernate, we remove the need for a shared, global SessionFactory by allowing each persistent module to have its own individually configured, module-specific SessionFactory (see Figure 1, SFA stands for the SessionFactory of Module A).
Using multiple session factories in a logical transaction imposes challenging issues concerning transaction management. In Spring, a transaction manager is configured with exactly one SessionFactory or one data source when the application starts up. Normally, this binding is static, and does not change afterwards. We had the following alternatives to handle a cross-module transaction:
- Use multiple transaction managers, and bind each of them to exactly one SessionFactory.
- Use a JTA transaction manager which supports distributed transactions with an XA protocol.
- Lose the static binding to one SessionFactory, and allow the transaction manager to bind to different session factories in the scope of a logical transaction.
We quickly abandoned the first two alternatives due to the potential cost and complexity of an XA protocol with two-phase commit. Since most of the modules of an application share the same data source, the imposed cost and complexity are definitively unnecessary.
Let’s look at the third option with a single, non-JTA transaction manager. Without being statically bound to a single SessionFactory from the application startup time, the transaction manager traverses the session factories involved in a cross-module transaction. However, by assuming that there is only one SessionFactory shared by all modules of an application, Spring creates a new Spring transaction for each SessionFactory involved in a cross-module transaction.
Hence, a logical cross-module transaction is mapped to multiple Spring transactions. The coordination among these Spring transactions remains with this option. There exist different patterns to realize distributed transactions with and without XA . For simplicity and higher throughput we chose the “Shared Transaction Resource” pattern which removes the need for XA by ensuring that all the resources are synchronized or backed by the same resource, which is in our case here the connection rather than the Hibernate session.
We extended Spring’s transaction management with two main concepts: SessionFactory swapping and transaction synchronization on connection-level. The concept of SessionFactory swapping allows a transaction manager to traverse the session factories involved in a cross-module transaction. The concept of transaction synchronization implements the Shared Transaction Resource pattern.
The SessionFactory is "swapped" at module boundaries. Figure 2 illustrates the concept of SessionFactory swapping with a transaction involving Module A, B and C. Module A starts a transaction and invokes a service provided by Module B. Module B subsequently invokes a service provided by Module C. Each module has a module-specific SessionFactory, e.g., the SessionFactory SFA for Module A, and SFB for Module B respectively.
Figure 2 Traversing session factories and synchronizing transactions with a shared database connection
Upon entering a module, the SessionFactory which is currently bound to the transaction manager is set aside, and the SessionFactory of the module to be entered gets bound to the transaction manager (see red arrows on their way entering into modules in Figure 2). When leaving the module, the transaction manager is restored to the previous SessionFactory (see green arrows on their way back leaving the modules).
This raises the question of how to know when a module boundary is crossed. Following the service-orientation in OSGi and Spring DM, each module declares a set of services (i.e. Spring beans) that it exports and imports. Invocations on an exported service are intercepted by a proxy object. Thus, the proxy is aware of the boundary crossing, and implements the code for SessionFactory swapping both before and after invoking the actual service-providing bean.
Since a logical cross-module transaction is mapped to multiple Spring transactions, we have to keep track of Spring transactions which belong to a single logical transaction in order to coordinate among them. As shown in Figure 3, we use a thread-bound propagation stack to track ongoing Spring transactions, and their parent-child relationship according to their transaction propagation behavior. In Figure 3, the brackets represent transaction boundaries. txA stands for transaction A, and is associated with SessionFactory SFA.
Figure 3 A propagation stack for keeping track of Spring transactions belonging to a logical transaction
By keeping track of transactions which belong to the same logical transaction, we are able to recognize transactional resources involved in a logical transaction in order to synchronize them. If the transactional resources share the same data source, they are synchronized by the same database connection (see Figure 1).
More concretely, all child transactions (so from Figure 2, transaction txB and txC, which are associated with SessionFactory SFB and SFC respectively) reuse the database connection of its parent transaction (in this case transaction txA). Technically, Spring opens a new session for transaction txB. Our extension re-connects this new session to the database connection opened by the parent transaction txA. Apart from using the same database connection, the completion of all child transactions is delayed until the completion of the parent transaction. As part of a commit operation, a flush operation must be performed for all involved sessions.
By default, we assume that modules of an application share the same data source. There are valid domain-specific scenarios which require a module to use a separate, dedicated data source. For instance, a module for encryption or security auditing may need to store sensitive encryption parameters or audit data in a separate database. We have implemented our Spring extensions in a proprietary framework, and introduced a customized transactional namespace to keep the complexity out of the application code. Using this customized transactional namespace, a module explicitly specifies that it does not use the shared database of the application.
Advantages of Modularized Session Factories
Extending Spring’s transaction management with SessionFactory swapping and the Shared Transaction Resource pattern was a very challenging task. Our extension consists of support for both declarative and programmatic transaction management, and for transactional tests. After modularizing the global SessionFactory, we measured a higher throughput of transactions (3-5%) in spite of the overhead caused by SessionFactory swapping and synchronization of transactions.
With module-specific session factories, the module boundaries become much more explicit. Listeners and interceptors can be registered to the SessionFactory of a particular module of interest. They receive far less invocations from the module-specific SessionFactory than from the global SessionFactory. This variance explains the increased throughput of transactions after introducing module-specific session factories.
Furthermore, the module internal session factories can now be configured individually to meet the needs of their module. This for example allows for the use of a dedicated cache, or specific replication configuration along with other options, all of which was not feasible in the past.
Modularization with OSGi
Since Peter Kriens’s blog  about class loading and global SessionFactory configuration issues when using Hibernate in an OSGi Service Platform, various solutions have been proposed. Most of them rely on Require-Bundle, Dynamic-Import or Eclipse-Buddy policy, but do not solve the problem from the ground up. The recent OSGi JPA Service Specification  addresses the issues with a new approach.
According to the specification, a persistence bundle contains the entity classes and accompanying persistence descriptions (which are comparable to Hibernate SessionFactory configurations). For each persistence unit in a persistence descriptor, the JPA provider registers an Entity Manager Factory (Builder) service, which can be retrieved by a client bundle to manipulate the entity classes. The OSGi JPA approach is comparable to our approach with Spring and Hibernate.
Once multiple entity managers from different bundles are involved, multiple database sessions or connections will be opened for a cross-bundle transaction. For the atomicity property of a transaction, it is of paramount importance to know which transactional resources belong to a logical transaction. The OSGi JTA Transaction Service Specification  introduces the enlistment strategy. A transactional resource enlists/associates itself with an ongoing transaction to participate in the resource commit coordination.
Our approach utilizes information such as transaction propagation behavior and underlying data source to keep track of transactional resources which belong to a logical transaction. The OSGi specification does not go into details about how XA resources are coordinated, or how the coordination can be optimized if a shared data source is used. They remain the responsibility of a concrete JTA provider or the underlying resource manager (e.g. the database). A more in-depth comparison between our approach and the OSGi strategy is not possible in the scope of this paper.
We would like to thank Rostislav Georgiev and Thomas Bastian for their insightful thoughts and intense discussions on this topic. Their in-depth knowledge about Spring and transaction management gave us a leap forward regarding both the conceptual work and implementation.
About the authors
Dr. Yun Ding and Karsten Klein are Software Architects at InterComponentWare (ICW) AG, an international company specializing in technology solutions for the healthcare market based in Walldorf, Germany. All ICW solutions are built on the ICW eHealth Platform, a suite of powerful technology components that support the quick assembly of eHealth solutions and the flexible implementation of integration scenarios. Both authors regularly contribute to conferences on enterprise application and to industry as well as research publications.
Here are some other helpful tips, tricks, tutorials and examples by Cameron McKenzie (@cameronmcnz):