We will start with a coverage of Hibernate 3 in a Spring environment, using it to demonstrate the approach that Spring takes towards integrating O/R mappers. This section will cover many issues in detail and show different variations of DAO implementations and transaction demarcation. Most of these patterns can be directly translated to all other supported ORM tools. The following sections in this chapter will then cover the other ORM technologies, showing briefer examples there.
Note | |
---|---|
As of Spring 2.5, Spring requires Hibernate 3.1 or later. Neither Hibernate 2.1 nor Hibernate 3.0 are supported. |
To avoid tying application objects to hard-coded resource lookups,
you can define resources such as a JDBC
DataSource
or a Hibernate
SessionFactory
as beans in the Spring
container. Application objects that need to access resources receive
references to such predefined instances through bean references, as
illustrated in the DAO definition in the next section.
The following excerpt from an XML application context definition
shows how to set up a JDBC DataSource
and a
Hibernate SessionFactory
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.hibernate3.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>
Switching from a local Jakarta Commons DBCP
BasicDataSource
to a JNDI-located
DataSource
(usually managed by an
application server) is just a matter of configuration:
<beans> <jee:jndi-lookup id="myDataSource" jndi-name="java:comp/env/jdbc/myds"/> </beans>
You can also access a JNDI-located
SessionFactory
, using Spring's
JndiObjectFactoryBean
/
<jee:jndi-lookup>
to retrieve and expose it.
However, that is typically not common outside of an EJB context.
Hibernate 3 has a feature called contextual sessions, wherein
Hibernate itself manages one current
Session
per transaction. This is roughly
equivalent to Spring's synchronization of one Hibernate
Session
per transaction. A corresponding
DAO implementation resembles the following example, based on the plain
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(); } }
This style is similar to that of the Hibernate reference
documentation and examples, except for holding the
SessionFactory
in an instance variable.
We strongly recommend such an instance-based setup over the old-school
static
HibernateUtil
class
from Hibernate's CaveatEmptor sample application. (In general, do not
keep any resources in static
variables unless
absolutely necessary.)
The above DAO follows the dependency injection pattern: it fits
nicely into a Spring IoC container, just as it would if coded against
Spring's HibernateTemplate
. Of course, such a DAO
can also be set up in plain Java (for example, in unit tests). Simply
instantiate it and call setSessionFactory(..)
with the desired factory reference. As a Spring bean definition, the DAO
would resemble the following:
<beans> <bean id="myProductDao" class="product.ProductDaoImpl"> <property name="sessionFactory" ref="mySessionFactory"/> </bean> </beans>
The main advantage of this DAO style is that it depends on Hibernate API only; no import of any Spring class is required. This is of course appealing from a non-invasiveness perspective, and will no doubt feel more natural to Hibernate developers.
However, the DAO throws plain
HibernateException
(which is unchecked, so does
not have to be declared or caught), which means that callers can only
treat exceptions as generally fatal - unless they want to depend on
Hibernate's own exception hierarchy. Catching specific causes such as an
optimistic locking failure is not possible without tying the caller to
the implementation strategy. This trade off might be acceptable to
applications that are strongly Hibernate-based and/or do not need any
special exception treatment.
Fortunately, Spring's
LocalSessionFactoryBean
supports Hibernate's
SessionFactory.getCurrentSession()
method for
any Spring transaction strategy, returning the current Spring-managed
transactional Session
even with
HibernateTransactionManager
. Of course, the
standard behavior of that method remains the return of the current
Session
associated with the ongoing JTA
transaction, if any. This behavior applies regardless of whether you are
using Spring's JtaTransactionManager
, EJB
container managed transactions (CMTs), or JTA.
In summary: you can implement DAOs based on the plain Hibernate 3 API, while still being able to participate in Spring-managed transactions.
We recommend that you use Spring's declarative transaction support, which enables you to replace explicit transaction demarcation API calls in your Java code with an AOP transaction interceptor. This transaction interceptor can be configured in a Spring container using either Java annotations or XML.This declarative transaction capability allows you to keep business services free of repetitive transaction demarcation code and to focus on adding business logic, which is the real value of your application.
Note | |
---|---|
Prior to continuing, you are strongly encouraged to read Section 10.5, “Declarative transaction management” if you have not done so. |
Furthermore, transaction semantics like propagation behavior and isolation level can be changed in a configuration file and do not affect the business service implementations.
The following example shows how you can configure an AOP transaction interceptor, using XML, for a simple service class:
<?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 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- SessionFactory, DataSource, etc. omitted --> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory"/> </bean> <aop:config> <aop:pointcut id="productServiceMethods" expression="execution(* product.ProductService.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="productServiceMethods"/> </aop:config> <tx:advice id="txAdvice" transaction-manager="myTxManager"> <tx:attributes> <tx:method name="increasePrice*" propagation="REQUIRED"/> <tx:method name="someOtherBusinessMethod" propagation="REQUIRES_NEW"/> <tx:method name="*" propagation="SUPPORTS" read-only="true"/> </tx:attributes> </tx:advice> <bean id="myProductService" class="product.SimpleProductService"> <property name="productDao" ref="myProductDao"/> </bean> </beans>
This is the service class that is advised:
public class ProductServiceImpl implements ProductService { private ProductDao productDao; public void setProductDao(ProductDao productDao) { this.productDao = productDao; } // notice the absence of transaction demarcation code in this method // Spring's declarative transaction infrastructure will be demarcating transactions on your behalf public void increasePriceOfAllProductsInCategory(final String category) { List productsToChange = this.productDao.loadProductsByCategory(category); // ... } }
We also show an attribute-support based configuration, in the following example. You annotate the service layer with @Transactional annotations and instruct the Spring container to find these annotations and provide transactional semantics for these annotated methods.
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(); } }
As you can see from the following configuration example, the configuration is much simplified, compared to the XML example above, while still providing the same functionality driven by the annotations in the service layer code. All you need to provide is the TransactionManager implementation and a "<tx:annotation-driven/>" entry.
<?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 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- SessionFactory, DataSource, etc. omitted --> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory"/> </bean> <tx:annotation-driven/> <bean id="myProductService" class="product.SimpleProductService"> <property name="productDao" ref="myProductDao"/> </bean> </beans>
You can demarcate transactions in a higher level of the
application, on top of such lower-level data access services spanning
any number of operations. Nor do restrictions exist on the
implementation of the surrounding business service; it just needs a
Spring PlatformTransactionManager
. Again, the
latter can come from anywhere, but preferably as a bean reference
through a setTransactionManager(..)
method,
just as the productDAO
should be set by a
setProductDao(..)
method. The following
snippets show a transaction manager and a business service definition in
a Spring application context, and an example for a business method
implementation:
<beans> <bean id="myTxManager" class="org.springframework.orm.hibernate3.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); // do the price increase... } } ); } }
Spring's TransactionInterceptor
allows any
checked application exception to be thrown with the callback code, while
TransactionTemplate
is restricted to unchecked
exceptions within the callback.
TransactionTemplate
triggers a rollback in case
of an unchecked application exception, or if the transaction is marked
rollback-only by the application (via
TransactionStatus
).
TransactionInterceptor
behaves the same way by
default but allows configurable rollback policies per method.
Both TransactionTemplate
and
TransactionInterceptor
delegate the actual
transaction handling to a
PlatformTransactionManager
instance, which can be
a HibernateTransactionManager
(for a single
Hibernate SessionFactory
, using a
ThreadLocal
Session
under the hood) or a
JtaTransactionManager
(delegating to the JTA
subsystem of the container) for Hibernate applications. You can even use
a custom PlatformTransactionManager
implementation. Switching from native Hibernate transaction management
to JTA, such as when facing distributed transaction requirements for
certain deployments of your application, is just a matter of
configuration. Simply replace the Hibernate transaction manager with
Spring's JTA transaction implementation. Both transaction demarcation
and data access code will work without changes, because they just use
the generic transaction management APIs.
For distributed transactions across multiple Hibernate session
factories, simply combine JtaTransactionManager
as a transaction strategy with multiple
LocalSessionFactoryBean
definitions. Each DAO
then gets one specific SessionFactory
reference passed into its corresponding bean property. If all underlying
JDBC data sources are transactional container ones, a business service
can demarcate transactions across any number of DAOs and any number of
session factories without special regard, as long as it is using
JtaTransactionManager
as the strategy.
<beans> <jee:jndi-lookup id="dataSource1" jndi-name="java:comp/env/jdbc/myds1"/> <jee:jndi-lookup id="dataSource2" jndi-name="java:comp/env/jdbc/myds2"/> <bean id="mySessionFactory1" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource" ref="myDataSource1"/> <property name="mappingResources"> <list> <value>product.hbm.xml</value> </list> </property> <property name="hibernateProperties"> <value> hibernate.dialect=org.hibernate.dialect.MySQLDialect hibernate.show_sql=true </value> </property> </bean> <bean id="mySessionFactory2" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource" ref="myDataSource2"/> <property name="mappingResources"> <list> <value>inventory.hbm.xml</value> </list> </property> <property name="hibernateProperties"> <value> hibernate.dialect=org.hibernate.dialect.OracleDialect </value> </property> </bean> <bean id="myTxManager" class="org.springframework.transaction.jta.JtaTransactionManager"/> <bean id="myProductDao" class="product.ProductDaoImpl"> <property name="sessionFactory" ref="mySessionFactory1"/> </bean> <bean id="myInventoryDao" class="product.InventoryDaoImpl"> <property name="sessionFactory" ref="mySessionFactory2"/> </bean> <bean id"myProductService" class="product.ProductServiceImpl"> <property name="productDao" ref="myProductDao"/> <property name="inventoryDao" ref="myInventoryDao"/> </bean> <aop:config> <aop:pointcut id="productServiceMethods" expression="execution(* product.ProductService.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="productServiceMethods"/> </aop:config> <tx:advice id="txAdvice" transaction-manager="myTxManager"> <tx:attributes> <tx:method name="increasePrice*" propagation="REQUIRED"/> <tx:method name="someOtherBusinessMethod" propagation="REQUIRES_NEW"/> <tx:method name="*" propagation="SUPPORTS" read-only="true"/> </tx:attributes> </tx:advice> </beans>
Both HibernateTransactionManager
and
JtaTransactionManager
allow for proper JVM-level
cache handling with Hibernate, without container-specific transaction
manager lookup or a JCA connector (if you are not using EJB to initiate
transactions).
HibernateTransactionManager
can export the
Hibernate JDBC Connection
to plain JDBC
access code, for a specific DataSource
.
This capability allows for high-level transaction demarcation with mixed
Hibernate and JDBC data access completely without JTA, if you are
accessing only one database.
HibernateTransactionManager
automatically exposes
the Hibernate transaction as a JDBC transaction if you have set up the
passed-in SessionFactory
with a
DataSource
through the
dataSource
property of the
LocalSessionFactoryBean
class. Alternatively, you
can specify explicitly the DataSource
for
which the transactions are supposed to be exposed through the
dataSource
property of the
HibernateTransactionManager
class.
You can switch between a container-managed JNDI
SessionFactory
and a
locally defined one, without having to change a single line of
application code. Whether to keep resource definitions in the container
or locally within the application is mainly a matter of the transaction
strategy that you use. Compared to a Spring-defined local
SessionFactory
, a manually registered
JNDI SessionFactory
does not provide any
benefits. Deploying a SessionFactory
through Hibernate's JCA connector provides the added value of
participating in the Java EE server's management infrastructure, but
does not add actual value beyond that.
Spring's transaction support is not bound to a container.
Configured with any strategy other than JTA, transaction support also
works in a stand-alone or test environment. Especially in the typical
case of single-database transactions, Spring's single-resource local
transaction support is
a lightweight and powerful alternative to JTA. When you use local EJB
stateless session beans to drive transactions, you depend both on an EJB
container and JTA, even if you access only a single database, and only
use stateless session beans to provide declarative transactions through
container-managed transactions. Also,
direct use of JTA programmatically requires a Java EE environment as
well. JTA does not involve only container dependencies in terms of JTA
itself and of JNDI DataSource
instances.
For non-Spring, JTA-driven Hibernate transactions, you have to use the
Hibernate JCA connector, or extra Hibernate transaction code with the
TransactionManagerLookup
configured for
proper JVM-level caching.
Spring-driven transactions can work as well with a locally defined
Hibernate SessionFactory
as they do with
a local JDBC DataSource
if they are
accessing a single database. Thus you only have to use Spring's JTA
transaction strategy when you have distributed transaction requirements.
A JCA connector requires container-specific deployment steps, and
obviously JCA support in the first place. This configuration requires
more work than deploying a simple web application with local resource
definitions and Spring-driven transactions. Also, you often need the
Enterprise Edition of your container if you are using, for example,
WebLogic Express, which does not provide JCA. A Spring application with
local resources and transactions spanning one single database works in
any Java EE web container (without JTA, JCA, or EJB) such as Tomcat,
Resin, or even plain Jetty. Additionally, you can easily reuse such a
middle tier in desktop applications or test suites.
All things considered, if you do not use EJBs, stick with local
SessionFactory
setup and Spring's
HibernateTransactionManager
or
JtaTransactionManager
. You get all of the
benefits, including proper transactional JVM-level caching and
distributed transactions, without the inconvenience of container
deployment. JNDI registration of a Hibernate
SessionFactory
through the JCA connector
only adds value when used in conjunction with EJBs.
In some JTA environments with very strict
XADataSource
implementations -- currently
only some WebLogic Server and WebSphere versions -- when Hibernate is
configured without regard to the JTA
PlatformTransactionManager
object for
that environment, it is possible for spurious warning or exceptions to
show up in the application server log. These warnings or exceptions
indicate that the connection being accessed is no longer valid, or JDBC
access is no longer valid, possibly because the transaction is no longer
active. As an example, here is an actual exception from WebLogic:
java.sql.SQLException: The transaction is no longer active - status: 'Committed'. No further JDBC access is allowed within this transaction.
You resolve this warning by simply making Hibernate aware of the
JTA PlatformTransactionManager
instance,
to which it will synchronize (along with Spring). You have two options
for doing this:
If in your application context you are already directly
obtaining the JTA
PlatformTransactionManager
object
(presumably from JNDI through
JndiObjectFactoryBean/
)
and feeding it, for example, to Spring's
<jee:jndi-lookup>
JtaTransactionManager
, then the easiest way
is to specify a reference to the bean defining this JTA
PlatformTransactionManager
instance as
the value of the jtaTransactionManager property
for LocalSessionFactoryBea.
Spring then makes
the object available to Hibernate.
More likely you do not already have the JTA
PlatformTransactionManager
instance,
because Spring's JtaTransactionManager
can
find it itself. Thus
you need to configure Hibernate to look up JTA
PlatformTransactionManager
directly.
You do this by configuring an application server- specific
TransactionManagerLookup
class in the Hibernate
configuration, as described in the Hibernate manual.
The remainder of this section describes the sequence of events
that occur with and without Hibernate's awareness of the JTA
PlatformTransactionManager
.
When Hibernate is not configured with any awareness of the JTA
PlatformTransactionManager
, the following
events occur when a JTA transaction commits:
The JTA transaction commits.
Spring's JtaTransactionManager
is
synchronized to the JTA transaction, so it is called back through an
afterCompletion callback by the JTA transaction
manager.
Among other activities, this synchronizationcan
trigger a callback by Spring to Hibernate, through Hibernate's
afterTransactionCompletion
callback (used
to clear the Hibernate cache), followed by an explicit
close()
call on the Hibernate Session, which
causes Hibernate to attempt to close()
the JDBC
Connection.
In some environments, this
Connection.close()
call then triggers the
warning or error, as the application server no longer considers the
Connection
usable at all, because the
transaction has already been committed.
When Hibernate is configured with awareness of the JTA
PlatformTransactionManager
, the following
events occur when a JTA transaction commits:
the JTA transaction is ready to commit.
Spring's JtaTransactionManager
is
synchronized to the JTA transaction, so the transaction is called
back through a beforeCompletion callback by the
JTA transaction manager.
Spring is aware that Hibernate itself is synchronized to the
JTA transaction, and behaves differently than in the previous
scenario. Assuming the Hibernate
Session
needs to be closed at all,
Spring will close it now.
The JTA transaction commits.
Hibernate is synchronized to the JTA transaction, so the transaction is called back through an afterCompletion callback by the JTA transaction manager, and can properly clear its cache.