Hibernate Transactions, Concurrency and Caching

TRANSACTIONS,  CONCURRENCY AND CACHING

Tutorial from: http://www.amicabile.com/hybernate/hybernate-chapter5.html

Transactions allow multiple users to work concurrently with the
same data without compromising the integrity and correctness of the data

Atomicity, consistency, isolation,
and durability are together known as the ACID criteria.

In an online application, database transactions must have extremely short
lifespans. A database transaction should span a single batch of database operations,
interleaved with business logic.

UNDERSTANDING DATABASE TRANSACTION

Databases implement the notion of a unit of work as a database transaction.
A database transaction groups data-access operations. A transaction is guaranteed
to end in one of two ways: it’s either committed or rolled back.

JDBC and JTA transactions

You begin a transaction by calling setAutoCommit(false) on a JDBC connection
and end it by calling commit(). You may, at any time, force an immediate
rollback by calling rollback()

Hibernate automatically
disables auto commit mode as soon as it fetches a connection

In a system that stores data in multiple databases, you can’t achieve atomicity
using JDBC alone. JTA is also for declarative container managed transactions (CMT).

CMT allows you to avoid
explicit transaction demarcation calls in your application source code;

Hibernate provides its own abstraction layer
Transaction management is exposed to the application developer via the Hibernate
Transaction interface.

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;
}
}

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.

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”

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.

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.

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

FlushMode.AUTO
FlushMode.COMMIT :Specifies that the session won’t be flushed before query
execution

ISOLATION ISSUES

  • Lost update—Two transactions both update a row and then the second transaction
aborts, causing both changes to be lost.
  • Dirty read—One transaction reads changes made by another transaction that
hasn’t yet been committed.
  • Unrepeatable read—A transaction reads a row twice and reads different state
each time.
  • Second lost updates problem—A special case of an unrepeatable read. Imagine
that two concurrent transactions both read a row, one writes to it and commits,
and then the second writes to it and commits.
  • Phantom read—A transaction executes a query twice, and the second result
set includes rows that weren’t visible in the first result set.

ISOLATION LEVELS

  • Read uncommitted—Permits dirty reads but not lost updates.  exclusive write locks.
  • Read committed—Permits unrepeatable reads but not dirty reads. momentary shared read locks and exclusive write locks.
  • Repeatable read—Permits neither unrepeatable reads nor dirty reads.Phantom
reads may occur.  shared read locks and exclusive
write locks.
  • Serializable—Provides the strictest transaction isolation.

CHOOSING AN ISOLATION LEVEL

The combination of the (mandatory) Hibernate first-level session
cache and versioning already gives you most of the features of repeatable read
isolation.
Hibernate will then set this isolation level on every JDBC connection obtained from
a connection pool before starting a transaction.

USING PESSIMISTIC LOCKING

A pessimistic lock is a lock that is acquired when an item of data is read and that
is held until transaction completion.
The Hibernate LockMode class lets you request a pessimistic lock on a particular
item. In addition, you can use the LockMode to force Hibernate to bypass the cache
layer or to execute a simple version check.

Transaction tx = session.beginTransaction();
Category cat =
(Category) session.get(Category.class, catId, LockMode.UPGRADE);
cat.setName(“New Name”);
tx.commit();

With this mode, Hibernate will load the Category using a SELECT…FOR UPDATE,
thus locking the retrieved rows in the database until they’re released when the
transaction end

LockMode.NONE:  Don’t go to the database
LockMode.READ : Bypass both levels of the cache
LockMode.UPDGRADE:Bypass both levels of the cache, do a version check
, and obtain a database-level pessimistic upgrade lock
LockMode.UPDGRADE_NOWAIT : The same as UPGRADE, but use a SELECT…FOR
UPDATE NOWAIT on Oracle. his disables waiting for concurrent lock releases,
thus throwing a locking exception immediately if the lock can’t be obtained.
LockMode.WRITE : Is obtained automatically when Hibernate has written to
a row in the current transaction
Locking is a mechanism that prevents concurrent access to a particular item of data.

