Let's get started covering Hibernate 5 in the Spring environment, using it to demonstrate Spring's approach to integrating OR mappers. This section covers many issues in detail and shows various options for implementing a DAO and demarcating transactions. Most of these patterns can be directly transferred to all other supported ORM tools. Subsequent sections of this chapter describe other ORM technologies and provide brief examples.
HibernateJpaVendorAdapter
from Spring, as well as for the native setup SessionFactory
from
Hibernate. It is highly recommended to use Hibernate ORM 5.4 for applications that are just getting started. To use
with HibernateJpaVendorAdapter
, Hibernate Search needs to be updated to version 5.11.6.
Configuring SessionFactory
in a Spring container
To avoid To bind application objects to
hard-coded resource lookups, you can define resources (such as DataSource
from JDBC or SessionFactory
from Hibernate) as beans in a Spring container. Application objects that need access to resources will receive
references to such predefined instances through bean references, as shown in the DAO definition in next
section.
The following extract from the XML application context definition shows how to set DataSource
from JDBC and SessionFactory
from Hibernate on top of it:
<beans>
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
<bean id="mySessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="myDataSource"/>
<property name="mappingResources">
<list>
<value>product.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.HSQLDialect
</value>
</property>
</bean>
</beans>
Moving from local BasicDataSource
from Jakarta Commons DBCP to DataSource
located in
JNDI (usually managed by the application server), is just a matter of configuration, as shown in the following
example:
<beans>
<jee:jndi-lookup id="myDataSource" jndi-name="java:comp/env/jdbc/myds"/>
</beans>
You can also access the SessionFactory
located in JNDI using JndiObjectFactoryBean
/
<jee:jndi-lookup>
from Spring to receive and open it. However, this is generally not a typical
solution outside the context of an EJB.
Spring also provides a LocalSessionFactoryBuilder
option that
is easy to interoperate with configuration based on the @Bean
annotation and programmatic
installation (without the participation of FactoryBean
).
Both LocalSessionFactoryBean
and LocalSessionFactoryBuilder
support background
bootstrapping, where Hibernate initialization occurs in parallel with the application boot thread for a given
boot executor (for example, SimpleAsyncTaskExecutor
). For LocalSessionFactoryBean
this
is available through the bootstrapExecutor
property. The programmatic LocalSessionFactoryBuilder
has an overloaded buildSessionFactory
method that takes a bootstrap executor argument.
Starting with Spring Framework 5.1, this native Hibernate setup can also expose EntityManagerFactory
from JPA for typical JPA interaction along with native Hibernate access.
DAO implementation based on the simple Hibernate API
Hibernate has a
feature called contextual sessions, in which Hibernate itself manages one current Session
for each
transaction. This is roughly equivalent to synchronizing one Session
from Hibernate for each
transaction in Spring. The corresponding DAO implementation is similar to the following example, based on the
regular Hibernate API:
public class ProductDaoImpl implements ProductDao {
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public Collection loadProductsByCategory(String category) {
return this.sessionFactory.getCurrentSession()
.createQuery("from test.Product product where product.category=?")
.setParameter(0, category)
.list();
}
}
class ProductDaoImpl(private val sessionFactory: SessionFactory) : ProductDao {
fun loadProductsByCategory(category: String): Collection<*> {
return sessionFactory.currentSession
.createQuery("from test.Product product where product.category=?")
.setParameter(0, category)
.list()
}
}
This style is similar to the style in the Hibernate reference documentation and examples, except that the
SessionFactory
is stored in an instance variable. We strongly recommend using this instance-based setup
rather than the good old HibernateUtil
class from the CaveatEmptor Hibernate sample application. (In
general, you should not store resources in static variables unless absolutely necessary.)
The previous DAO
example follows the dependency injection pattern. It fits nicely into a Spring IoC container, just as it would if it
were written based on the HibernateTemplate
template from Spring. It is also possible to set up such a
DAO in regular Java (for example, in unit tests). To do this, create an instance of it and call setSessionFactory(..)
with the desired reference to the factory. As a Spring bean definition, the DAO object would look like this:
<beans>
<bean id="myProductDao" class="product.ProductDaoImpl">
<property name="sessionFactory" ref="mySessionFactory"/>
</bean>
</beans>
The main advantage of this style of DAO is that it depends only on the Hibernate API. There is no need to import any Spring class. This is attractive from a non-invasive perspective and may seem more natural to Hibernate developers.
However, the DAO throws a HibernateException
(which is unchecked, so it doesn't need
to be declared or caught), which means that callers can only deal with those exceptions that are considered mostly
critical if they want to avoid dependence on Hibernate's own exception hierarchy. It is impossible to determine
specific causes (such as optimistic lock failure) without linking the calling program to the implementation
strategy. This trade-off may be acceptable for applications that rely heavily on Hibernate, do not need special
exception handling, or both.
Fortunately, Spring's LocalSessionFactoryBean
supports the SessionFactory.getCurrentSession()
method from Hibernate for any Spring transactional strategy, returning the current transactional
Session
managed by Spring, even when using the HibernateTransactionManager
. The default
logic for this method is to return the current Session
associated with the current JTA transaction, if
any. This logic applies whether you use JtaTransactionManager
from Spring, container-managed
transactions (CMT transactions) from EJB, or the JTA interface.
In general, you can implement DAO objects based on the normal Hibernate API, but still be able to use them in Spring-managed transactions.
Declarative Transaction Distinction
We recommend using Spring's declarative transaction support, which will allow Replace explicit API calls to delimit transactions in Java code with an AOP transaction interceptor. You can configure this transaction interceptor in a Spring container using Java or XML annotations. This ability to use declarative transactions allows business services to free up repetitive transaction demarcation code and focus it on adding business logic that actually matters to the application.
You can mark the service layer with @Transactional
annotations and instruct the Spring container
to look up these annotations and provide transactional semantics for these annotated methods. The following example
shows how to do this:
public class ProductServiceImpl implements ProductService {
private ProductDao productDao;
public void setProductDao(ProductDao productDao) {
this.productDao = productDao;
}
@Transactional
public void increasePriceOfAllProductsInCategory(final String category) {
List productsToChange = this.productDao.loadProductsByCategory(category);
// ...
}
@Transactional(readOnly = true)
public List<Product> findAllProducts() {
return this.productDao.findAllProducts();
}
}
class ProductServiceImpl(private val productDao: ProductDao) : ProductService {
@Transactional
fun increasePriceOfAllProductsInCategory(category: String) {
val productsToChange = productDao.loadProductsByCategory(category)
// ...
}
@Transactional(readOnly = true)
fun findAllProducts() = productDao.findAllProducts()
}
In the container, you need to configure the PlatformTransactionManager
implementation (as a bean)
and the <tx:annotation-driven/>
entry, allowing processing of @Transactional
at
runtime. The following example shows how to do this:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- SessionFactory, DataSource, etc. omitted -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<tx:annotation-driven/>
<bean id="myProductService" class="product.SimpleProductService">
<property name="productDao" ref="myProductDao"/>
</bean>
</beans>
Delimiting software transactions
You can decouple transactions at a higher application level, on top
of data access services at a lower level levels that cover any number of operations. There are also no restrictions
on the implementation of the surrounding business service. It only needs PlatformTransactionManager
from Spring. Again, the latter can be obtained from anywhere, but it would be preferable to be in the form of a
reference to the bean via the setTransactionManager(..)
method. Additionally, productDAO
must be set by the setProductDao(..)
method. The following couple of snippets show the transaction
manager and business service definition in the context of a Spring application and an example implementation of a
business method:
<beans>
<bean id="myTxManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="mySessionFactory"/>
</bean>
<bean id="myProductService" class="product.ProductServiceImpl">
<property name="transactionManager" ref="myTxManager"/>
<property name="productDao" ref="myProductDao"/>
</bean>
</beans>
public class ProductServiceImpl implements ProductService {
private TransactionTemplate transactionTemplate;
private ProductDao productDao;
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
public void setProductDao(ProductDao productDao) {
this.productDao = productDao;
}
public void increasePriceOfAllProductsInCategory(final String category) {
this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
public void doInTransactionWithoutResult(TransactionStatus status) {
List productsToChange = this.productDao.loadProductsByCategory(category);
// raise prices...
}
});
}
}
class ProductServiceImpl(transactionManager: PlatformTransactionManager,
private val productDao: ProductDao) : ProductService {
private val transactionTemplate = TransactionTemplate(transactionManager)
fun increasePriceOfAllProductsInCategory(category: String) {
transactionTemplate.execute {
val productsToChange = productDao.loadProductsByCategory(category)
// raise prices...
}
}
}
TransactionInterceptor
from Spring allows any checked exception of the application to be thrown
along with the callback code, while TransactionTemplate
is limited to unchecked exceptions within the
callback. TransactionTemplate
triggers a rollback in the event of an unchecked application exception or
if the transaction is marked by the application to be rolled back (by setting TransactionStatus
). By
default, TransactionInterceptor
behaves the same, but allows you to configure a rollback policy on a
per-method basis.
Transaction Management Strategies
And TransactionTemplate
, and the
TransactionInterceptor
delegate the actual transaction processing to an instance of the PlatformTransactionManager
(which could be a HibernateTransactionManager
(for one SessionFactory
from Hibernate),
using ThreadLocal Session
which is called "behind the scenes") or JtaTransactionManager
(delegating to the container's JTA subsystem) for Hibernate applications. You can even use your own PlatformTransactionManager
implementation. Moving from native Hibernate transaction management to JTA (for example, when you have distributed
transaction requirements for certain deployments of your application) is just a matter of configuration. You can
replace Hibernate's transaction manager with Spring's JTA transaction implementation. Both the transaction
demarcation code and the data access code work without modification because they use typed transaction management
APIs.
In the case of Hibernate sessions distributed across multiple factories, transactions can be combined
with JtaTransactionManager
as a transactional strategy with multiple
LocalSessionFactoryBean
definitions. Each DAO will then receive one specific reference to SessionFactory
passed to the corresponding bean property. If all underlying JDBC data sources are transactional and containerized,
a business service will be able to decouple transactions across any number of DAOs and any number of session
factories without much issue if JtaTransactionManager
is used as a strategy.
HibernateTransactionManager
and JtaTransactionManager
allow you to handle cache gracefully at the JVM level using Hibernate without
having to look for a transaction manager in the container or a JCA connector (unless you are using EJB to initiate
transactions).
HibernateTransactionManager
can export Connection
from JDBC-based
Hibernate to regular JDBC access code for a specific DataSource
. This capability allows high-level
decoupling of mixed Hibernate and JDBC data access transactions entirely without JTA, as long as you only access one
database. The HibernateTransactionManager
automatically opens a Hibernate transaction as a JDBC
transaction if you have configured the passed SessionFactory
with a DataSource
via the
dataSource
property of the LocalSessionFactoryBean
class. Alternatively, you can
explicitly set the DataSource
for which transactions should be opened via the dataSource
property of the HibernateTransactionManager class.
Comparing container-managed resources and locally defined resources
You can switch between a container-managed SessionFactory
from JNDI and a locally
defined one without changing a single line of application code. The question of whether to store resource
definitions in a container or locally in an application largely depends on the transaction strategy used. Compared
to a local SessionFactory
defined by Spring, manually registering a SessionFactory
from
JNDI does not provide any benefit. Deploying SessionFactory
through the Hibernate JCA connector
provides the added benefit of being present in the Java EE server management infrastructure, but has no actual value
beyond that.
Spring's transaction support is not container-specific . When configured using any strategy
other than JTA, transaction support also works in a standalone or test environment. Spring's support for local
single-resource transactions is a lightweight and full-featured alternative to JTA, especially in the typical case
of single-database transactions. If you use local stateless EJB session beans to manage transactions, there will be
a dependency on both the EJB container and the JTA, even if you access only one database and use only those
non-stateful session beans to provide declarative transactions via container-managed transactions. Direct use of JTA
programmatically also requires a Java EE environment. JTA includes more than just container dependencies in terms of
JTA itself and DataSource
instances from JNDI. For non-Spring Hibernate transactions managed by JTA,
you must use the JCA connector from Hibernate or additional transactional code from Hibernate with TransactionManagerLookup
configured for proper caching at the JVM level.
Spring-managed transactions can work just as well with a
locally defined SessionFactory
from Hibernate as they can with a local DataSource
from
JDBC, provided they access the same database. Thus, Spring's JTA transaction strategy should only be used when there
are distributed transaction requirements. The JCA connector requires container-specific deployment steps and
(obviously) JCA support tools first. This configuration will require more work than if you were deploying a simple
web application with local resource definitions and Spring-managed transactions. In addition, an Enterprise Edition
of the container is often required if, for example, WebLogic Express is used, which does not provide JCA. A Spring
application with local resources and transactions spanning a single database runs in any Java EE web container (no
JTA, JCA or EJB) such as Tomcat, Resin or even plain Jetty. You can also easily reuse such an intermediate in
desktop applications or test suites.
In general, if you are not using EJB beans, stick to setting up locally
SessionFactory
and HibernateTransactionManager
or JtaTransactionManager
from Spring. You'll
enjoy all the benefits, including proper JVM-level transaction caching and distributed transactions, without all the
inconvenience of deploying containers. JNDI registration of SessionFactory
from Hibernate via the JCA
connector is only beneficial when used in conjunction with EJB beans.
False application server warnings when using Hibernate
In
some JTA environments with a very strict XADataSource
implementation (currently some versions of
WebLogic Server and WebSphere), if Hibernate is configured without a dispatcher JTA transactions for this
environment, false warnings or exceptions may appear in the application server log. These warnings or exceptions
indicate that the connection being accessed is no longer valid or the JDBC access is no longer valid, perhaps
because a transaction is no longer active. As an example, here is a real exception from WebLogic:
java.sql.SQLException : The transaction is no longer active - status: 'Committed'. No further JDBC access is allowed within this transaction.
Another common problem is connection leakage after JTA transactions if Hibernate sessions (and possibly underlying JDBC connections) are not properly closed This way.
These problems can be solved by making Hibernate compatible with the JTA transaction manager it synchronizes with (along with Spring). There are two options on how to do this:
Pass your
JtaTransactionManager
bean from Spring to the Hibernate configuration. The easiest way is to provide a bean reference in thejtaTransactionManager
property for yourLocalSessionFactoryBean
bean (see "Configuring Hibernate transactions"). Spring will then make the corresponding JTA strategies available to Hibernate.You can also explicitly configure JTA-related Hibernate properties, specifically "hibernate.transaction.coordinator_class", "hibernate.connection" .handling_mode" and possibly "hibernate.transaction.jta.platform" in "hibernateProperties", for the
LocalSessionFactoryBean
(see the Hibernate manual for details on these properties).
The remainder of this section describes the sequence of events that occur with and without the fact that
Hibernate is compatible with the PlatformTransactionManager
from JTA.
If Hibernate was configured without any regard to the JTA transaction manager, when a JTA transaction commits, the following events occur:
The JTA transaction is committed.
JtaTransactionManager
from Spring is synchronized with the JTA transaction, so it is called back via theafterCompletion
callback by the JTA transaction manager.Among other actions , this synchronization causes a callback from Spring to Hibernate via Hibernate's
afterTransactionCompletion
callback (used to clear the Hibernate cache), followed by an explicitclose()
call to Hibernate session, which will cause Hibernate to attempt aclose()
JDBC connection.In some environments this call to
Connection.close()
then causes a warning or error to be raised because the application server no longer considersConnection
usable because the transaction has already been committed.
If Hibernate is configured with a JTA transaction manager in mind, the following events occur when a JTA transaction commits:
The JTA transaction is ready to commit.
JtaTransactionManager
from Spring is synchronized with the JTA transaction, so the transaction is called back by the JTA transaction manager'sbeforeCompletion
callback.Spring takes into account that Hibernate itself synchronizes with the JTA transaction and begins to behave differently than in the previous scenario. In particular, it aligns with Hibernate's transactional resource management.
The JTA transaction is committed.
Hibernate is synchronized with the JTA transaction so that the transaction is called back by the
afterCompletion
callback by the JTA transaction manager and can properly clear its cache .
GO TO FULL VERSION