Thư viện tri thức trực tuyến
Kho tài liệu với 50,000+ tài liệu học thuật
© 2023 Siêu thị PDF - Kho tài liệu học thuật hàng đầu Việt Nam

manning Hibernate in Action phần 6 potx
Nội dung xem thử
Mô tả chi tiết
Licensed to Jose Carlos Romero Figueroa <[email protected]>
158 CHAPTER 5
Transactions, concurrency, and caching
We aren’t interested in the details of direct JDBC or JTA transaction demarcation. You’ll be using these APIs only indirectly.
Hibernate communicates with the database via a JDBC Connection; hence it must
support both APIs. In a stand-alone (or web-based) application, only the JDBC
transaction handling is available; in an application server, Hibernate can use JTA.
Since we would like Hibernate application code to look the same in both managed
and non-managed environments, Hibernate provides its own abstraction layer, hiding the underlying transaction API. Hibernate allows user extension, so you could
even plug in an adaptor for the CORBA transaction service.
Transaction management is exposed to the application developer via the Hibernate Transaction interface. You aren’t forced to use this API—Hibernate lets you
control JTA or JDBC transactions directly, but this usage is discouraged, and we
won’t discuss this option.
5.1.2 The Hibernate Transaction API
The Transaction interface provides methods for declaring the boundaries of a database transaction. See listing 5.1 for an example of the basic usage of Transaction.
Listing 5.1 Using the Hibernate Transaction API
Session session = sessions.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
concludeAuction();
tx.commit();
} catch (Exception e) {
if (tx != null) {
try {
tx.rollback();
} catch (HibernateException he) {
//log he and rethrow e
}
}
throw e;
} finally {
try {
session.close();
} catch (HibernateException he) {
throw he;
}
}
Licensed to Jose Carlos Romero Figueroa <[email protected]>
Understanding database transactions 159
The call to session.beginTransaction() marks the beginning of a database transaction. In the case of a non-managed environment, this starts a JDBC transaction
on the JDBC connection. In the case of a managed environment, it starts a new JTA
transaction if there is no current JTA transaction, or joins the existing current JTA
transaction. This is all handled by Hibernate—you shouldn’t need to care about
the implementation.
The call to tx.commit()synchronizes the Session state with the database. Hibernate then commits the underlying transaction if and only if beginTransaction()
started a new transaction (in both managed and non-managed cases). If beginTransaction() did not start an underlying database transaction, commit() only synchronizes the Session state with the database; it’s left to the responsible party (the
code that started the transaction in the first place) to end the transaction. This is
consistent with the behavior defined by JTA.
If concludeAuction() threw an exception, we must force the transaction to roll
back by calling tx.rollback(). This method either rolls back the transaction
immediately or marks the transaction for “rollback only” (if you’re using CMTs).
FAQ Is it faster to roll back read-only transactions? If code in a transaction reads
data but doesn’t modify it, should you roll back the transaction instead of
committing it? Would this be faster?
Apparently some developers found this approach to be faster in some
special circumstances, and this belief has now spread through the community. We tested this with the more popular database systems and
found no difference. We also failed to discover any source of real numbers showing a performance difference. There is also no reason why a
database system should be implemented suboptimally—that is, why it
shouldn’t use the fastest transaction cleanup algorithm internally. Always
commit your transaction and roll back if the commit fails.
It’s critically important to close the Session in a finally block in order to ensure that
the JDBC connection is released and returned to the connection pool. (This step
is the responsibility of the application, even in a managed environment.)
NOTE The example in listing 5.1 is the standard idiom for a Hibernate unit of
work; therefore, it includes all exception-handling code for the checked
HibernateException. As you can see, even rolling back a Transaction
and closing the Session can throw an exception. You don’t want to use this
example as a template in your own application, since you’d rather hide the
exception handling with generic infrastructure code. You can, for example, use a utility class to convert the HibernateException to an unchecked
runtime exception and hide the details of rolling back a transaction and
Licensed to Jose Carlos Romero Figueroa <[email protected]>
160 CHAPTER 5
Transactions, concurrency, and caching
closing the session. We discuss this question of application design in more
detail in chapter 8, section 8.1, “Designing layered applications.”
However, there is one important aspect you must be aware of: the Session has to be immediately closed and discarded (not reused) when an
exception occurs. Hibernate can’t retry failed transactions. This is no
problem in practice, because database exceptions are usually fatal (constraint violations, for example) and there is no well-defined state to continue after a failed transaction. An application in production shouldn’t
throw any database exceptions either.
We’ve noted that the call to commit() synchronizes the Session state with the database. This is called flushing, a process you automatically trigger when you use the
Hibernate Transaction API.
5.1.3 Flushing the Session
The Hibernate Session implements transparent write behind. Changes to the domain
model made in the scope of a Session aren’t immediately propagated to the database. This allows Hibernate to coalesce many changes into a minimal number of
database requests, helping minimize the impact of network latency.
For example, if a single property of an object is changed twice in the same
Transaction, Hibernate only needs to execute one SQL UPDATE. Another example of the usefulness of transparent write behind is that Hibernate can take
advantage of the JDBC batch API when executing multiple UPDATE, INSERT, or
DELETE statements.
Hibernate flushes occur only at the following times:
■ When a Transaction is committed
■ Sometimes before a query is executed
■ When the application calls Session.flush() explicitly
Flushing the Session state to the database at the end of a database transaction is
required in order to make the changes durable and is the common case. Hibernate
doesn’t flush before every query. However, if there are changes held in memory that
would affect the results of the query, Hibernate will, by default, synchronize first.
You can control this behavior by explicitly setting the Hibernate FlushMode via a
call to session.setFlushMode(). The flush modes are as follows:
■ FlushMode.AUTO—The default. Enables the behavior just described.
■ FlushMode.COMMIT—Specifies that the session won’t be flushed before query
execution (it will be flushed only at the end of the database transaction). Be
Licensed to Jose Carlos Romero Figueroa <[email protected]>
Understanding database transactions 161
aware that this setting may expose you to stale data: modifications you made
to objects only in memory may conflict with the results of the query.
■ FlushMode.NEVER—Lets you specify that only explicit calls to flush() result
in synchronization of session state with the database.
We don’t recommend that you change this setting from the default. It’s provided
to allow performance optimization in rare cases. Likewise, most applications rarely
need to call flush() explicitly. This functionality is useful when you’re working
with triggers, mixing Hibernate with direct JDBC, or working with buggy JDBC drivers. You should be aware of the option but not necessarily look out for use cases.
Now that you understand the basic usage of database transactions with the
Hibernate Transaction interface, let’s turn our attention more closely to the subject of concurrent data access.
It seems as though you shouldn’t have to care about transaction isolation—the
term implies that something either is or is not isolated. This is misleading. Complete
isolation of concurrent transactions is extremely expensive in terms of application
scalability, so databases provide several degrees of isolation. For most applications,
incomplete transaction isolation is acceptable. It’s important to understand the
degree of isolation you should choose for an application that uses Hibernate and
how Hibernate integrates with the transaction capabilities of the database.
5.1.4 Understanding isolation levels
Databases (and other transactional systems) attempt to ensure transaction isolation,
meaning that, from the point of view of each concurrent transaction, it appears
that no other transactions are in progress.
Traditionally, this has been implemented using locking. A transaction may place
a lock on a particular item of data, temporarily preventing access to that item by
other transactions. Some modern databases such as Oracle and PostgreSQL implement transaction isolation using multiversion concurrency control, which is generally
considered more scalable. We’ll discuss isolation assuming a locking model (most
of our observations are also applicable to multiversion concurrency).
This discussion is about database transactions and the isolation level provided
by the database. Hibernate doesn’t add additional semantics; it uses whatever is
available with a given database. If you consider the many years of experience that
database vendors have had with implementing concurrency control, you’ll clearly
see the advantage of this approach. Your part, as a Hibernate application developer, is to understand the capabilities of your database and how to change the database isolation behavior if needed in your particular scenario (and by your data
integrity requirements).