By specifying an explicit LockMode other than LockMode.NONE, you force Hibernate
to bypass both levels of the cache and go all the way to the database.
Our coarse-grained transactions will correspond to what the
user of the application considers a single unit of work.
The database isolates the effects of concurrent database transactions.
It should appear to the application that each transaction is the only
transaction currently
accessing the database

WORKING WITH APPLICATION TRANSACTIONS

You shouldn’t hold the database transaction  open while waiting for user input.
Business processes, which might be considered a single unit of work from the point
of view of the user, necessarily span multiple user client requests.

Last commit wins
First commit wins
Merge conflicting updates

Hibernate can help you implement the second and third
strategies, using managed versioning for optimistic locking.

USING MANAGED VERSIONING

Managed versioning relies on either a version number that is incremented or a
timestamp that is updated to the current time, every time an object is modified
modified.
Hibernate managed versioning, we must add a new property to our Comment class
and map it as a version number using the <version> tag.

The version number is just a counter value
<class table=”COMMENTS”>
<id …
<version name=”version” column=”VERSION”/>
…
</class>
Hibernate will increment the version number
whenever an object is dirty. This includes all dirty properties, whether
they’re single-valued or collections.

Some people prefer to use a timestamp instead

public class Comment {
…
private Date lastUpdatedDatetime;
…
void setLastUpdatedDatetime(Date lastUpdatedDatetime) {
this.lastUpdatedDatetime = lastUpdatedDatetime;
}
public Date getLastUpdatedDatetime() {
return lastUpdatedDatetime;
}
}

<class name=”Comment” table=”COMMENTS”>
<id …../>
<timestamp name=”lastUpdatedDatetime” column=”LAST_UPDATED”/>
…
</class>

update COMMENTS set COMMENT_TEXT=’New comment text’, VERSION=3
where COMMENT_ID=123 and VERSION=2

If another application transaction would have updated the same item since it was
read by the current application transaction, the VERSION column would not contain
the value 2, and the row would not be updated. Hibernate would check the row
count returned by the JDBC driver—which in this case would be the number of
rows updated, zero—and throw a StaleObjectStateException.

You can’t use an exclusive lock to block concurrent
access longer than a single database transaction.

TRANSACTION SUMMARY

Usually, you use read committed
isolation for database transactions, together with optimistic concurrency
control (version and timestamp checking) for long application transactions.
Hibernate greatly simplifies the implementation of application transactions
because it manages version numbers and timestamps for you.

GRANULARITY OF A SESSION

Usually, we open a new Session for each client
request (for example, a web browser request) and
begin a new Transaction. After executing the business
logic, we commit the database transaction and
close the Session, before sending the response to
the client

The session (S1) and the database transaction (T1) therefore have the same
granularity.

If you need a long-running application transaction, you might, thanks to
detached objects and Hibernate’s support for optimistic locking
implement it using the same approach

Suppose your application transaction spans two client request/response
cycles—for example, two HTTP requests in a web application. You could load the
interesting objects in a first Session and later reattach them to a new Session after
they’ve been modified by the user. Hibernate will automatically perform a version
check.

Alternatively, you might prefer to use a single Session that spans multiple
requests to implement your application transaction.

A Session is serializable and may be safely stored in the servlet HttpSession, for
example. The underlying JDBC connection has to be closed, of course, and a new
connection must be obtained on a subsequent request. You use the disconnect()
and reconnect() methods of the Session interface to release the connection and
later obtain a new connection. The longer the session remains open, the greater the chance that it holds stale data in its cache of persistent objects

<class name=”Comment” table=”COMMENT” optimistic-lock=”all”>
<id …../>
…
</class>

Now, Hibernate will include all properties in the WHERE clause:

Hibernate will include only the modified properties  if you set optimistic-lock=”dirty”

it’s slower, more complex, and less reliable
than version numbers and doesn’t work if your application transaction spans multiple
sessions

CACHING THEORY AND PRACTICE

The cache may be used to avoid a database hit whenever
¦ The application performs a lookup by identifier (primary key)
¦ The persistence layer resolves an association lazily

Transaction scope+
Process scope
Cluster scope

CACHING AND OBJECT IDENTITY

