This part of the reference documentation is concerned with data access and the interaction between the data access layer and the business or service layer.
Spring’s comprehensive transaction management support is covered in some detail, followed by thorough coverage of the various data access frameworks and technologies with which the Spring Framework integrates.
1. Transaction Management
Comprehensive transaction support is among the most compelling reasons to use the Spring Framework. The Spring Framework provides a consistent abstraction for transaction management that delivers the following benefits:
-
A consistent programming model across different transaction APIs, such as Java Transaction API (JTA), JDBC, Hibernate, and the Java Persistence API (JPA).
-
Support for declarative transaction management.
-
A simpler API for programmatic transaction management than complex transaction APIs, such as JTA.
-
Excellent integration with Spring’s data access abstractions.
The following sections describe the Spring Framework’s transaction features and technologies:
-
Advantages of the Spring Framework’s transaction support model describes why you would use the Spring Framework’s transaction abstraction instead of EJB Container-Managed Transactions (CMT) or choosing to drive local transactions through a proprietary API, such as Hibernate.
-
Understanding the Spring Framework transaction abstraction outlines the core classes and describes how to configure and obtain
DataSource
instances from a variety of sources. -
Synchronizing resources with transactions describes how the application code ensures that resources are created, reused, and cleaned up properly.
-
Declarative transaction management describes support for declarative transaction management.
-
Programmatic transaction management covers support for programmatic (that is, explicitly coded) transaction management.
-
Transaction bound event describes how you could use application events within a transaction.
The chapter also includes discussions of best practices, application server integration, and solutions to common problems.
1.1. Advantages of the Spring Framework’s Transaction Support Model
Traditionally, Java EE developers have had two choices for transaction management: global or local transactions, both of which have profound limitations. Global and local transaction management is reviewed in the next two sections, followed by a discussion of how the Spring Framework’s transaction management support addresses the limitations of the global and local transaction models.
1.1.1. Global Transactions
Global transactions let you work with multiple transactional resources, typically
relational databases and message queues. The application server manages global
transactions through the JTA, which is a cumbersome API (partly due to its
exception model). Furthermore, a JTA UserTransaction
normally needs to be
sourced from
JNDI, meaning that you also need to use JNDI in order to use JTA. The use
of global transactions limits any potential reuse of application code, as JTA is
normally only available in an application server environment.
Previously, the preferred way to use global transactions was through EJB CMT (Container Managed Transaction). CMT is a form of declarative transaction management (as distinguished from programmatic transaction management). EJB CMT removes the need for transaction-related JNDI lookups, although the use of EJB itself necessitates the use of JNDI. It removes most but not all of the need to write Java code to control transactions. The significant downside is that CMT is tied to JTA and an application server environment. Also, it is only available if one chooses to implement business logic in EJBs (or at least behind a transactional EJB facade). The negatives of EJB in general are so great that this is not an attractive proposition, especially in the face of compelling alternatives for declarative transaction management.
1.1.2. Local Transactions
Local transactions are resource-specific, such as a transaction associated with a JDBC connection. Local transactions may be easier to use but have a significant disadvantage: They cannot work across multiple transactional resources. For example, code that manages transactions by using a JDBC connection cannot run within a global JTA transaction. Because the application server is not involved in transaction management, it cannot help ensure correctness across multiple resources. (It is worth noting that most applications use a single transaction resource.) Another downside is that local transactions are invasive to the programming model.
1.1.3. Spring Framework’s Consistent Programming Model
Spring resolves the disadvantages of global and local transactions. It lets application developers use a consistent programming model in any environment. You write your code once, and it can benefit from different transaction management strategies in different environments. The Spring Framework provides both declarative and programmatic transaction management. Most users prefer declarative transaction management, which we recommend in most cases.
With programmatic transaction management, developers work with the Spring Framework transaction abstraction, which can run over any underlying transaction infrastructure. With the preferred declarative model, developers typically write little or no code related to transaction management and, hence, do not depend on the Spring Framework transaction API or any other transaction API.
1.2. Understanding the Spring Framework Transaction Abstraction
The key to the Spring transaction abstraction is the notion of a transaction
strategy. A transaction strategy is defined by the
org.springframework.transaction.PlatformTransactionManager
interface, which the
following listing shows:
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
This is primarily a service provider interface (SPI), although you can use it
programmatically
from your application code. Because
PlatformTransactionManager
is an interface, it can be easily mocked or stubbed as
necessary. It is not tied to a lookup strategy, such as JNDI.
PlatformTransactionManager
implementations are defined like any other object (or
bean)
in the Spring Framework IoC container. This benefit alone makes Spring Framework
transactions a worthwhile abstraction, even when you work with JTA. You can test transactional
code
much more easily than if it used JTA directly.
Again, in keeping with Spring’s philosophy, the TransactionException
that can be
thrown
by any of the PlatformTransactionManager
interface’s methods is unchecked (that
is, it extends the java.lang.RuntimeException
class). Transaction infrastructure
failures are almost invariably fatal. In rare cases where application code can actually
recover from a transaction failure, the application developer can still choose to catch
and handle TransactionException
. The salient point is that developers are not
forced to do so.
The getTransaction(..)
method returns a TransactionStatus
object,
depending on a
TransactionDefinition
parameter. The returned TransactionStatus
might
represent a
new transaction or can represent an existing transaction, if a matching transaction
exists in the current call stack. The implication in this latter case is that, as with
Java EE transaction contexts, a TransactionStatus
is associated with a thread of
execution.
The TransactionDefinition
interface specifies:
-
Propagation: Typically, all code executed within a transaction scope runs in that transaction. However, you can specify the behavior if a transactional method is executed when a transaction context already exists. For example, code can continue running in the existing transaction (the common case), or the existing transaction can be suspended and a new transaction created. Spring offers all of the transaction propagation options familiar from EJB CMT. To read about the semantics of transaction propagation in Spring, see Transaction Propagation.
-
Isolation: The degree to which this transaction is isolated from the work of other transactions. For example, can this transaction see uncommitted writes from other transactions?
-
Timeout: How long this transaction runs before timing out and being automatically rolled back by the underlying transaction infrastructure.
-
Read-only status: You can use a read-only transaction when your code reads but does not modify data. Read-only transactions can be a useful optimization in some cases, such as when you use Hibernate.
These settings reflect standard transactional concepts. If necessary, refer to resources that discuss transaction isolation levels and other core transaction concepts. Understanding these concepts is essential to using the Spring Framework or any transaction management solution.
The TransactionStatus
interface provides a simple way for transactional code to
control transaction execution and query transaction status. The concepts should be
familiar, as they are common to all transaction APIs. The following listing shows the
TransactionStatus
interface:
public interface TransactionStatus extends SavepointManager {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
void flush();
boolean isCompleted();
}
Regardless of whether you opt for declarative or programmatic transaction management in
Spring, defining the correct PlatformTransactionManager
implementation is
absolutely
essential. You typically define this implementation through dependency injection.
PlatformTransactionManager
implementations normally require knowledge of the
environment in which they work: JDBC, JTA, Hibernate, and so on. The following examples
show how you can define a local PlatformTransactionManager
implementation (in this
case,
with plain JDBC.)
You can define a JDBC DataSource
by creating a bean similar to the following:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
The related PlatformTransactionManager
bean definition then has a reference to
the DataSource
definition. It should resemble the following example:
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
If you use JTA in a Java EE container, then you use a container DataSource
, obtained
through JNDI, in conjunction with Spring’s JtaTransactionManager
. The following
example shows what the JTA
and JNDI lookup version would look like:
<?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:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jee
https://www.springframework.org/schema/jee/spring-jee.xsd">
<jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
<!-- other <bean/> definitions here -->
</beans>
The JtaTransactionManager
does not need to know about the DataSource
(or any other
specific resources) because it uses the container’s global transaction management
infrastructure.
The preceding definition of the dataSource bean uses the <jndi-lookup/>
tag
from the jee namespace. For more information see
The
JEE Schema.
|
You can also use easily Hibernate local transactions, as shown in the following
examples. In this case, you need to define a Hibernate LocalSessionFactoryBean
,
which your application code can use to obtain Hibernate Session
instances.
The DataSource
bean definition is similar to the local JDBC example shown
previously and, thus, is not shown in the following example.
If the DataSource (used by any non-JTA transaction manager) is looked up
through
JNDI and managed by a Java EE container, it should be non-transactional, because the
Spring Framework (rather than the Java EE container) manages the transactions.
|
The txManager
bean in this case is of the HibernateTransactionManager
type. In the
same way as the DataSourceTransactionManager
needs a reference to the DataSource
,
the HibernateTransactionManager
needs a reference to the
SessionFactory
.
The following example declares sessionFactory
and txManager
beans:
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mappingResources">
<list>
<value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<value>
hibernate.dialect=${hibernate.dialect}
</value>
</property>
</bean>
<bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
If you use Hibernate and Java EE container-managed JTA transactions, you
should use the same JtaTransactionManager
as in the previous JTA example for
JDBC, as the following example shows:
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
| If you use JTA, your transaction manager definition should look the same, regardless of what data access technology you use, be it JDBC, Hibernate JPA, or any other supported technology. This is due to the fact that JTA transactions are global transactions, which can enlist any transactional resource. |
In all these cases, application code does not need to change. You can change how transactions are managed merely by changing configuration, even if that change means moving from local to global transactions or vice versa.
1.3. Synchronizing Resources with Transactions
How to create different transaction managers and how they are linked to related resources
that need to be synchronized to transactions (for example
DataSourceTransactionManager
to a JDBC DataSource
, HibernateTransactionManager
to a Hibernate
SessionFactory
,
and so forth) should now be clear. This section describes how the application code
(directly or indirectly, by using a persistence API such as JDBC, Hibernate, or JPA)
ensures that these resources are created, reused, and cleaned up properly. The section
also discusses how transaction synchronization is (optionally) triggered through the
relevant PlatformTransactionManager
.
1.3.1. High-level Synchronization Approach
The preferred approach is to use Spring’s highest-level template based persistence
integration APIs or to use native ORM APIs with transaction-aware factory beans or
proxies for managing the native resource factories. These transaction-aware solutions
internally handle resource creation and reuse, cleanup, optional transaction
synchronization of the resources, and exception mapping. Thus, user data access code does
not have to address these tasks but can focus purely on non-boilerplate
persistence logic. Generally, you use the native ORM API or take a template approach
for JDBC access by using the JdbcTemplate
. These solutions are detailed in
subsequent
chapters of this reference documentation.
1.3.2. Low-level Synchronization Approach
Classes such as DataSourceUtils
(for JDBC),
EntityManagerFactoryUtils
(for JPA),
SessionFactoryUtils
(for Hibernate), and so on exist at a lower level. When you
want the
application code to deal directly with the resource types of the native persistence APIs,
you use these classes to ensure that proper Spring Framework-managed instances are obtained,
transactions are (optionally) synchronized, and exceptions that occur in the process are
properly mapped to a consistent API.
For example, in the case of JDBC, instead of the traditional JDBC approach of calling
the getConnection()
method on the DataSource
, you can instead use
Spring’s
org.springframework.jdbc.datasource.DataSourceUtils
class, as follows:
Connection conn = DataSourceUtils.getConnection(dataSource);
If an existing transaction already has a connection synchronized (linked) to it, that
instance is returned. Otherwise, the method call triggers the creation of a new
connection, which is (optionally) synchronized to any existing transaction and made
available for subsequent reuse in that same transaction. As mentioned earlier, any
SQLException
is wrapped in a Spring Framework CannotGetJdbcConnectionException
,
one
of the Spring Framework’s hierarchy of unchecked DataAccessException
types.
This approach
gives you more information than can be obtained easily from the SQLException
and
ensures portability across databases and even across different persistence technologies.
This approach also works without Spring transaction management (transaction synchronization is optional), so you can use it whether or not you use Spring for transaction management.
Of course, once you have used Spring’s JDBC support, JPA support, or Hibernate support,
you generally prefer not to use DataSourceUtils
or the other helper classes,
because you are much happier working through the Spring abstraction than directly
with the relevant APIs. For example, if you use the Spring JdbcTemplate
or
jdbc.object
package to simplify your use of JDBC, correct connection retrieval
occurs
behind the scenes and you need not write any special code.
1.3.3.
TransactionAwareDataSourceProxy
At the very lowest level exists the TransactionAwareDataSourceProxy
class. This
is a
proxy for a target DataSource
, which wraps the target DataSource
to add awareness of
Spring-managed transactions. In this respect, it is similar to a transactional JNDI
DataSource
, as provided by a Java EE server.
You should almost never need or want to use this class, except when existing
code must be called and passed a standard JDBC DataSource
interface
implementation. In
that case, it is possible that this code is usable but is participating in Spring-managed
transactions. You can write your new code by using the higher-level
abstractions mentioned earlier.
1.4. Declarative transaction management
| Most Spring Framework users choose declarative transaction management. This option has the least impact on application code and, hence, is most consistent with the ideals of a non-invasive lightweight container. |
The Spring Framework’s declarative transaction management is made possible with Spring aspect-oriented programming (AOP). However, as the transactional aspects code comes with the Spring Framework distribution and may be used in a boilerplate fashion, AOP concepts do not generally have to be understood to make effective use of this code.
The Spring Framework’s declarative transaction management is similar to EJB CMT, in that
you can specify transaction behavior (or lack of it) down to the individual method level.
You can make a setRollbackOnly()
call within a transaction context, if
necessary. The differences between the two types of transaction management are:
-
Unlike EJB CMT, which is tied to JTA, the Spring Framework’s declarative transaction management works in any environment. It can work with JTA transactions or local transactions by using JDBC, JPA, or Hibernate by adjusting the configuration files.
-
You can apply the Spring Framework declarative transaction management to any class, not merely special classes such as EJBs.
-
The Spring Framework offers declarative rollback rules, a feature with no EJB equivalent. Both programmatic and declarative support for rollback rules is provided.
-
The Spring Framework lets you customize transactional behavior by using AOP. For example, you can insert custom behavior in the case of transaction rollback. You can also add arbitrary advice, along with transactional advice. With EJB CMT, you cannot influence the container’s transaction management, except with
setRollbackOnly()
. -
The Spring Framework does not support propagation of transaction contexts across remote calls, as high-end application servers do. If you need this feature, we recommend that you use EJB. However, consider carefully before using such a feature, because, normally, one does not want transactions to span remote calls.
The concept of rollback rules is important. They let you specify which exceptions
(and throwables) should cause automatic rollback. You can specify this declaratively, in
configuration, not in Java code. So, although you can still call setRollbackOnly()
on
the TransactionStatus
object to roll back the current transaction back, most often
you
can specify a rule that MyApplicationException
must always result in rollback. The
significant advantage to this option is that business objects do not depend on the
transaction infrastructure. For example, they typically do not need to import Spring
transaction APIs or other Spring APIs.
Although EJB container default behavior automatically rolls back the transaction on a
system exception (usually a runtime exception), EJB CMT does not roll back the
transaction automatically on an application exception (that is, a checked exception
other than java.rmi.RemoteException
). While the Spring default behavior for
declarative transaction management follows EJB convention (roll back is automatic only
on unchecked exceptions), it is often useful to customize this behavior.
1.4.1. Understanding the Spring Framework’s Declarative Transaction Implementation
It is not sufficient merely to tell you to annotate your classes with the
@Transactional
annotation, add @EnableTransactionManagement
to
your configuration,
and expect you to understand how it all works. To provide a deeper understanding,
this section explains the inner workings of the Spring Framework’s declarative
transaction infrastructure in the event of transaction-related issues.
The most important concepts to grasp with regard to the Spring Framework’s declarative
transaction support are that this support is enabled
via
AOP proxies and that the transactional
advice is driven by metadata (currently XML- or annotation-based). The combination of
AOP with transactional metadata yields an AOP proxy that uses a
TransactionInterceptor
in conjunction with an appropriate PlatformTransactionManager
implementation to
drive
transactions around method invocations.
| Spring AOP is covered in the AOP section. |
The following images shows a Conceptual view of calling a method on a transactional proxy:
1.4.2. Example of Declarative Transaction Implementation
Consider the following interface and its attendant implementation. This example uses
Foo
and Bar
classes as placeholders so that you can concentrate on
the transaction
usage without focusing on a particular domain model. For the purposes of this example,
the fact that the DefaultFooService
class throws UnsupportedOperationException
instances in the body of each implemented method is good. That behavior lets you see
transactions be created and then rolled back in response to the
UnsupportedOperationException
instance. The following listing shows the FooService
interface:
// the service interface that we want to make transactional
package x.y.service;
public interface FooService {
Foo getFoo(String fooName);
Foo getFoo(String fooName, String barName);
void insertFoo(Foo foo);
void updateFoo(Foo foo);
}
The following example shows an implementation of the preceding interface:
package x.y.service;
public class DefaultFooService implements FooService {
public Foo getFoo(String fooName) {
throw new UnsupportedOperationException();
}
public Foo getFoo(String fooName, String barName) {
throw new UnsupportedOperationException();
}
public void insertFoo(Foo foo) {
throw new UnsupportedOperationException();
}
public void updateFoo(Foo foo) {
throw new UnsupportedOperationException();
}
}
Assume that the first two methods of the FooService
interface, getFoo(String)
and
getFoo(String, String)
, must execute in the context of a transaction with
read-only
semantics, and that the other methods, insertFoo(Foo)
and
updateFoo(Foo)
, must
execute in the context of a transaction with read-write semantics. The following
configuration is explained in detail in the next few paragraphs:
<!-- from the file 'context.xml' -->
<?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">
<!-- this is the service object that we want to make transactional -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- the transactional semantics... -->
<tx:attributes>
<!-- all methods starting with 'get' are read-only -->
<tx:method name="get*" read-only="true"/>
<!-- other methods use the default transaction settings (see below) -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- ensure that the above transactional advice runs for any execution
of an operation defined by the FooService interface -->
<aop:config>
<aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
</aop:config>
<!-- don't forget the DataSource -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
<property name="username" value="scott"/>
<property name="password" value="tiger"/>
</bean>
<!-- similarly, don't forget the PlatformTransactionManager -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- other <bean/> definitions here -->
</beans>
Examine the preceding configuration. It assumes that you want to make a service object, the
fooService
bean, transactional. The transaction semantics to apply are encapsulated in the
<tx:advice/>
definition. The <tx:advice/>
definition
reads as “all methods, on
starting with get
, are to execute in the context of a read-only transaction,
and all
other methods are to execute with the default transaction semantics”. The
transaction-manager
attribute of the <tx:advice/>
tag is set
to the name of the
PlatformTransactionManager
bean that is going to drive the transactions (in
this
case, the txManager
bean).
You can omit the transaction-manager attribute in the transactional
advice
(<tx:advice/> ) if the bean name of the PlatformTransactionManager
that you want to
wire in has the name transactionManager . If the PlatformTransactionManager
bean that
you want to wire in has any other name, you must use the
transaction-manager
attribute explicitly, as in the preceding example.
|
The <aop:config/>
definition ensures that the transactional advice defined
by the
txAdvice
bean executes at the appropriate points in the program. First, you
define a
pointcut that matches the execution of any operation defined in the FooService
interface
(fooServiceOperation
). Then you associate the pointcut with the
txAdvice
by using an
advisor. The result indicates that, at the execution of a fooServiceOperation
,
the advice defined by txAdvice
is run.
The expression defined within the <aop:pointcut/>
element is an AspectJ
pointcut
expression. See the
AOP section for more details on pointcut
expressions in Spring.
A common requirement is to make an entire service layer transactional. The best way to do this is to change the pointcut expression to match any operation in your service layer. The following example shows how to do so:
<aop:config>
<aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>
In the preceding example, it is assumed that all your service interfaces are defined
in the x.y.service package. See the
AOP section for more details.
|
Now that we have analyzed the configuration, you may be asking yourself, “What does all this configuration actually do?”
The configuration shown earlier is used to create a transactional proxy around the object
that is created from the fooService
bean definition. The proxy is configured
with
the transactional advice so that, when an appropriate method is invoked on the proxy,
a transaction is started, suspended, marked as read-only, and so on, depending on the
transaction configuration associated with that method. Consider the following program
that test drives the configuration shown earlier:
public final class Boot {
public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml", Boot.class);
FooService fooService = (FooService) ctx.getBean("fooService");
fooService.insertFoo (new Foo());
}
}
The output from running the preceding program should resemble the following (the Log4J output and the stack trace from the UnsupportedOperationException thrown by the insertFoo(..) method of the DefaultFooService class have been truncated for clarity):
<!-- the Spring container is starting up... -->
[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'fooService' with 0 common interceptors and 1 specific interceptors
<!-- the DefaultFooService is actually proxied -->
[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService]
<!-- ... the insertFoo(..) method is now being invoked on the proxy -->
[TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo
<!-- the transactional advice kicks in here... -->
[DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo]
[DataSourceTransactionManager] - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] for JDBC transaction
<!-- the insertFoo(..) method from DefaultFooService throws an exception... -->
[RuleBasedTransactionAttribute] - Applying rules to determine whether transaction should rollback on java.lang.UnsupportedOperationException
[TransactionInterceptor] - Invoking rollback for transaction on x.y.service.FooService.insertFoo due to throwable [java.lang.UnsupportedOperationException]
<!-- and the transaction is rolled back (by default, RuntimeException instances cause rollback) -->
[DataSourceTransactionManager] - Rolling back JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@a53de4]
[DataSourceTransactionManager] - Releasing JDBC Connection after transaction
[DataSourceUtils] - Returning JDBC Connection to DataSource
Exception in thread "main" java.lang.UnsupportedOperationException at x.y.service.DefaultFooService.insertFoo(DefaultFooService.java:14)
<!-- AOP infrastructure stack trace elements removed for clarity -->
at $Proxy0.insertFoo(Unknown Source)
at Boot.main(Boot.java:11)
1.4.3. Rolling Back a Declarative Transaction
The previous section outlined the basics of how to specify transactional settings for classes, typically service layer classes, declaratively in your application. This section describes how you can control the rollback of transactions in a simple, declarative fashion.
The recommended way to indicate to the Spring Framework’s transaction infrastructure
that a transaction’s work is to be rolled back is to throw an Exception
from
code that
is currently executing in the context of a transaction. The Spring Framework’s
transaction infrastructure code catches any unhandled Exception
as it bubbles
up
the call stack and makes a determination whether to mark the transaction for rollback.
In its default configuration, the Spring Framework’s transaction infrastructure code
marks a transaction for rollback only in the case of runtime, unchecked exceptions.
That is, when the thrown exception is an instance or subclass of
RuntimeException
. (
Error
instances also, by default, result in a rollback). Checked exceptions
that are
thrown from a transactional method do not result in rollback in the default
configuration.
You can configure exactly which Exception
types mark a transaction for rollback,
including checked exceptions. The following XML snippet demonstrates how you configure
rollback for a checked, application-specific Exception
type:
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
If you do not want a transaction rolled
back when an exception is thrown, you can also specify 'no rollback rules'. The following
example tells the Spring Framework’s
transaction infrastructure to commit the attendant transaction even in the face of an
unhandled InstrumentNotFoundException
:
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
When the Spring Framework’s transaction infrastructure catches an exception and it
consults the configured rollback rules to determine whether to mark the transaction for
rollback, the strongest matching rule wins. So, in the case of the following
configuration, any exception other than an InstrumentNotFoundException
results
in a
rollback of the attendant transaction:
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
</tx:attributes>
</tx:advice>
You can also indicate a required rollback programmatically. Although simple, this process is quite invasive and tightly couples your code to the Spring Framework’s transaction infrastructure. The following example shows how to programmatically indicate a required rollback:
public void resolvePosition() {
try {
// some business logic...
} catch (NoProductInStockException ex) {
// trigger rollback programmatically
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
You are strongly encouraged to use the declarative approach to rollback, if at all possible. Programmatic rollback is available should you absolutely need it, but its usage flies in the face of achieving a clean POJO-based architecture.
1.4.4. Configuring Different Transactional Semantics for Different Beans
Consider the scenario where you have a number of service layer objects, and you want to
apply a totally different transactional configuration to each of them. You can do so
by defining distinct <aop:advisor/>
elements with differing pointcut
and
advice-ref
attribute values.
As a point of comparison, first assume that all of your service layer classes are
defined in a root x.y.service
package. To make all beans that are instances of
classes
defined in that package (or in subpackages) and that have names ending in
Service
have
the default transactional configuration, you could write the following:
<?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">
<aop:config>
<aop:pointcut id="serviceOperation"
expression="execution(* x.y.service..*Service.*(..))"/>
<aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/>
</aop:config>
<!-- these two beans will be transactional... -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<bean id="barService" class="x.y.service.extras.SimpleBarService"/>
<!-- ... and these two beans won't -->
<bean id="anotherService" class="org.xyz.SomeService"/> <!-- (not in the right package) -->
<bean id="barManager" class="x.y.service.SimpleBarManager"/> <!-- (doesn't end in 'Service') -->
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- other transaction infrastructure beans such as a PlatformTransactionManager omitted... -->
</beans>
The following example shows how to configure two distinct beans with totally different transactional settings:
<?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">
<aop:config>
<aop:pointcut id="defaultServiceOperation"
expression="execution(* x.y.service.*Service.*(..))"/>
<aop:pointcut id="noTxServiceOperation"
expression="execution(* x.y.service.ddl.DefaultDdlManager.*(..))"/>
<aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/>
<aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/>
</aop:config>
<!-- this bean will be transactional (see the 'defaultServiceOperation' pointcut) -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- this bean will also be transactional, but with totally different transactional settings -->
<bean id="anotherFooService" class="x.y.service.ddl.DefaultDdlManager"/>
<tx:advice id="defaultTxAdvice">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<tx:advice id="noTxAdvice">
<tx:attributes>
<tx:method name="*" propagation="NEVER"/>
</tx:attributes>
</tx:advice>
<!-- other transaction infrastructure beans such as a PlatformTransactionManager omitted... -->
</beans>
1.4.5. <tx:advice/> Settings
This section summarizes the various transactional settings that you can specify by using
the <tx:advice/>
tag. The default <tx:advice/>
settings are:
-
The propagation setting is
REQUIRED.
-
The isolation level is
DEFAULT.
-
The transaction is read-write.
-
The transaction timeout defaults to the default timeout of the underlying transaction system or none if timeouts are not supported.
-
Any
RuntimeException
triggers rollback, and any checkedException
does not.
You can change these default settings. The following table summarizes the various attributes
of the <tx:method/>
tags
that are nested within <tx:advice/>
and
<tx:attributes/>
tags:
| Attribute | Required? | Default | Description |
|---|---|---|---|
|
Yes |
Method names with which
the transaction attributes are to be associated. The
wildcard (*) character can be used to associate the same transaction attribute
settings with a number of methods (for example, |
|
|
No |
|
Transaction propagation behavior. |
|
No |
|
Transaction isolation
level. Only applicable to propagation settings of |
|
No |
-1 |
Transaction timeout
(seconds). Only applicable to propagation |
|
No |
false |
Read-write versus
read-only transaction. Applies only to |
|
No |
Comma-delimited list of
|
|
|
No |
Comma-delimited list of
|
1.4.6.
Using @Transactional
In addition to the XML-based declarative approach to transaction configuration, you can use an annotation-based approach. Declaring transaction semantics directly in the Java source code puts the declarations much closer to the affected code. There is not much danger of undue coupling, because code that is meant to be used transactionally is almost always deployed that way anyway.
The standard javax.transaction.Transactional annotation is also
supported as a
drop-in replacement to Spring’s own annotation. Please refer to JTA 1.2
documentation
for more details.
|
The ease-of-use afforded by the use of the @Transactional
annotation is best
illustrated with an example, which is explained in the text that follows.
Consider the following class definition:
// the service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {
Foo getFoo(String fooName);
Foo getFoo(String fooName, String barName);
void insertFoo(Foo foo);
void updateFoo(Foo foo);
}
Used at the class level as above, the annotation indicates a default for all methods of the declaring class (as well as its subclasses). Alternatively, each method can get annotated individually. Note that a class-level annotation does not apply to ancestor classes up the class hierarchy; in such a scenario, methods need to be locally redeclared in order to participate in a subclass-level annotation.
When a POJO class such as the one above is defined as a bean in a Spring context,
you can make the bean instance transactional through an
@EnableTransactionManagement
annotation in a @Configuration
class. See the
javadoc
for full details.
In XML configuration, the <tx:annotation-driven/>
tag provides similar
convenience:
<!-- from the file 'context.xml' -->
<?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">
<!-- this is the service object that we want to make transactional -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- enable the configuration of transactional behavior based on annotations -->
<tx:annotation-driven transaction-manager="txManager"/><!-- a PlatformTransactionManager is still required --> (1)
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- (this dependency is defined somewhere else) -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- other <bean/> definitions here -->
</beans>
| 1 | The line that makes the bean instance transactional. |
You can omit the transaction-manager attribute in the <tx:annotation-driven/>
tag
if the bean name of the PlatformTransactionManager that you want to
wire in has the name,
transactionManager . If the PlatformTransactionManager bean
that you want to
dependency-inject has any other name, you have to use the
transaction-manager attribute,
as in the preceding example.
|
You can apply the @Transactional
annotation to an interface definition, a method
on an interface, a class definition, or a public method on a class. However, the
mere presence of the @Transactional
annotation is not enough to activate the
transactional behavior. The @Transactional
annotation is merely metadata that
can
be consumed by some runtime infrastructure that is @Transactional
-aware and
that
can use the metadata to configure the appropriate beans with transactional behavior.
In the preceding example, the <tx:annotation-driven/>
element switches on
the
transactional behavior.
The Spring team recommends that you annotate only concrete classes (and methods of
concrete classes) with the @Transactional annotation, as opposed to
annotating interfaces.
You certainly can place the @Transactional annotation on an interface
(or an interface
method), but this works only as you would expect it to if you use interface-based
proxies. The fact that Java annotations are not inherited from interfaces means
that,
if you use class-based proxies (proxy-target-class="true" ) or the
weaving-based
aspect (mode="aspectj" ), the transaction settings are not recognized by
the proxying
and weaving infrastructure, and the object is not wrapped in a transactional proxy.
|
In proxy mode (which is the default), only external method calls coming in through
the proxy are intercepted. This means that self-invocation (in effect, a method
within
the target object calling another method of the target object) does not lead to an
actual
transaction at runtime even if the invoked method is marked with @Transactional .
Also,
the proxy must be fully initialized to provide the expected behavior, so you should
not
rely on this feature in your initialization code (that is,
@PostConstruct ).
|
Consider using of AspectJ mode (see the mode
attribute in the following table)
if you
expect self-invocations to be wrapped with transactions as well. In this case, there no
proxy in the first place. Instead, the target class is woven (that is, its byte code is
modified) to turn @Transactional
into runtime behavior on any kind of method.
| XML Attribute | Annotation Attribute | Default | Description |
|---|---|---|---|
|
N/A (see |
|
Name of the transaction
manager to use. Required only if the name of the transaction
manager is not |
|
|
|
The default mode ( |
|
|
|
Applies to
|
|
|
|
Defines the order of the
transaction advice that is applied to beans annotated with
|
The default advice mode for processing @Transactional annotations is
proxy ,
which allows for interception of calls through the proxy only. Local calls within
the
same class cannot get intercepted that way. For a more advanced mode of
interception,
consider switching to aspectj mode in combination with compile-time or
load-time weaving.
|
The proxy-target-class attribute controls what type of transactional
proxies are
created for classes annotated with the @Transactional annotation. If
proxy-target-class is set to true , class-based proxies are
created. If
proxy-target-class is false or if the attribute is
omitted, standard JDK
interface-based proxies are created. (See [aop-proxying]
for a discussion of the
different proxy types.)
|
@EnableTransactionManagement and
<tx:annotation-driven/> looks for
@Transactional only on beans in the same application context in which
they are defined.
This means that, if you put annotation-driven configuration in a WebApplicationContext
for a DispatcherServlet , it checks for @Transactional
beans only in your controllers
and not your services. See MVC
for more information.
|
The most derived location takes precedence when evaluating the transactional settings
for a method. In the case of the following example, the DefaultFooService
class
is
annotated at the class level with the settings for a read-only transaction, but the
@Transactional
annotation on the updateFoo(Foo)
method in the same
class takes
precedence over the transactional settings defined at the class level.
@Transactional(readOnly = true)
public class DefaultFooService implements FooService {
public Foo getFoo(String fooName) {
// do something
}
// these settings have precedence for this method
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void updateFoo(Foo foo) {
// do something
}
}
@Transactional
Settings
The @Transactional
annotation is metadata that specifies that an interface,
class,
or method must have transactional semantics (for example, “start a brand new read-only
transaction when this method is invoked, suspending any existing transaction”).
The default @Transactional
settings are as follows:
-
The propagation setting is
PROPAGATION_REQUIRED.
-
The isolation level is
ISOLATION_DEFAULT.
-
The transaction is read-write.
-
The transaction timeout defaults to the default timeout of the underlying transaction system, or to none if timeouts are not supported.
-
Any
RuntimeException
triggers rollback, and any checkedException
does not.
You can change these default settings. The following table summarizes the various
properties of the @Transactional
annotation:
| Property | Type | Description |
|---|---|---|
|
Optional qualifier that specifies the transaction manager to be used. |
|
|
Optional propagation setting. |
|
|
|
Optional isolation
level. Applies only to propagation values of |
|
|
Optional transaction
timeout. Applies only to propagation values of |
|
|
Read-write versus
read-only transaction. Only applicable to values of |
|
Array of
|
Optional array of exception classes that must cause rollback. |
|
Array of class
names. The classes must be derived from |
Optional array of names of exception classes that must cause rollback. |
|
Array of
|
Optional array of exception classes that must not cause rollback. |
|
Array of |
Optional array of names of exception classes that must not cause rollback. |
Currently, you cannot have explicit control over the name of a transaction, where 'name'
means the transaction name that appears in a transaction monitor, if applicable
(for example, WebLogic’s transaction monitor), and in logging output. For declarative
transactions, the transaction name is always the fully-qualified class name +
.
+ the method name of the transactionally advised class. For example, if the
handlePayment(..)
method of the BusinessService
class started
a transaction, the
name of the transaction would be: com.example.BusinessService.handlePayment
.
Multiple
Transaction Managers with @Transactional
Most Spring applications need only a single transaction manager, but there may be
situations where you want multiple independent transaction managers in a single
application. You can use the value
attribute of the
@Transactional
annotation to
optionally specify the identity of the PlatformTransactionManager
to be
used.
This can either be the bean name or the qualifier value of the transaction manager bean.
For example, using the qualifier notation, you can combine the following Java code with
the following transaction manager bean declarations in the application context:
public class TransactionalService {
@Transactional("order")
public void setSomething(String name) { ... }
@Transactional("account")
public void doSomething() { ... }
}
The following listing shows the bean declarations:
<tx:annotation-driven/>
<bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
...
<qualifier value="order"/>
</bean>
<bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
...
<qualifier value="account"/>
</bean>
In this case, the two methods on TransactionalService
run under separate
transaction
managers, differentiated by the order
and account
qualifiers.
The default
<tx:annotation-driven>
target bean name,
transactionManager
, is still used if no
specifically qualified PlatformTransactionManager
bean is found.
Custom Shortcut Annotations
If you find you repeatedly use the same attributes with @Transactional
on
many different
methods, Spring’s
meta-annotation support lets you
define custom shortcut annotations for your specific use cases. For example, consider
the
following annotation definitions:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("order")
public @interface OrderTx {
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("account")
public @interface AccountTx {
}
The preceding annotations lets us write the example from the previous section as follows:
public class TransactionalService {
@OrderTx
public void setSomething(String name) { ... }
@AccountTx
public void doSomething() { ... }
}
In the preceding example, we used the syntax to define the transaction manager qualifier, but we could also have included propagation behavior, rollback rules, timeouts, and other features.
1.4.7. Transaction Propagation
This section describes some semantics of transaction propagation in Spring. Note that this section is not an introduction to transaction propagation proper. Rather, it details some of the semantics regarding transaction propagation in Spring.
In Spring-managed transactions, be aware of the difference between physical and logical transactions, and how the propagation setting applies to this difference.
Understanding
PROPAGATION_REQUIRED
PROPAGATION_REQUIRED
enforces a physical transaction, either locally for the
current
scope if no transaction exists yet or participating in an existing 'outer' transaction
defined for a larger scope. This is a fine default in common call stack arrangements
within the same thread (for example, a service facade that delegates to several
repository methods
where all the underlying resources have to participate in the service-level
transaction).
By default, a participating transaction joins the characteristics of the outer
scope,
silently ignoring the local isolation level, timeout value, or read-only flag
(if any).
Consider switching the validateExistingTransactions flag to true
on your transaction
manager if you want isolation level declarations to be rejected when
participating in
an existing transaction with a different isolation level. This non-lenient mode
also
rejects read-only mismatches (that is, an inner read-write transaction that
tries to participate
in a read-only outer scope).
|
When the propagation setting is PROPAGATION_REQUIRED
, a logical transaction
scope
is created for each method upon which the setting is applied. Each such logical
transaction scope can determine rollback-only status individually, with an outer
transaction scope being logically independent from the inner transaction scope.
In the case of standard PROPAGATION_REQUIRED
behavior, all these scopes are
mapped to the same physical transaction. So a rollback-only marker set in the inner
transaction scope does affect the outer transaction’s chance to actually commit.
However, in the case where an inner transaction scope sets the rollback-only marker, the
outer transaction has not decided on the rollback itself, so the rollback (silently
triggered by the inner transaction scope) is unexpected. A corresponding
UnexpectedRollbackException
is thrown at that point. This is expected
behavior so
that the caller of a transaction can never be misled to assume that a commit was
performed when it really was not. So, if an inner transaction (of which the outer caller
is not aware) silently marks a transaction as rollback-only, the outer caller still
calls commit. The outer caller needs to receive an
UnexpectedRollbackException
to
indicate clearly that a rollback was performed instead.
Understanding
PROPAGATION_REQUIRES_NEW
PROPAGATION_REQUIRES_NEW
, in contrast to PROPAGATION_REQUIRED
,
always uses an
independent physical transaction for each affected transaction scope, never
participating in an existing transaction for an outer scope. In such an arrangement,
the underlying resource transactions are different and, hence, can commit or roll back
independently, with an outer transaction not affected by an inner transaction’s rollback
status and with an inner transaction’s locks released immediately after its completion.
Such an independent inner transaction can also declare its own isolation level, timeout,
and read-only settings and not inherit an outer transaction’s characteristics.
Understanding
PROPAGATION_NESTED
PROPAGATION_NESTED
uses a single physical transaction with multiple
savepoints
that it can roll back to. Such partial rollbacks let an inner transaction scope
trigger a rollback for its scope, with the outer transaction being able to continue
the physical transaction despite some operations having been rolled back. This setting
is typically mapped onto JDBC savepoints, so it works only with JDBC resource
transactions. See Spring’s DataSourceTransactionManager
.
1.4.8. Advising Transactional Operations
Suppose you want to execute both transactional operations and some basic profiling advice.
How do you effect this in the context of <tx:annotation-driven/>
?
When you invoke the updateFoo(Foo)
method, you want to see the following
actions:
-
The configured profiling aspect starts.
-
The transactional advice executes.
-
The method on the advised object executes.
-
The transaction commits.
-
The profiling aspect reports the exact duration of the whole transactional method invocation.
| This chapter is not concerned with explaining AOP in any great detail (except as it applies to transactions). See AOP for detailed coverage of the AOP configuration and AOP in general. |
The following code shows the simple profiling aspect discussed earlier:
package x.y;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;
import org.springframework.core.Ordered;
public class SimpleProfiler implements Ordered {
private int order;
// allows us to control the ordering of advice
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
// this method is the around advice
public Object profile(ProceedingJoinPoint call) throws Throwable {
Object returnValue;
StopWatch clock = new StopWatch(getClass().getName());
try {
clock.start(call.toShortString());
returnValue = call.proceed();
} finally {
clock.stop();
System.out.println(clock.prettyPrint());
}
return returnValue;
}
}
The ordering of advice
is controlled through the Ordered
interface. For full details on advice
ordering, see
Advice
ordering.
The following configuration creates a fooService
bean that has profiling and
transactional aspects applied to it in the desired order:
<?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">
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- this is the aspect -->
<bean id="profiler" class="x.y.SimpleProfiler">
<!-- execute before the transactional advice (hence the lower order number) -->
<property name="order" value="1"/>
</bean>
<tx:annotation-driven transaction-manager="txManager" order="200"/>
<aop:config>
<!-- this advice will execute around the transactional advice -->
<aop:aspect id="profilingAspect" ref="profiler">
<aop:pointcut id="serviceMethodWithReturnValue"
expression="execution(!void x.y..*Service.*(..))"/>
<aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
</aop:aspect>
</aop:config>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
<property name="username" value="scott"/>
<property name="password" value="tiger"/>
</bean>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
You can configure any number of additional aspects in similar fashion.
The following example creates the same setup as the previous two examples but uses the purely XML declarative approach:
<?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">
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- the profiling advice -->
<bean id="profiler" class="x.y.SimpleProfiler">
<!-- execute before the transactional advice (hence the lower order number) -->
<property name="order" value="1"/>
</bean>
<aop:config>
<aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/>
<!-- will execute after the profiling advice (c.f. the order attribute) -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="entryPointMethod" order="2"/>
<!-- order value is higher than the profiling aspect -->
<aop:aspect id="profilingAspect" ref="profiler">
<aop:pointcut id="serviceMethodWithReturnValue"
expression="execution(!void x.y..*Service.*(..))"/>
<aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
</aop:aspect>
</aop:config>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- other <bean/> definitions such as a DataSource and a PlatformTransactionManager here -->
</beans>
The result of the preceding configuration is a fooService
bean that has
profiling and
transactional aspects applied to it in that order. If you want the profiling advice
to execute after the transactional advice on the way in and before the
transactional advice on the way out, you can swap the value of the profiling
aspect bean’s order
property so that it is higher than the transactional
advice’s
order value.
You can configure additional aspects in similar fashion.
1.4.9.
Using @Transactional
with AspectJ
You can also use the Spring Framework’s @Transactional
support outside of a
Spring
container by means of an AspectJ aspect. To do so, first annotate your classes
(and optionally your classes' methods) with the @Transactional
annotation,
and then link (weave) your application with the
org.springframework.transaction.aspectj.AnnotationTransactionAspect
defined in
the
spring-aspects.jar
file. You must also configure The aspect with a transaction
manager. You can use the Spring Framework’s IoC container to take care of
dependency-injecting the aspect. The simplest way to configure the transaction
management aspect is to use the <tx:annotation-driven/>
element and
specify the mode
attribute to aspectj
as described in Using
@Transactional
. Because
we focus here on applications that run outside of a Spring container, we show
you how to do it programmatically.
Prior to continuing, you may want to read Using
@Transactional and
AOP
respectively.
|
The following example shows how to create a transaction manager and configure the
AnnotationTransactionAspect
to use it:
// construct an appropriate transaction manager
DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource());
// configure the AnnotationTransactionAspect to use it; this must be done before executing any transactional methods
AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager);
| When you use this aspect, you must annotate the implementation class (or the methods within that class or both), not the interface (if any) that the class implements. AspectJ follows Java’s rule that annotations on interfaces are not inherited. |
The @Transactional
annotation on a class specifies the default transaction
semantics
for the execution of any public method in the class.
The @Transactional
annotation on a method within the class overrides the default
transaction semantics given by the class annotation (if present). You can annotate any
method,
regardless of visibility.
To weave your applications with the AnnotationTransactionAspect
, you must either
build
your application with AspectJ (see the
AspectJ
Development
Guide) or use load-time weaving. See Load-time
weaving with
AspectJ in the Spring Framework for a discussion of load-time weaving with AspectJ.
1.5. Programmatic Transaction Management
The Spring Framework provides two means of programmatic transaction management, by using:
-
The
TransactionTemplate
. -
A
PlatformTransactionManager
implementation directly.
The Spring team generally recommends the TransactionTemplate
for programmatic
transaction management. The second approach is similar to using the JTA
UserTransaction
API, although exception handling is less cumbersome.
1.5.1.
Using the TransactionTemplate
The TransactionTemplate
adopts the same approach as other Spring templates, such
as
the JdbcTemplate
. It uses a callback approach (to free application code from
having to
do the boilerplate acquisition and release transactional resources) and results in
code that is intention driven, in that your code focuses solely on what
you want to do.
As the examples that follow show, using the TransactionTemplate
absolutely
couples you to Spring’s transaction infrastructure and APIs. Whether or not
programmatic
transaction management is suitable for your development needs is a decision that you
have to make yourself.
|
Application code that must execute in a transactional context and that explicitly uses the
TransactionTemplate
resembles the next example. You, as an application
developer, can write a TransactionCallback
implementation (typically expressed
as an
anonymous inner class) that contains the code that you need to execute in the context of
a transaction. You can then pass an instance of your custom TransactionCallback
to the
execute(..)
method exposed on the TransactionTemplate
. The
following example shows how to do so:
public class SimpleService implements Service {
// single TransactionTemplate shared amongst all methods in this instance
private final TransactionTemplate transactionTemplate;
// use constructor-injection to supply the PlatformTransactionManager
public SimpleService(PlatformTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
public Object someServiceMethod() {
return transactionTemplate.execute(new TransactionCallback() {
// the code in this method executes in a transactional context
public Object doInTransaction(TransactionStatus status) {
updateOperation1();
return resultOfUpdateOperation2();
}
});
}
}
If there is no return value, you can use the convenient TransactionCallbackWithoutResult
class
with an anonymous class, as follows:
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
updateOperation1();
updateOperation2();
}
});
Code within the callback can roll the transaction back by calling the
setRollbackOnly()
method on the supplied TransactionStatus
object,
as follows:
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
updateOperation1();
updateOperation2();
} catch (SomeBusinessException ex) {
status.setRollbackOnly();
}
}
});
Specifying Transaction Settings
You can specify transaction settings (such as the propagation mode, the isolation level,
the timeout, and so forth) on the TransactionTemplate
either
programmatically or in
configuration. By default, TransactionTemplate
instances have the
default
transactional settings. The
following example shows the programmatic customization of the transactional settings for
a specific TransactionTemplate:
public class SimpleService implements Service {
private final TransactionTemplate transactionTemplate;
public SimpleService(PlatformTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
// the transaction settings can be set here explicitly if so desired
this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
this.transactionTemplate.setTimeout(30); // 30 seconds
// and so forth...
}
}
The following example defines a TransactionTemplate
with some custom
transactional
settings by using Spring XML configuration:
<bean id="sharedTransactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
<property name="timeout" value="30"/>
</bean>
You can then inject the sharedTransactionTemplate
into as many services as are required.
Finally, instances of the TransactionTemplate
class are thread-safe, in that
instances
do not maintain any conversational state. TransactionTemplate
instances do,
however,
maintain configuration state. So, while a number of classes may share a single instance
of a TransactionTemplate
, if a class needs to use a TransactionTemplate
with
different settings (for example, a different isolation level), you need to create
two distinct TransactionTemplate
instances.
1.5.2.
Using the PlatformTransactionManager
You can also use the org.springframework.transaction.PlatformTransactionManager
directly to manage your transaction. To do so, pass the implementation of the
PlatformTransactionManager
you use to your bean through a bean reference. Then,
by using the TransactionDefinition
and TransactionStatus
objects,
you can initiate
transactions, roll back, and commit. The following example shows how to do so:
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can be done only programmatically
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
// execute your business logic here
}
catch (MyException ex) {
txManager.rollback(status);
throw ex;
}
txManager.commit(status);
1.6. Choosing Between Programmatic and Declarative Transaction Management
Programmatic transaction management is usually a good idea only if you have a small
number of transactional operations. For example, if you have a web application that
requires transactions only for certain update operations, you may not want to set up
transactional proxies by using Spring or any other technology. In this case, using the
TransactionTemplate
may be a good approach. Being able to set the transaction name
explicitly is also something that can be done only by using the programmatic approach to
transaction management.
On the other hand, if your application has numerous transactional operations, declarative transaction management is usually worthwhile. It keeps transaction management out of business logic and is not difficult to configure. When using the Spring Framework, rather than EJB CMT, the configuration cost of declarative transaction management is greatly reduced.
1.7. Transaction-bound Events
As of Spring 4.2, the listener of an event can be bound to a phase of the transaction. The typical example is to handle the event when the transaction has completed successfully. Doing so lets events be used with more flexibility when the outcome of the current transaction actually matters to the listener.
You can register a regular event listener by using the @EventListener
annotation. If
you need
to bind it to the transaction, use @TransactionalEventListener
. When you do so, the
listener
is bound to the commit phase of the transaction by default.
The next example shows this concept. Assume that a component publishes an order-created event and that we want to define a listener that should only handle that event once the transaction in which it has been published has committed successfully. The following example sets up such an event listener:
@Component
public class MyComponent {
@TransactionalEventListener
public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
...
}
}
The @TransactionalEventListener
annotation exposes a phase
attribute
that lets you customize
the phase of the transaction to which the listener should be bound. The valid phases are BEFORE_COMMIT
,
AFTER_COMMIT
(default), AFTER_ROLLBACK
, and
AFTER_COMPLETION
that aggregates the transaction
completion (be it a commit or a rollback).
If no transaction is running, the listener is not invoked at all, since we cannot honor the
required
semantics. You can, however, override that behavior by setting the
fallbackExecution
attribute
of the annotation to true
.
1.8. Application server-specific integration
Spring’s transaction abstraction is generally application server-agnostic. Additionally,
Spring’s JtaTransactionManager
class (which can optionally perform a JNDI lookup
for
the JTA UserTransaction
and TransactionManager
objects) autodetects
the location for
the latter object, which varies by application server. Having access to the JTA
TransactionManager
allows for enhanced transaction semantics — in particular,
supporting transaction suspension. See the
JtaTransactionManager
javadoc for details.
Spring’s JtaTransactionManager
is the standard choice to run on Java EE application
servers and is known to work on all common servers. Advanced functionality, such as
transaction suspension, works on many servers as well (including GlassFish, JBoss and
Geronimo) without any special configuration required. However, for fully supported
transaction suspension and further advanced integration, Spring includes special adapters
for WebLogic Server and WebSphere. These adapters are discussed in the following
sections.
For standard scenarios, including WebLogic Server and WebSphere, consider using the
convenient <tx:jta-transaction-manager/>
configuration element. When
configured,
this element automatically detects the underlying server and chooses the best
transaction manager available for the platform. This means that you need not explicitly
configure server-specific adapter classes (as discussed in the following sections).
Rather, they are chosen automatically, with the standard
JtaTransactionManager
as the default fallback.
1.8.1. IBM WebSphere
On WebSphere <IP_ADDRESS> and above, the recommended Spring JTA transaction manager to use is
WebSphereUowTransactionManager
. This special adapter uses IBM’s UOWManager
API,
which is available in WebSphere Application Server <IP_ADDRESS> and later. With this adapter,
Spring-driven transaction suspension (suspend and resume as initiated by
PROPAGATION_REQUIRES_NEW
) is officially supported by IBM.
1.8.2. Oracle WebLogic Server
On WebLogic Server 9.0 or above, you would typically use the
WebLogicJtaTransactionManager
instead of the stock
JtaTransactionManager
class. This
special WebLogic-specific subclass of the normal JtaTransactionManager
supports
the
full power of Spring’s transaction definitions in a WebLogic-managed transaction
environment, beyond standard JTA semantics. Features include transaction names,
per-transaction isolation levels, and proper resuming of transactions in all cases.
1.9. Solutions to Common Problems
This section describes solutions to some common problems.
1.9.1.
Using the Wrong Transaction Manager for a Specific DataSource
Use the correct PlatformTransactionManager
implementation based on your choice
of
transactional technologies and requirements. Used properly, the Spring Framework merely
provides a straightforward and portable abstraction. If you use global
transactions, you must use the
org.springframework.transaction.jta.JtaTransactionManager
class (or an
application
server-specific subclass of
it) for all your transactional operations. Otherwise, the transaction infrastructure
tries to perform local transactions on such resources as container DataSource
instances. Such local transactions do not make sense, and a good application server
treats them as errors.
1.10. Further Resources
For more information about the Spring Framework’s transaction support, see:
-
Distributed transactions in Spring, with and without XA is a JavaWorld presentation in which Spring’s David Syer guides you through seven patterns for distributed transactions in Spring applications, three of them with XA and four without.
-
Java Transaction Design Strategies is a book available from InfoQ that provides a well-paced introduction to transactions in Java. It also includes side-by-side examples of how to configure and use transactions with both the Spring Framework and EJB3.
2. DAO Support
The Data Access Object (DAO) support in Spring is aimed at making it easy to work with data access technologies (such as JDBC, Hibernate, or JPA) in a consistent way. This lets you switch between the aforementioned persistence technologies fairly easily, and it also lets you code without worrying about catching exceptions that are specific to each technology.
2.1. Consistent Exception Hierarchy
Spring provides a convenient translation from technology-specific exceptions, such as
SQLException
to its own exception class hierarchy, which has DataAccessException
as
the root exception. These exceptions wrap the original exception so that there is never any
risk that you might lose any information about what might have gone wrong.
In addition to JDBC exceptions, Spring can also wrap JPA- and Hibernate-specific exceptions, converting them to a set of focused runtime exceptions.  (You can still trap and handle exceptions anywhere you need to though.) As mentioned above, JDBC exceptions (including database-specific dialects) are also converted to the same hierarchy, meaning that you can perform some operations with JDBC within a consistent programming model.
The preceding discussion holds true for the various template classes in Spring’s support for
various ORM
frameworks. If you use the interceptor-based classes, the application must care
about handling HibernateExceptions
and PersistenceExceptions
itself,
preferably by
delegating to the convertHibernateAccessException(..)
or
convertJpaAccessException()
methods, respectively, of
SessionFactoryUtils
. These methods convert the exceptions
to exceptions that are compatible with the exceptions in the
org.springframework.dao
exception hierarchy. As PersistenceExceptions
are unchecked, they can get
thrown, too (sacrificing generic DAO abstraction in terms of exceptions, though).
The following image shows the exception hierarchy that Spring provides. (Note that the
class hierarchy detailed in the image shows only a subset of the entire
DataAccessException
hierarchy.)
2.2. Annotations Used to Configure DAO or Repository Classes
The best way to guarantee that your Data Access Objects (DAOs) or repositories provide
exception translation is to use the @Repository
annotation. This annotation also
lets the component scanning support find and configure your DAOs and repositories
without having to provide XML configuration entries for them. The following example shows
how to use the @Repository
annotation:
@Repository (1)
public class SomeMovieFinder implements MovieFinder {
// ...
}
| 1 | The @Repository annotation. |
Any DAO or repository implementation needs access to a persistence resource,
depending on the persistence technology used. For example, a JDBC-based repository
needs access to a JDBC DataSource
, and a JPA-based repository needs access to an
EntityManager
. The easiest way to accomplish this is to have this resource
dependency
injected by using one of the @Autowired
, @Inject
,
@Resource
or @PersistenceContext
annotations. The following example works for a JPA repository:
@Repository
public class JpaMovieFinder implements MovieFinder {
@PersistenceContext
private EntityManager entityManager;
// ...
}
If you use the classic Hibernate APIs, you can inject SessionFactory
, as the
following
example shows:
@Repository
public class HibernateMovieFinder implements MovieFinder {
private SessionFactory sessionFactory;
@Autowired
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
// ...
}
The last example we show here is for typical JDBC support. You could have the
DataSource
injected into an initialization method, where you would create a
JdbcTemplate
and other data access support classes (such as
SimpleJdbcCall
and others) by using
this DataSource
. The following example autowires a DataSource
:
@Repository
public class JdbcMovieFinder implements MovieFinder {
private JdbcTemplate jdbcTemplate;
@Autowired
public void init(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
// ...
}
| See the specific coverage of each persistence technology for details on how to configure the application context to take advantage of these annotations. |
3. Data Access with JDBC
The value provided by the Spring Framework JDBC abstraction is perhaps best shown by the sequence of actions outlined in the following table below. The table shows which actions Spring takes care of and which actions are your responsibility.
| Action | Spring | You |
|---|---|---|
Define connection parameters. |
X |
|
Open the connection. |
X |
|
Specify the SQL statement. |
X |
|
Declare parameters and provide parameter values |
X |
|
Prepare and execute the statement. |
X |
|
Set up the loop to iterate through the results (if any). |
X |
|
Do the work for each iteration. |
X |
|
Process any exception. |
X |
|
Handle transactions. |
X |
|
Close the connection, the statement, and the resultset. |
X |
The Spring Framework takes care of all the low-level details that can make JDBC such a tedious API.
3.1. Choosing an Approach for JDBC Database Access
You can choose among several approaches to form the basis for your JDBC database access.
In addition to three flavors of JdbcTemplate
, a new SimpleJdbcInsert
and
SimpleJdbcCall
approach optimizes database metadata, and the RDBMS Object style
takes a
more object-oriented approach similar to that of JDO Query design. Once you start using
one of these approaches, you can still mix and match to include a feature from a
different approach. All approaches require a JDBC 2.0-compliant driver, and some
advanced features require a JDBC 3.0 driver.
-
JdbcTemplate
is the classic and most popular Spring JDBC approach. This “lowest-level” approach and all others use a JdbcTemplate under the covers. -
NamedParameterJdbcTemplate
wraps aJdbcTemplate
to provide named parameters instead of the traditional JDBC?
placeholders. This approach provides better documentation and ease of use when you have multiple parameters for an SQL statement. -
SimpleJdbcInsert
andSimpleJdbcCall
optimize database metadata to limit the amount of necessary configuration. This approach simplifies coding so that you need to provide only the name of the table or procedure and provide a map of parameters matching the column names. This works only if the database provides adequate metadata. If the database does not provide this metadata, you have to provide explicit configuration of the parameters. -
RDBMS objects, including
MappingSqlQuery
,SqlUpdate
andStoredProcedure
, require you to create reusable and thread-safe objects during initialization of your data-access layer. This approach is modeled after JDO Query, wherein you define your query string, declare parameters, and compile the query. Once you do that, execute methods can be called multiple times with various parameter values.
3.2. Package Hierarchy
The Spring Framework’s JDBC abstraction framework consists of four different packages:
-
core
: Theorg.springframework.jdbc.core
package contains theJdbcTemplate
class and its various callback interfaces, plus a variety of related classes. A subpackage namedorg.springframework.jdbc.core.simple
contains theSimpleJdbcInsert
andSimpleJdbcCall
classes. Another subpackage namedorg.springframework.jdbc.core.namedparam
contains theNamedParameterJdbcTemplate
class and the related support classes. See Using the JDBC Core Classes to Control Basic JDBC Processing and Error Handling, JDBC Batch Operations, and Simplifying JDBC Operations with theSimpleJdbc
Classes. -
datasource
: Theorg.springframework.jdbc.datasource
package contains a utility class for easyDataSource
access and various simpleDataSource
implementations that you can use for testing and running unmodified JDBC code outside of a Java EE container. A subpackage namedorg.springfamework.jdbc.datasource.embedded
provides support for creating embedded databases by using Java database engines, such as HSQL, H2, and Derby. See Controlling Database Connections and Embedded Database Support. -
object
: Theorg.springframework.jdbc.object
package contains classes that represent RDBMS queries, updates, and stored procedures as thread-safe, reusable objects. See Modeling JDBC Operations as Java Objects. This approach is modeled by JDO, although objects returned by queries are naturally disconnected from the database. This higher-level of JDBC abstraction depends on the lower-level abstraction in theorg.springframework.jdbc.core
package. -
support
: Theorg.springframework.jdbc.support
package providesSQLException
translation functionality and some utility classes. Exceptions thrown during JDBC processing are translated to exceptions defined in theorg.springframework.dao
package. This means that code using the Spring JDBC abstraction layer does not need to implement JDBC or RDBMS-specific error handling. All translated exceptions are unchecked, which gives you the option of catching the exceptions from which you can recover while letting other exceptions be propagated to the caller. See UsingSQLExceptionTranslator
.
3.3. Using the JDBC Core Classes to Control Basic JDBC Processing and Error Handling
This section covers how to use the JDBC core classes to control basic JDBC processing, including error handling. It includes the following topics:
3.3.1.
Using JdbcTemplate
JdbcTemplate
is the central class in the JDBC core package. It handles the
creation and release of resources, which helps you avoid common errors, such as
forgetting to close the connection. It performs the basic tasks of the core JDBC
workflow (such as statement creation and execution), leaving application code to provide
SQL and extract results. The JdbcTemplate
class:
-
Runs SQL queries
-
Updates statements and stored procedure calls
-
Performs iteration over
ResultSet
instances and extraction of returned parameter values. -
Catches JDBC exceptions and translates them to the generic, more informative, exception hierarchy defined in the
org.springframework.dao
package. (See Consistent Exception Hierarchy.)
When you use the JdbcTemplate
for your code, you need only to implement callback
interfaces, giving them a clearly defined contract. Given a Connection
provided
by the
JdbcTemplate
class, the PreparedStatementCreator
callback interface creates a prepared statement, providing SQL and any necessary parameters.
The same is true for the
CallableStatementCreator
interface, which creates callable statements. The
RowCallbackHandler
interface extracts values from each row of a
ResultSet
.
You can use JdbcTemplate
within a DAO implementation through direct
instantiation
with a DataSource
reference, or you can configure it in a Spring IoC container
and give it to
DAOs as a bean reference.
The DataSource should always be configured as a bean in the Spring IoC
container. In
the first case the bean is given to the service directly; in the second case it is
given
to the prepared template.
|
All SQL issued by this class is logged at the DEBUG
level under the category
corresponding to the fully qualified class name of the template instance (typically
JdbcTemplate
, but it may be different if you use a custom subclass of the
JdbcTemplate
class).
The following sections provide some examples of JdbcTemplate
usage. These
examples
are not an exhaustive list of all of the functionality exposed by the
JdbcTemplate
.
See the attendant javadoc
for that.
Querying
(SELECT
)
The following query gets the number of rows in a relation:
int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class);
The following query uses a bind variable:
int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject(
"select count(*) from t_actor where first_name = ?", Integer.class, "Joe");
The following query looks for a String
:
String lastName = this.jdbcTemplate.queryForObject(
"select last_name from t_actor where id = ?",
new Object[]{1212L}, String.class);
The following query finds and populates a single domain object:
Actor actor = this.jdbcTemplate.queryForObject(
"select first_name, last_name from t_actor where id = ?",
new Object[]{1212L},
new RowMapper<Actor>() {
public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
});
The following query finds and populates a number of domain objects:
List<Actor> actors = this.jdbcTemplate.query(
"select first_name, last_name from t_actor",
new RowMapper<Actor>() {
public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
});
If the last two snippets of code actually existed in the same application, it would make
sense to remove the duplication present in the two RowMapper
anonymous
inner classes
and extract them out into a single class (typically a static
nested class)
that could
then be referenced by DAO methods as needed. For example, it may be better to write the
preceding code snippet as follows:
public List<Actor> findAllActors() {
return this.jdbcTemplate.query( "select first_name, last_name from t_actor", new ActorMapper());
}
private static final class ActorMapper implements RowMapper<Actor> {
public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
}
Updating
(INSERT
, UPDATE
, and DELETE
) with
JdbcTemplate
You can use the update(..)
method to perform insert, update, and delete
operations.
Parameter values are usually provided as variable arguments or, alternatively, as an
object array.
The following example inserts a new entry:
this.jdbcTemplate.update(
"insert into t_actor (first_name, last_name) values (?, ?)",
"Leonor", "Watling");
The following example updates an existing entry:
this.jdbcTemplate.update(
"update t_actor set last_name = ? where id = ?",
"Banjo", 5276L);
The following example deletes an entry:
this.jdbcTemplate.update(
"delete from actor where id = ?",
Long.valueOf(actorId));
Other
JdbcTemplate
Operations
You can use the execute(..)
method to run any arbitrary SQL. Consequently,
the
method is often used for DDL statements. It is heavily overloaded with variants that
take
callback interfaces, binding variable arrays, and so on. The following example creates a
table:
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
The following example invokes a stored procedure:
this.jdbcTemplate.update(
"call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
Long.valueOf(unionId));
More sophisticated stored procedure support is covered later.
JdbcTemplate
Best Practices
Instances of the JdbcTemplate
class are thread-safe, once configured. This
is
important because it means that you can configure a single instance of a JdbcTemplate
and then safely inject this shared reference into multiple DAOs (or repositories).
The JdbcTemplate
is stateful, in that it maintains a reference to a DataSource
,
but
this state is not conversational state.
A common practice when using the JdbcTemplate
class (and the associated
NamedParameterJdbcTemplate
class) is to
configure a DataSource
in your Spring configuration file and then
dependency-inject
that shared DataSource
bean into your DAO classes. The
JdbcTemplate
is created in
the setter for the DataSource
. This leads to DAOs that resemble the
following:
public class JdbcCorporateEventDao implements CorporateEventDao {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
}
The following example shows the corresponding XML configuration:
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="corporateEventDao" class="com.example.JdbcCorporateEventDao">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<context:property-placeholder location="jdbc.properties"/>
</beans>
An alternative to explicit configuration is to use component-scanning and annotation
support for dependency injection. In this case, you can annotate the class with @Repository
(which makes it a candidate for component-scanning) and annotate the
DataSource
setter
method with @Autowired
. The following example shows how to do so:
@Repository (1)
public class JdbcCorporateEventDao implements CorporateEventDao {
private JdbcTemplate jdbcTemplate;
@Autowired (2)
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource); (3)
}
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
}
| 1 | Annotate the class with @Repository . |
| 2 | annotate the DataSource setter method with @Autowired .
|
| 3 | Create a new JdbcTemplate with the DataSource . |
The following example shows the corresponding XML configuration:
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- Scans within the base package of the application for @Component classes to configure as beans -->
<context:component-scan base-package="org.springframework.docs.test" />
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<context:property-placeholder location="jdbc.properties"/>
</beans>
If you use Spring’s JdbcDaoSupport
class and your various JDBC-backed DAO
classes
extend from it, your sub-class inherits a setDataSource(..)
method from the
JdbcDaoSupport
class. You can choose whether to inherit from this class.
The
JdbcDaoSupport
class is provided as a convenience only.
Regardless of which of the above template initialization styles you choose to use (or
not), it is seldom necessary to create a new instance of a JdbcTemplate
class each
time you want to run SQL. Once configured, a JdbcTemplate
instance is
thread-safe.
If your application accesses multiple
databases, you may want multiple JdbcTemplate
instances, which requires
multiple DataSources
and, subsequently, multiple differently
configured JdbcTemplate
instances.
3.3.2.
Using NamedParameterJdbcTemplate
The NamedParameterJdbcTemplate
class adds support for programming JDBC
statements
by using named parameters, as opposed to programming JDBC statements using only classic
placeholder ( '?'
) arguments. The NamedParameterJdbcTemplate
class
wraps a
JdbcTemplate
and delegates to the wrapped JdbcTemplate
to do much
of its work. This
section describes only those areas of the NamedParameterJdbcTemplate
class that
differ
from the JdbcTemplate
itself — namely, programming JDBC statements by using
named
parameters. The following example shows how to use NamedParameterJdbcTemplate
:
// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
public int countOfActorsByFirstName(String firstName) {
String sql = "select count(*) from T_ACTOR where first_name = :first_name";
SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName);
return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
}
Notice the use of the named parameter notation in the value assigned to the sql
variable and the corresponding value that is plugged into the namedParameters
variable (of type MapSqlParameterSource
).
Alternatively, you can pass along named parameters and their corresponding values to a
NamedParameterJdbcTemplate
instance by using the Map
-based
style.The remaining
methods exposed by the NamedParameterJdbcOperations
and implemented by the
NamedParameterJdbcTemplate
class follow a similar pattern and are not covered
here.
The following example shows the use of the Map
-based style:
// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
public int countOfActorsByFirstName(String firstName) {
String sql = "select count(*) from T_ACTOR where first_name = :first_name";
Map<String, String> namedParameters = Collections.singletonMap("first_name", firstName);
return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
}
One nice feature related to the NamedParameterJdbcTemplate
(and existing in the
same
Java package) is the SqlParameterSource
interface. You have already seen an
example of
an implementation of this interface in one of the previous code snippets (the
MapSqlParameterSource
class). An SqlParameterSource
is a source of
named parameter
values to a NamedParameterJdbcTemplate
. The MapSqlParameterSource
class is a
simple implementation that is an adapter around a java.util.Map
, where the keys
are the parameter names and the values are the parameter values.
Another SqlParameterSource
implementation is the BeanPropertySqlParameterSource
class. This class wraps an arbitrary JavaBean (that is, an instance of a class that
adheres to the
JavaBean conventions) and uses the properties of the wrapped JavaBean as the source
of named parameter values.
The following example shows a typical JavaBean:
public class Actor {
private Long id;
private String firstName;
private String lastName;
public String getFirstName() {
return this.firstName;
}
public String getLastName() {
return this.lastName;
}
public Long getId() {
return this.id;
}
// setters omitted...
}
The following example uses a NamedParameterJdbcTemplate
to return the count of
the
members of the class shown in the preceding example:
// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
public int countOfActors(Actor exampleActor) {
// notice how the named parameters match the properties of the above 'Actor' class
String sql = "select count(*) from T_ACTOR where first_name = :firstName and last_name = :lastName";
SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor);
return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
}
Remember that the NamedParameterJdbcTemplate
class wraps a classic JdbcTemplate
template. If you need access to the wrapped JdbcTemplate
instance to access
functionality that is present only in the JdbcTemplate
class, you can use the
getJdbcOperations()
method to access the wrapped JdbcTemplate
through the
JdbcOperations
interface.
See also JdbcTemplate
Best Practices for guidelines on using the
NamedParameterJdbcTemplate
class in the context of an application.
3.3.3.
Using SQLExceptionTranslator
SQLExceptionTranslator
is an interface to be implemented by classes that can
translate
between SQLExceptions
and Spring’s own org.springframework.dao.DataAccessException
,
which is agnostic in regard to data access strategy. Implementations can be generic (for
example, using SQLState codes for JDBC) or proprietary (for example, using Oracle error
codes) for greater precision.
SQLErrorCodeSQLExceptionTranslator
is the implementation of SQLExceptionTranslator
that is used by default. This implementation uses specific vendor codes. It is more
precise than the SQLState
implementation. The error code translations are based
on
codes held in a JavaBean type class called SQLErrorCodes
. This class is created
and
populated by an SQLErrorCodesFactory
, which (as the name suggests) is a factory
for
creating SQLErrorCodes
based on the contents of a configuration file named
sql-error-codes.xml
. This file is populated with vendor codes and based on the
DatabaseProductName
taken from DatabaseMetaData
. The codes for the
actual
database you are using are used.
The SQLErrorCodeSQLExceptionTranslator
applies matching rules in the following
sequence:
-
Any custom translation implemented by a subclass. Normally, the provided concrete
SQLErrorCodeSQLExceptionTranslator
is used, so this rule does not apply. It applies only if you have actually provided a subclass implementation. -
Any custom implementation of the
SQLExceptionTranslator
interface that is provided as thecustomSqlExceptionTranslator
property of theSQLErrorCodes
class. -
The list of instances of the
CustomSQLErrorCodesTranslation
class (provided for thecustomTranslations
property of theSQLErrorCodes
class) are searched for a match. -
Error code matching is applied.
-
Use the fallback translator.
SQLExceptionSubclassTranslator
is the default fallback translator. If this translation is not available, the next fallback translator is theSQLStateSQLExceptionTranslator
.
The SQLErrorCodesFactory is used by default to define
Error codes and custom exception
translations. They are looked up in a file named sql-error-codes.xml
from the
classpath, and the matching SQLErrorCodes instance is located based on
the database
name from the database metadata of the database in use.
|
You can extend SQLErrorCodeSQLExceptionTranslator
, as the following example
shows:
public class CustomSQLErrorCodesTranslator extends SQLErrorCodeSQLExceptionTranslator {
protected DataAccessException customTranslate(String task, String sql, SQLException sqlex) {
if (sqlex.getErrorCode() == -12345) {
return new DeadlockLoserDataAccessException(task, sqlex);
}
return null;
}
}
In the preceding example, the specific error code (-12345
) is translated, while
other errors are
left to be translated by the default translator implementation. To use this custom
translator, you must pass it to the JdbcTemplate
through the method
setExceptionTranslator
, and you must use this JdbcTemplate
for all
of the data access
processing where this translator is needed. The following example shows how you can use this
custom
translator:
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
// create a JdbcTemplate and set data source
this.jdbcTemplate = new JdbcTemplate();
this.jdbcTemplate.setDataSource(dataSource);
// create a custom translator and set the DataSource for the default translation lookup
CustomSQLErrorCodesTranslator tr = new CustomSQLErrorCodesTranslator();
tr.setDataSource(dataSource);
this.jdbcTemplate.setExceptionTranslator(tr);
}
public void updateShippingCharge(long orderId, long pct) {
// use the prepared JdbcTemplate for this update
this.jdbcTemplate.update("update orders" +
" set shipping_charge = shipping_charge * ? / 100" +
" where id = ?", pct, orderId);
}
The custom translator is passed a data source in order to look up the error codes in
sql-error-codes.xml
.
3.3.4. Running Statements
Running an SQL statement requires very little code. You need a DataSource
and a
JdbcTemplate
, including the convenience methods that are provided with the
JdbcTemplate
. The following example shows what you need to include for a
minimal but
fully functional class that creates a new table:
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
public class ExecuteAStatement {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public void doExecute() {
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
}
}
3.3.5. Running Queries
Some query methods return a single value. To retrieve a count or a specific value from
one row, use queryForObject(..)
. The latter converts the returned JDBC Type
to the
Java class that is passed in as an argument. If the type conversion is invalid, an
InvalidDataAccessApiUsageException
is thrown. The following example contains
two
query methods, one for an int
and one that queries for a String
:
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
public class RunAQuery {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public int getCount() {
return this.jdbcTemplate.queryForObject("select count(*) from mytable", Integer.class);
}
public String getName() {
return this.jdbcTemplate.queryForObject("select name from mytable", String.class);
}
}
In addition to the single result query methods, several methods return a list with an
entry for each row that the query returned. The most generic method is queryForList(..)
,
which returns a List
where each element is a Map
containing one
entry for each column,
using the column name as the key. If you add a method to the preceding example to retrieve a
list of all the rows, it might be as follows:
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public List<Map<String, Object>> getList() {
return this.jdbcTemplate.queryForList("select * from mytable");
}
The returned list would resemble the following:
[{name=Bob, id=1}, {name=Mary, id=2}]
3.3.6. Updating the Database
The following example updates a column for a certain primary key:
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
public class ExecuteAnUpdate {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public void setName(int id, String name) {
this.jdbcTemplate.update("update mytable set name = ? where id = ?", name, id);
}
}
In the preceding example, an SQL statement has placeholders for row parameters. You can pass the parameter values in as varargs or ,alternatively, as an array of objects. Thus, you should explicitly wrap primitives in the primitive wrapper classes, or you should use auto-boxing.
3.3.7. Retrieving Auto-generated Keys
An update()
convenience method supports the retrieval of primary keys generated
by the
database. This support is part of the JDBC 3.0 standard. See Chapter 13.6 of the
specification for details. The method takes a PreparedStatementCreator
as its
first
argument, and this is the way the required insert statement is specified. The other
argument is a KeyHolder
, which contains the generated key on successful return
from the
update. There is no standard single way to create an appropriate
PreparedStatement
(which explains why the method signature is the way it is). The following example works
on Oracle but may not work on other platforms:
final String INSERT_SQL = "insert into my_test (name) values(?)";
final String name = "Rob";
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(
new PreparedStatementCreator() {
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
PreparedStatement ps = connection.prepareStatement(INSERT_SQL, new String[] {"id"});
ps.setString(1, name);
return ps;
}
},
keyHolder);
// keyHolder.getKey() now contains the generated key
3.4. Controlling Database Connections
This section covers:
3.4.1.
Using DataSource
Spring obtains a connection to the database through a DataSource
. A DataSource
is
part of the JDBC specification and is a generalized connection factory. It lets a
container or a framework hide connection pooling and transaction management issues
from the application code. As a developer, you need not know details about how to
connect to the database. That is the responsibility of the administrator who sets up
the datasource. You most likely fill both roles as you develop and test code, but you do
not necessarily have to know how the production data source is configured.
When you use Spring’s JDBC layer, you can obtain a data source from JNDI, or you can configure your own with a connection pool implementation provided by a third party. Popular implementations are Apache Jakarta Commons DBCP and C3P0. Implementations in the Spring distribution are meant only for testing purposes and do not provide pooling.
This section uses Spring’s DriverManagerDataSource
implementation, and several
additional implementations are covered later.
You should use the DriverManagerDataSource class only for testing
purposes,
since it does not provide pooling and performs poorly when multiple requests for a
connection are made.
|
To configure a DriverManagerDataSource
:
-
Obtain a connection with
DriverManagerDataSource
as you typically obtain a JDBC connection. -
Specify the fully qualified classname of the JDBC driver so that the
DriverManager
can load the driver class. -
Provide a URL that varies between JDBC drivers. (See the documentation for your driver for the correct value.)
-
Provide a username and a password to connect to the database.
The following example shows how to configure a DriverManagerDataSource
in Java:
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:hsql://localhost:");
dataSource.setUsername("sa");
dataSource.setPassword("");
The following example shows the corresponding XML configuration:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<context:property-placeholder location="jdbc.properties"/>
The next two examples show the basic connectivity and configuration for DBCP and C3P0. To learn about more options that help control the pooling features, see the product documentation for the respective connection pooling implementations.
The following example shows DBCP configuration:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<context:property-placeholder location="jdbc.properties"/>
The following example shows C3P0 configuration:
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${jdbc.driverClassName}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<context:property-placeholder location="jdbc.properties"/>
3.4.2.
Using DataSourceUtils
The DataSourceUtils
class is a convenient and powerful helper class that
provides
static
methods to obtain connections from JNDI and close connections if
necessary. It
supports thread-bound connections with, for example,
DataSourceTransactionManager
.
3.4.3.
Implementing SmartDataSource
The SmartDataSource
interface should be implemented by classes that can provide
a
connection to a relational database. It extends the DataSource
interface to let
classes that use it query whether the connection should be closed after a given
operation. This usage is efficient when you know that you need to reuse a connection.
3.4.4.
Extending AbstractDataSource
AbstractDataSource
is an abstract
base class for Spring’s DataSource
implementations. It implements code that is common to all DataSource
implementations.
You should extend the AbstractDataSource
class if you write your own DataSource
implementation.
3.4.5.
Using SingleConnectionDataSource
The SingleConnectionDataSource
class is an implementation of the SmartDataSource
interface that wraps a single Connection
that is not closed after each use.
This is not multi-threading capable.
If any client code calls close
on the assumption of a pooled connection (as when
using
persistence tools), you should set the suppressClose
property to
true
. This setting returns a
close-suppressing proxy that wraps the physical connection. Note that you can no longer
cast this to a native Oracle Connection
or a similar object.
SingleConnectionDataSource
is primarily a test class. For example, it enables
easy testing of code outside an
application server, in conjunction with a simple JNDI environment. In contrast to
DriverManagerDataSource
, it reuses the same connection all the time, avoiding
excessive creation of physical connections.
3.4.6.
Using DriverManagerDataSource
The DriverManagerDataSource
class is an implementation of the standard DataSource
interface that configures a plain JDBC driver through bean properties and returns a new
Connection
every time.
This implementation is useful for test and stand-alone environments outside of a Java EE
container, either as a DataSource
bean in a Spring IoC container or in
conjunction
with a simple JNDI environment. Pool-assuming Connection.close()
calls
close the connection, so any DataSource
-aware persistence code should work.
However,
using JavaBean-style connection pools (such as commons-dbcp
) is so easy, even
in a test
environment, that it is almost always preferable to use such a connection pool over
DriverManagerDataSource
.
3.4.7.
Using TransactionAwareDataSourceProxy
TransactionAwareDataSourceProxy
is a proxy for a target DataSource
.
The proxy wraps that
target DataSource
to add awareness of Spring-managed transactions. In this
respect, it
is similar to a transactional JNDI DataSource
, as provided by a Java EE server.
It is rarely desirable to use this class, except when already existing code must be
called and passed a standard JDBC DataSource interface implementation.
In this case,
you can still have this code be usable and, at the same time, have this code
participating in Spring managed transactions. It is generally preferable to write
your
own new code by using the higher level abstractions for resource management, such as
JdbcTemplate or DataSourceUtils .
|
See the TransactionAwareDataSourceProxy
javadoc for more details.
3.4.8.
Using DataSourceTransactionManager
The DataSourceTransactionManager
class is a
PlatformTransactionManager
implementation for single JDBC datasources. It binds a JDBC connection from the
specified data source to the currently executing thread, potentially allowing for one
thread connection per data source.
Application code is required to retrieve the JDBC connection through
DataSourceUtils.getConnection(DataSource)
instead of Java EE’s standard
DataSource.getConnection
. It throws unchecked
org.springframework.dao
exceptions
instead of checked SQLExceptions
. All framework classes (such as JdbcTemplate
)
use this
strategy implicitly. If not used with this transaction manager, the lookup strategy
behaves exactly like the common one. Thus, it can be used in any case.
The DataSourceTransactionManager
class supports custom isolation levels and
timeouts
that get applied as appropriate JDBC statement query timeouts. To support the latter,
application code must either use JdbcTemplate
or call the
DataSourceUtils.applyTransactionTimeout(..)
method for each created statement.
You can use this implementation instead of JtaTransactionManager
in the
single-resource
case, as it does not require the container to support JTA. Switching between
both is just a matter of configuration, provided you stick to the required connection lookup
pattern. JTA does not support custom isolation levels.
3.5. JDBC Batch Operations
Most JDBC drivers provide improved performance if you batch multiple calls to the same prepared statement. By grouping updates into batches, you limit the number of round trips to the database.
3.5.1.
Basic Batch Operations with JdbcTemplate
You accomplish JdbcTemplate
batch processing by implementing two methods of a
special
interface, BatchPreparedStatementSetter
, and passing that implementation in as
the second parameter
in your batchUpdate
method call. You can use the getBatchSize
method to provide the size of
the current batch. You can use the setValues
method to set the values for the
parameters of
the prepared statement. This method is called the number of times that you
specified in the getBatchSize
call. The following example updates the actor
table
based on entries in a list, and the entire list is used as the batch:
public class JdbcActorDao implements ActorDao {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public int[] batchUpdate(final List<Actor> actors) {
return this.jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
new BatchPreparedStatementSetter() {
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setString(1, actors.get(i).getFirstName());
ps.setString(2, actors.get(i).getLastName());
ps.setLong(3, actors.get(i).getId().longValue());
}
public int getBatchSize() {
return actors.size();
}
});
}
// ... additional methods
}
If you process a stream of updates or reading from a file, you might have a
preferred batch size, but the last batch might not have that number of entries. In this
case, you can use the InterruptibleBatchPreparedStatementSetter
interface,
which lets
you interrupt a batch once the input source is exhausted. The isBatchExhausted
method
lets you signal the end of the batch.
3.5.2. Batch Operations with a List of Objects
Both the JdbcTemplate
and the NamedParameterJdbcTemplate
provides
an alternate way
of providing the batch update. Instead of implementing a special batch interface, you
provide all parameter values in the call as a list. The framework loops over these
values and uses an internal prepared statement setter. The API varies, depending on
whether you use named parameters. For the named parameters, you provide an array of
SqlParameterSource
, one entry for each member of the batch. You can use the
SqlParameterSourceUtils.createBatch
convenience methods to create this array,
passing
in an array of bean-style objects (with getter methods corresponding to parameters),
String
-keyed Map
instances (containing the corresponding
parameters as values), or a mix of both.
The following example shows a batch update using named parameters:
public class JdbcActorDao implements ActorDao {
private NamedParameterTemplate namedParameterJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
public int[] batchUpdate(List<Actor> actors) {
return this.namedParameterJdbcTemplate.batchUpdate(
"update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
SqlParameterSourceUtils.createBatch(actors));
}
// ... additional methods
}
For an SQL statement that uses the classic ?
placeholders, you pass in a list
containing an object array with the update values. This object array must have one entry
for each placeholder in the SQL statement, and they must be in the same order as they are
defined in the SQL statement.
The following example is the same as the preceding example, except that it uses classic
JDBC ?
placeholders:
public class JdbcActorDao implements ActorDao {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public int[] batchUpdate(final List<Actor> actors) {
List<Object[]> batch = new ArrayList<Object[]>();
for (Actor actor : actors) {
Object[] values = new Object[] {
actor.getFirstName(), actor.getLastName(), actor.getId()};
batch.add(values);
}
return this.jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
batch);
}
// ... additional methods
}
All of the batch update methods that we described earlier return an int
array
containing the number of affected rows for each batch entry. This count is reported by
the JDBC driver. If the count is not available, the JDBC driver returns a value of
-2
.
|
In such a scenario, with automatic setting of values on an underlying Alternatively, you might consider specifying the corresponding JDBC types explicitly, either through a 'BatchPreparedStatementSetter' (as shown earlier), through an explicit type array given to a 'List<Object[]>' based call, through 'registerSqlType' calls on a custom 'MapSqlParameterSource' instance, or through a 'BeanPropertySqlParameterSource' that derives the SQL type from the Java-declared property type even for a null value. |
3.5.3. Batch Operations with Multiple Batches
The preceding example of a batch update deals with batches that are so large that you want to
break them up into several smaller batches. You can do this with the methods
mentioned earlier by making multiple calls to the batchUpdate
method, but there
is now a
more convenient method. This method takes, in addition to the SQL statement, a
Collection
of objects that contain the parameters, the number of updates to
make for each
batch, and a ParameterizedPreparedStatementSetter
to set the values for the
parameters
of the prepared statement. The framework loops over the provided values and breaks the
update calls into batches of the size specified.
The following example shows a batch update that uses a batch size of 100:
public class JdbcActorDao implements ActorDao {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public int[][] batchUpdate(final Collection<Actor> actors) {
int[][] updateCounts = jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
actors,
100,
new ParameterizedPreparedStatementSetter<Actor>() {
public void setValues(PreparedStatement ps, Actor argument) throws SQLException {
ps.setString(1, argument.getFirstName());
ps.setString(2, argument.getLastName());
ps.setLong(3, argument.getId().longValue());
}
});
return updateCounts;
}
// ... additional methods
}
The batch update methods for this call returns an array of int
arrays that
contain an array
entry for each batch with an array of the number of affected rows for each update. The top
level array’s length indicates the number of batches executed and the second level array’s
length indicates the number of updates in that batch. The number of updates in each batch
should be the batch size provided for all batches (except that the last one that might
be less), depending on the total number of update objects provided. The update count for
each update statement is the one reported by the JDBC driver. If the count is not
available, the JDBC driver returns a value of -2
.
3.6.
Simplifying JDBC Operations with the SimpleJdbc
Classes
The SimpleJdbcInsert
and SimpleJdbcCall
classes provide a simplified
configuration
by taking advantage of database metadata that can be retrieved through the JDBC driver.
This means that you have less to configure up front, although you can override or turn off
the metadata processing if you prefer to provide all the details in your code.
3.6.1.
Inserting Data by Using SimpleJdbcInsert
We start by looking at the SimpleJdbcInsert
class with the minimal amount of
configuration options. You should instantiate the SimpleJdbcInsert
in the data
access
layer’s initialization method. For this example, the initializing method is the
setDataSource
method. You do not need to subclass the
SimpleJdbcInsert
class. Instead,
you can create a new instance and set the table name by using the withTableName
method.
Configuration methods for this class follow the fluid
style that returns the
instance
of the SimpleJdbcInsert
, which lets you chain all configuration methods. The
following
example uses only one configuration method (we show examples of multiple methods later):
public class JdbcActorDao implements ActorDao {
private JdbcTemplate jdbcTemplate;
private SimpleJdbcInsert insertActor;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.insertActor = new SimpleJdbcInsert(dataSource).withTableName("t_actor");
}
public void add(Actor actor) {
Map<String, Object> parameters = new HashMap<String, Object>(3);
parameters.put("id", actor.getId());
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
insertActor.execute(parameters);
}
// ... additional methods
}
The execute
method used here takes a plain java.util.Map
as its
only parameter. The
important thing to note here is that the keys used for the Map
must match the
column
names of the table, as defined in the database. This is because we read the metadata
to construct the actual insert statement.
3.6.2.
Retrieving Auto-generated Keys by Using SimpleJdbcInsert
The next example uses the same insert as the preceding example, but, instead of passing in
the id
, it
retrieves the auto-generated key and sets it on the new Actor
object. When it
creates
the SimpleJdbcInsert
, in addition to specifying the table name, it specifies
the name
of the generated key column with the usingGeneratedKeyColumns
method. The
following
listing shows how it works:
public class JdbcActorDao implements ActorDao {
private JdbcTemplate jdbcTemplate;
private SimpleJdbcInsert insertActor;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id");
}
public void add(Actor actor) {
Map<String, Object> parameters = new HashMap<String, Object>(2);
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}
// ... additional methods
}