Isolating Access to ResourcesEarly in this chapter you saw that a characteristic of a transaction is that it be isolated from the effects of other transactions. Isolation isn't as rigid a requirement as the other ACID properties so you have some leeway in how it's enforced in your applications. To reflect this, resource managers support one or more isolation levels. An isolation level describes the extent to which access to a single resource by concurrent transactions is separated. The need to isolate transactions is best understood when you consider the interactions that can occur between concurrent transactions when they aren't kept apart. These interactions are called isolation conditions and they consist of dirty reads, nonrepeatable reads, and phantom reads. A dirty read occurs when a transaction sees uncommitted changes introduced into the system by another transaction. If a transaction reads uncommitted data that is eventually rolled back, it has an invalid view of the system's state. A nonrepeatable read occurs when a transaction reads some particular data (typically a row from a database table) more than once and does not get the same values every time. This is the result of a transaction being allowed to change data that has been read by another transaction that has not yet ended. A phantom read occurs when a transaction reads the data rows that satisfy some criteria and then reads again based on the same criteria and finds additional rows included in the results. A phantom read could be the result of cached results not being used by the database to protect a transaction from the introduction of new data. Choosing an Isolation LevelThe next step in working with isolation levels is to understand the choices that are available to you. J2EE is consistent with SQL99 in recognizing four isolation levels: read uncommitted, read committed, repeatable read, and serializable. This order is significant with read uncommitted being the least restrictive and serializable the most. Read UncommittedAn isolation level of read uncommitted allows the uncommitted results of a transaction to be read by other transactions. A concurrent transaction that reads uncommitted data has no way to tell if what it has read is later rolled back. This level of isolation (or lack thereof) can result in dirty, nonrepeatable, and phantom reads. This isolation level is safe only for transactions that access read-only data. Read CommittedThe read committed isolation level requires that the data modified by one transaction cannot be read by another transaction until the first transaction either commits or rolls back. This level protects you from dirty reads but still allows nonrepeatable and phantom reads. Most databases default to a read committed isolation level. Repeatable ReadThe repeatable read isolation level requires that the data read by one transaction not be modified by another transaction until the first transaction either commits or rolls back. Using this level, the data read by a transaction is guaranteed to have the same value if the transaction reads it again. With repeatable read you're protected from dirty and nonrepeatable reads, but phantom reads are possible. SerializableThe serializable isolation level provides maximum protection by preventing any transaction from either reading or writing data that has been accessed by another transaction that has not yet completed. Serializable prevents dirty, nonrepeatable, and phantom reads. Note Just to be clear, the serializable isolation level has no connection to a class being Serializable from a Java standpoint. Serializable isolation implies that transactions Performance ImpactsAfter reading the preceding descriptions, your first thought might be that you should only execute serializable transactions in your applications. Unfortunately, there's a price to pay for the benefits offered by the more restrictive isolation levels. The locking and additional database overhead that are required to implement serializable isolation are a definite performance impact to an application. Rather than always assigning a highly restrictive level to your method transactions, you should instead take the approach of assigning the level required to support the needs of a method. If a method accesses read-only data, assigning a read uncommitted isolation level to it can be perfectly acceptable. On the other hand, a critical business method, such as one required to submit a customer order, would likely require the protection offered by serializable. As an alternative approach, you might choose serializable isolation for all your transactions and only reduce the level if performance problems warrant it. Changing the Isolation Level ProgrammaticallyThere are a few guidelines to adhere to when assigning isolation levels. First, most resource managers require that all access to them within a single transaction be performed using the must be executed serially as opposed to in parallel. same isolation level. You should especially be careful if you have multiple EJBs accessing a resource manager within a transaction. Another concern is to keep the isolation level specified by a single bean the same throughout a transaction. As you'll see momentarily, changing the isolation level in the midst of a transaction can produce an undesirable result. You're not, however, required to assign the same isolation level to all resource managers that are accessed by an enterprise bean. Before making that decision you should be careful that the inconsistency is justified, though. If you're using bean-managed transaction demarcation , you can assign isolation levels programmatically. For example, you can call the setTransactionIsolation method on a Connection object when you're using JDBC. An example of this is shown in the following: // acquire a connection Connection con = ... // choose serializable isolation con.setTransactionIsolation( Connection.TRANSACTION_SERIALIZABLE ); // start a transaction UserTransaction tx = myEJBContext.getUserTransaction(); tx.begin(); // do the work ... // complete the transaction tx.commit(); As mentioned previously, you shouldn't change the isolation level during a transaction. It is quite likely that a resource manager will respond to this request by immediately committing the current transaction and then starting a new one using the newly requested isolation. A call to setTransactionIsolation might fail because JDBC drivers aren't required to support every isolation level. If a driver is requested to assign a level it can't support, it's allowed to substitute a more restrictive level. If no more restrictive level exists, a SQLException is thrown. The preceding example applies only to JDBC drivers because there is no standard API for managing isolation level. Every resource manager that supports isolation levels is expected to provide its own API. Isolation level is a rare topic under EJB transactions because it's the one area where bean-managed demarcation is easier than container-managed. The EJB specification doesn't set forth a standard way to assign isolation levels when you're using container-managed transactions. To support control over isolation, a J2EE implementation has to allow you to specify the levels at deployment. If your application server can't do this, the default isolation for each resource manager is used. As was pointed out earlier, that typically means that read committed will be applied to all your database access. |