This message is a summary based on a very good article
(http://www.ibm.com/developerworks/java/library/j-ts1.html?S_TACT=105AGX02&S_CMP=EDU)
and focusing on common mistakes when implementing transactions in the
Java platform. The original article is aimed to discuss Transaction
Strategy. Here, we just want to discuss the common mistakes on
transaction management.
Why we need Transaction?
The most common reason for using transactions in an application is to
maintain a high degree of data integrity and consistency. Transactions
improve the quality, integrity, and consistency of your data and make
your applications more robust. Implementation of successful transaction
processing in Java applications is not a trivial exercise, and it's
about design as much as about coding.
Although most of this article's code examples use the Spring Framework
(version 2.5), the transaction concepts are the same as for the EJB 3.0
specification.
O/R Mapping Transaction pitfalls
Pitfall1: ORM-based frameworks (such as
hibernate, TopLink or JPA) require a transaction in order to trigger
the synchronization between the object cache and the
database. It is through a
transaction commit that the SQL code is generated
and the database affected by the desired action (that is, insert,
update, delete). Without a transaction there is no trigger for the ORM
to generate SQL code and persist the changes, so the method simply ends
— no exceptions, no updates. If you are using an ORM-based framework,
you must use transactions. You can no longer rely on the database to
manage the connections and commit the work.
public class TradingServiceImpl {
@PersistenceContext(unitName="trading") EntityManager em;
public long insertTrade(TradeData trade) throws Exception {
em.persist(trade);
return trade.getTradeId();
}
}
Solution:: the method insertTrade must have
transactional setting, such as @Transactional annotation, or codes, or
AOP-like configurations.
Spring Framework @Transactional annotation pitfalls
Pitfall2: When using the @Transactional
annotation in Spring, you must add the following line to your Spring
configuration file:
<tx:annotation-driven transaction-manager="transactionManager">
</tx:annotation-driven>
Without it, the @Transactional annotation is ignored, resulting in no transaction being used in your code.
NOTE: when using the @Transactional annotation by itself without any
parameters, the propagation mode is set to REQUIRED, the read-only flag
is set to false, the transaction isolation level is set to the database
default (usually READ_COMMITTED), and the transaction will not roll
back on a checked exception.
@Transactional read-only flag pitfalls
Pitfall3: the improper use of the read-only flag on the Spring @Transactional annotation.
3.1: The read-only flag is somewhat
meaningless when you use it for JDBC-based Java persistence and causes
additional overhead when an unnecessary transaction is
started.
3.2: when you use an ORM-based framework,
the read-only flag is quite useless and in most cases is ignored.
Example 1:
@Transactional(readOnly = true, propagation=Propagation.SUPPORTS)
public Long insertTrade(TradeData trade) throws Exception {
//JDBC Code
}
When the insertTrade() method executes, does it:
-
Throw a read-only connection exception
- Correctly insert the trade order and commit the data
- Do nothing because the propagation level is set to SUPPORTS
The correct answer is 2. The trade order is correctly inserted into the
database, even though the read-only flag is set to true and the
transaction propagation set to SUPPORTS. But how can that be? No
transaction is started because of the SUPPORTS propagation mode, so the
method effectively uses a local (database) transaction. The read-only
flag is applied only if a transaction is started. In this case, no
transaction was started, so the read-only flag is ignored.
Example 2:
@Transactional(readOnly = true, propagation=Propagation.REQUIRED)
public long insertTrade(TradeData trade) throws Exception {
//JDBC code
}
When executed, does the insertTrade() method:
- Throw a read-only connection exception
- Correctly insert the trade order and commit the data
- Do nothing because the read-only flag is set to true
The correct answer is 2.
An exception will be thrown, indicating that you are trying to perform
an update operation on a read-only connection. Because a transaction is
started (REQUIRED), the connection is set to read-only. Sure enough,
when you try to execute the SQL statement, you get an exception telling
you that the connection is a read-only connection.
So, the summary is
the read-only flag is that you need to
start a transaction in order to use it.. However, why would
you need a transaction if you are only reading data? The answer is that
you don't.
Example3:
@Transactional(readOnly = true, propagation=Propagation.REQUIRED)
public long insertTrade(TradeData trade) throws Exception {
em.persist(trade);
return trade.getTradeId();
}
Does the insertTrade() method:
- Throw a read-only connection exception
- Correctly insert the trade order and commit the data
- Do nothing because the readOnly flag is set to true
In some cases the answer is 3, but in most cases (particularly when using JPA) the answer is 2.
When you are generating a key on an insert, the ORM framework will go
to the database to obtain the key and subsequently perform the insert.
For some vendors, such as Hibernate, the flush mode will be set to
MANUAL, and no insert will occur for inserts with non-generated keys.
However, other vendors, like TopLink, will always perform inserts and
updates when the read-only flag is set to true. Although this is both
vendor and version specific, the point here is that you cannot be
guaranteed that the insert or update will not occur when the read-only
flag is set, particularly when using JPA as it is vendor-agnostic.
Example4:
@Transactional(readOnly = true)
public TradeData getTrade(long tradeId) throws Exception {
return em.find(TradeData.class, tradeId);
}
Does the getTrade() method:
- Start a transaction, get the trade order, then commit the transaction
- Get the trade order without starting a transaction
The correct answer here is 1. A transaction is started and committed.
Don't forget: the default propagation mode for the @Transactional
annotation is REQUIRED. This means that a transaction is started when
in fact one is not required.
REQUIRES_NEW transaction attribute pitfalls
The REQUIRES_NEW transaction attribute always starts a new transaction
when the method is started, whether or not an existing transaction is
present.
Pitfall 4:When you use the REQUIRES_NEW
transaction attribute, if an existing transaction context is present,
the current transaction is suspended and a new transaction started.
Once that method ends, the new transaction commits and the original
transaction resumes.
The main point here is always to use
either the MANDATORY or REQUIRED attribute instead of REQUIRES_NEW
unless you have a reason to use it.
Transaction rollback pitfalls
Pitfall 5:Run-time exceptions (that is,
unchecked exceptions) automatically force the entire logical unit of
work to roll back, but checked exceptions do not.
Solution:
- In the Spring Framework you specify this through the rollbackFor parameter in the @Transactional annotation
- The @TransactionAttribute annotation found in the EJB 3.0
specification does not include directives to specify the rollback
behavior. Rather, you must use the SessionContext.setRollbackOnly()
method to mark the transaction for rollback.
Read-Only Transaction Pitfall
At certain times you may want to start a transaction for a database
read operation for example, when isolating your read operations for
consistency or setting a specific transaction isolation level for the
read operation. However, these situations are rare in business
applications, and
Pitfall 6: unless you're
faced with one, you should avoid starting a transaction for database
read operations, as they are unnecessary and can lead to database
deadlocks, poor performance, and poor throughput.
However, most of framework enables the default transaction support for all methods.
Summary
Many pitfalls are associated with implementing transaction support in
the Java platform (including some less common ones that I haven't
discussed here). The biggest issue with most of them is that no
compiler warnings or run-time errors tell you that the transaction
implementation is incorrect.
posted on 2009-04-15 15:12
Justin Chen 阅读(2848)
评论(0) 编辑 收藏 所属分类:
Java Common