Spring 3 transaction management
转自:
In this article I will show you how to
handle transaction management with Spring.
Great! Let's begin with some basic information you may want to skip if you already know about transactions.
Transactions ensure that the data in your application (in the data source) stays consistent. You should also be familiar with the ACIDconcept.
Now, in Java you can handle transactions with plain SQL, with plain JDBC (a bit higher level), using Hibernate (or any other ORM library), or on an even higher level - with EJB or, finally, Spring!
EJBs require an application server, but our application presented here - doesn't.
Spring offers two ways of handling transactions: programmatic and declarative. If you are familiar with EJB transaction handling, this corresponds to bean-managed and container-managed transaction management.
Programmatic means you have transaction management code surrounding your business code. That gives you extreme flexibility, but is difficult to maintain and, well, boilerplate.
Declarative means you separate transaction management from the business code. You only use annotations or XML based configuration.
We say that:
We will focus on declarative management.
OK, a small description of what we want to achieve and how.
The goals:
The means:
We are going to build on the project I used in my previous post on Spring & JPA 2.0.
We must first specify that we want to use annotation driven transaction management.
And, of course, the transaction manager itself:
What we are using here is the JpaTransactionManager. There are more managers available:
It references a bean called entityManagerFactory. Here it is:
@Transactional is the annotation you can use on any method of any bean and on the bean itself.
If you apply the annotation on the bean (i.e. on the class level), every public method will be transactional.
Note: Remember that it affects only Spring managed data sources (in our example whatever comes from the entity manager factory). If you get your data source outside of Spring, the @Transactional annotation will have no effect.
You should put this annotation on your business logic methods (service methods), not on DAO methods (unless you know what you're doing) - that's a rule of thumb. Normally a business method will call many DAO methods and those calls make only sense when made together, every single one or none (atomicity).
Whenever a transactional method is called, a decision is made - what to do with the transaction. Create a new one? Use an existing one if it exists, otherwise create a new one? Use an existing one only if it exists, otherwise fail?
To make it possible for you to specify this, propagation behaviors exist.
See the behaviors:
It is up to you to decide which behavior is best for each method. REQUIRED is the default one.
Note: If you are using the XML setup for transactions, you can also configure these behaviors there instead of Java code.
Concurrent transactions cause problems that might be difficult to investigate.
Now, the perfect solution to these problems is maximum isolation, but in reality this would cost too much resources and could lead to deadlocks. So, instead, you will set one of five isolation levels (where the fifth one is actually the maximum isolation level):
The default choice is DEFAULT.
HSQLDB 2.0 supports READ_COMMITTED and SERIALIZABLE levels ( HSQLDB FAQ).
With Spring transaction management the default behavior for automatic rollback is this: Only unchecked exceptions cause a rollback. Unchecked exceptions are RuntimeExceptions and Errors.
Think about it for a second! Are you sure you understand what it does?
That's the default behavior. However, you may want to change it for certain cases:
Here I asked the transaction manager to rollback for IOExceptions and not to rollback for RuntimeExceptions.
Note: If you are using the XML setup for transactions, you can also configure these rules there instead of Java code.
You've seen it before.
That's all!
You have to use the classic AOP approach. org.springframework.transaction.interceptor.TransactionProxyFactoryBean might be your starting point.
You may want to go for something like this:
That's it! Although the transactions themselves are by no means a trivial topic, Spring makes at least handling them easy.
I got my knowledge mostly from this book:
Spring Recipes: A Problem-Solution Approach
It helps me run this website if you buy this book through this link, thanks!
A brief intro to HSQLDB and how to use it in this app (Note that the project in that article is NOT a Maven project, but an Eclipse JPA one, so the project creating part doesn't matter)?
Read on: Remove JpaTemplate in favor of @PersistenceContext.
Download the source code for this article
Benefits of Spring Transaction Management
- Very easy to use, does not require any underlying transaction API knowledge
- Your transaction management code will be independent of the transaction technology
- Both annotation- and XML-based configuration
- It does not require to run on a server - no server needed
Great! Let's begin with some basic information you may want to skip if you already know about transactions.
Overview
Transactions ensure that the data in your application (in the data source) stays consistent. You should also be familiar with the ACIDconcept.
Now, in Java you can handle transactions with plain SQL, with plain JDBC (a bit higher level), using Hibernate (or any other ORM library), or on an even higher level - with EJB or, finally, Spring!
EJBs require an application server, but our application presented here - doesn't.
Programmatic vs. Declarative
Spring offers two ways of handling transactions: programmatic and declarative. If you are familiar with EJB transaction handling, this corresponds to bean-managed and container-managed transaction management.
Programmatic means you have transaction management code surrounding your business code. That gives you extreme flexibility, but is difficult to maintain and, well, boilerplate.
Declarative means you separate transaction management from the business code. You only use annotations or XML based configuration.
We say that:
- programmatic management is more flexible during development time but less flexible during application life
- declarative management is less flexible during development time but more flexible during application life
In this article...
We will focus on declarative management.
OK, a small description of what we want to achieve and how.
The goals:
- A desktop application (i.e. it doesn't need a server)
- Declarative transaction management with minimum XML
The means:
- A Maven project created in Eclipse and making use of SpringIDE
- Spring transaction management
- Java with annotations enabled (I'm going to use Java 6)
- JPA 2.0
We are going to build on the project I used in my previous post on Spring & JPA 2.0.
Transaction Manager
We must first specify that we want to use annotation driven transaction management.
1
|
<
tx:annotation-driven
transaction-manager
=
"myTransactionManager"
/>
|
And, of course, the transaction manager itself:
1
2
3
|
<
bean
id
=
"myTransactionManager"
class
=
"org.springframework.orm.jpa.JpaTransactionManager"
>
<
property
name
=
"entityManagerFactory"
ref
=
"entityManagerFactory"
/>
</
bean
>
|
What we are using here is the JpaTransactionManager. There are more managers available:
- DataSourceTransactionManager - for single data source and JDBC
- JtaTransactionManager - for JTA
- HibernateTransactionManager - for Hibernate
It references a bean called entityManagerFactory. Here it is:
1
2
3
4
|
<
bean
id
=
"entityManagerFactory"
class
=
"org.springframework.orm.jpa.LocalEntityManagerFactoryBean"
>
<
property
name
=
"persistenceUnitName"
value
=
"Dogs"
/>
</
bean
>
|
Annotation based
@Transactional is the annotation you can use on any method of any bean and on the bean itself.
1
2
3
4
5
|
import
org.springframework.transaction.annotation.Transactional;
// ...
@Transactional
public
void
doSomething(...
// ...
|
If you apply the annotation on the bean (i.e. on the class level), every public method will be transactional.
Note: Remember that it affects only Spring managed data sources (in our example whatever comes from the entity manager factory). If you get your data source outside of Spring, the @Transactional annotation will have no effect.
Where to put @Transactional
You should put this annotation on your business logic methods (service methods), not on DAO methods (unless you know what you're doing) - that's a rule of thumb. Normally a business method will call many DAO methods and those calls make only sense when made together, every single one or none (atomicity).
Transaction Propagation
Whenever a transactional method is called, a decision is made - what to do with the transaction. Create a new one? Use an existing one if it exists, otherwise create a new one? Use an existing one only if it exists, otherwise fail?
To make it possible for you to specify this, propagation behaviors exist.
1
2
3
4
5
6
|
import
org.springframework.transaction.annotation.Propagation;
import
org.springframework.transaction.annotation.Transactional;
// ...
@Transactional
(propagation = Propagation.REQUIRED)
public
void
doSomething(...
// ...
|
See the behaviors:
- REQUIRED - uses the existing transaction. If it doesn't exist, creates a new one
- REQUIRES_NEW - must start a new transaction. If there's an existing one, it should be suspended
- SUPPORTS - if there is a transaction, runs in it. If there isn't, it just runs outside transaction context
- NOT_SUPPORTED - must not run in a transaction. If there's an existing one, it should be suspended
- MANDATORY - must run in a transaction. If there isn't one, it throws an exception
- NEVER - must not run in a transaction. If there's an existing one, an exception will be thrown
- NESTED - if there is a transaction, it runs withing the nested transaction of it. Otherwise it runs in its own
It is up to you to decide which behavior is best for each method. REQUIRED is the default one.
Note: If you are using the XML setup for transactions, you can also configure these behaviors there instead of Java code.
Transaction Isolation
Concurrent transactions cause problems that might be difficult to investigate.
- Lost update - two transactions both update a row, the second transaction aborts, both changes are lost
- Dirty read - reading changes that are not yet committed
- Unrepeatable read - a transactions read twice the same row, getting different data each time
- Phantom read - similar to the previous one, except that the number of rows changed
Now, the perfect solution to these problems is maximum isolation, but in reality this would cost too much resources and could lead to deadlocks. So, instead, you will set one of five isolation levels (where the fifth one is actually the maximum isolation level):
- DEFAULT - uses the default database isolation level
- READ_UNCOMMITTED - dirty read, unrepeatable read, phantom read problems may occur, but not lost update
- READ_COMMITTED - unrepeatable read, phantom read problems my occur
- REPEATABLE_READ - phantom read problems my occur
- SERIALIZABLE - all problems avoided! But performance is low
The default choice is DEFAULT.
HSQLDB 2.0 supports READ_COMMITTED and SERIALIZABLE levels ( HSQLDB FAQ).
... and roll...back! Transaction Rollback
With Spring transaction management the default behavior for automatic rollback is this: Only unchecked exceptions cause a rollback. Unchecked exceptions are RuntimeExceptions and Errors.
1
2
3
4
5
|
import
org.springframework.transaction.annotation.Transactional;
// ...
@Transactional
public
void
doSomething(...
// ...
|
- If the doSomething method terminates its execution naturally, with no exceptions, the transaction is committed
- If the doSomething method or any other method it callsthrows any kind of exception that is caught within doSomething or this other method and not rethrown, the transaction is committed
- If the doSomething method or any other method it callsthrows any kind of checked exception that is notcaught or is caught and rethrown, the transaction iscommitted (so everything up to the moment of the exception being thrown is persisted)
- If the doSomething method or any other method it callsthrows any kind of unchecked exception that is notcaught, the transaction is rolled back (so nothing saved in this transaction is persisted)
That's the default behavior. However, you may want to change it for certain cases:
1
2
|
@Transactional
(rollbackFor = IOException.
class
, noRollbackFor = RuntimeException.
class
)
public
void
doSomething(...
|
Note: If you are using the XML setup for transactions, you can also configure these rules there instead of Java code.
Our sample application
Spring beans file
You've seen it before.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
xsi:schemaLocation="http://www.springframework.org/schema/beans
...
<
tx:annotation-driven
transaction-manager
=
"myTransactionManager"
/>
<
bean
id
=
"myTransactionManager"
class
=
"org.springframework.orm.jpa.JpaTransactionManager"
>
<
property
name
=
"entityManagerFactory"
ref
=
"entityManagerFactory"
/>
</
bean
>
<
bean
id
=
"entityManagerFactory"
class
=
"org.springframework.orm.jpa.LocalEntityManagerFactoryBean"
>
<
property
name
=
"persistenceUnitName"
value
=
"Dogs"
/>
</
bean
>
...
|
Using transactions
1
2
3
4
5
|
import
org.springframework.transaction.annotation.Transactional;
// ...
@Transactional
public
void
doSomething(...
// ...
|
That's all!
What if you...
What if you are using Spring 1.x?
You have to use the classic AOP approach. org.springframework.transaction.interceptor.TransactionProxyFactoryBean might be your starting point.
What if you want to declare transactions in XML and not in the code?
You may want to go for something like this:
1
2
3
4
5
6
7
8
9
10
11
|
<
tx:advice
id
=
"dogsTxAdvice"
transaction-manager
=
"myTransactionManager"
>
<
tx:attributes
>
<
tx:method
name
=
"persistDog"
/>
</
tx:attributes
>
</
tx:advice
>
<
aop:config
>
<
aop:pointcut
id
=
"dogsOperation"
expression
=
"execution(* me.m1key.springtx.beans.DogsBean.*(..))"
/>
<
aop:advisor
advice-ref
=
"dogsTxAdvice"
pointcut-ref
=
"dogsOperation"
/>
</
aop:config
>
|
Summary
That's it! Although the transactions themselves are by no means a trivial topic, Spring makes at least handling them easy.
I got my knowledge mostly from this book:
Spring Recipes: A Problem-Solution Approach
It helps me run this website if you buy this book through this link, thanks!
A brief intro to HSQLDB and how to use it in this app (Note that the project in that article is NOT a Maven project, but an Eclipse JPA one, so the project creating part doesn't matter)?
Read on: Remove JpaTemplate in favor of @PersistenceContext.
Download the source code for this article