Any ORM implementation that allows multiple units of work to share the same persistent
instances must provide some form of object-level locking to ensure synchronization
of concurrent access. Usually this is implemented using read and write
locks (held in memory) together with deadlock detection.

You shouldn’t use any kind of cache beyond a transaction
scope cache for legacy data

USING THE FIRST LEVEL CACHE

The first-level cache is
always active—it’s used to resolve circular references in your object graph and to
optimize performance in a single unit of work.

The session cache ensures that when the application requests the same persistent
object twice in a particular session, it gets back the same Java instance.

Changes made in a particular unit of work are always immediately visible to
all other code executed inside that unit of work.
Whenever you pass an object to save(), update(), or saveOrUpdate(), and whenever
you retrieve an object using load(), find(), list(), iterate(), or filter(),
that object is added to the session cache. When flush() is subsequently called, the
state of that object will be synchronized with the database.
you can use the
evict() method of the Session to remove the object and its collections from the
first-level cache.

USING SECOND LEVEL CACHE

The (process or cluster scope) second-
level cache on the other hand is optional and works best for read-mostly candidate
classes.

If you have data that is updated more often than it’s read, don’t
enable the second-level cache, even if all other conditions for caching are true!
A concurrency strategy is a mediator; it’s responsible for storing items of data in
the cache and retrieving them from the cache.
It’s possible to define your own concurrency strategy by implementing
net.sf.hibernate.cache.CacheConcurrencyStrategy,

transactional—Available in a managed environment only. It guarantees full
transactional isolation up to repeatable read, if required.
read-write—Maintains read committed isolation, using a timestamping mechanism.
nonstrict-read-write—Makes no guarantee of consistency between the cache
and the database.
read-only—A concurrency strategy suitable for data which never changes.

You might be better off disabling
the second-level cache for a particular class if stale data isn’t an option.

CHOOSING A CACHE PROVIDER

Hibernate forces you to choose a single cache provider for the whole
application.

EHCache
OpenSymphony OSCache
SwarmCache s.
JBossCache

CACHING IN PRACTICE

<class
name=”Category”
table=”CATEGORY”>
<cache usage=”read-write”/>
<id ….
</class>

<class
name=”Category”
table=”CATEGORY”>
<cache usage=”read-write”/>
<id ….
<set lazy=”true”>
<cache usage=”read-write”/>
<key ….
</set>
</class>

A collection cache holds only the identifiers of the associated item
instances. So, if we require the instances themselves to be cached, we must enable
caching of the Item class.

<class
name=”Item”
table=”ITEM”>
<cache usage=”read-write”/>
<id ….
<set lazy=”true”>
<cache usage=”read-write”/>
<key ….
</set>
</class>

<class
name=”Bid”
table=”BID”>
<cache usage=”read-only”/>
<id ….
</class>

Cached Bid data is valid indefinitely, because bids are never updated. No cache
invalidation is required.

Hibernate keeps different classes/collections in different cache regions. A region
is a named cache: a handle by which you can reference classes and collections in
the cache provider configuration and set the expiration policies applicable to
that region.

The name of the region is the class name, in the case of a class cache; or the class
name together with the property name, in the case of a collection cache. Category
instances are cached in a region named org.hibernate.auction.Category, and the
items collection is cached in a region named org.hibernate.auction.Category.
items.

SETTING UP A LOCAL CACHE PROVIDER

hibernate.cache.provider_class=net.sf.ehcache.hibernate.Provider
regions.
EHCache has its own configuration file, ehcache.xml, in the classpath of the application.

<cache name=”org.hibernate.auction.model.Category”
maxElementsInMemory=”500″
eternal=”true”
timeToIdleSeconds=”0″
timeToLiveSeconds=”0″
overflowToDisk=”false”
/>

We therefore disable eviction by timeout by choosing
a cache size limit greater than the number of categories in our system and setting
eternal=”true”.

<cache name=”org.hibernate.auction.model.Bid”
maxElementsInMemory=”5000″
eternal=”false”
timeToIdleSeconds=”1800″
timeToLiveSeconds=”100000″
overflowToDisk=”false”
/>

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s