1. Trang chủ
  2. » Công Nghệ Thông Tin

Transactions

13 248 0
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Nội dung

So far we've been talking about all the ways to connect to a database and how to manipulate data in a database, but we haven't said much about transactions. If your JDBC Connection is in auto-commit mode, which it is by default, then every DML statement is committed to the database upon its completion. That may be fine for simple applications, but there are three reasons why you may want to turn off auto-commit and manage your own transactions: • To increase performance • To maintain the integrity of business processes • To use distributed transactions First, if you are performing batch insert, update, or delete operations, then turning off auto-commit and committing results manually at reasonable intervals will improve performance. Note that I said "at reasonable intervals." If you perform more operations per transaction than can fit into a rollback segment, the database takes additional time to increase the rollback segment to hold your uncommitted transaction statements, and that impairs performance. The second reason why you may want to manage your own transactions is to maintain the integrity of your business processes. For example, if a customer places an order for some merchandise, the information you'd need to store for that order would include a list of items to purchase, billing and shipping information, and an authorized credit card charge. This information would likely be stored in several different tables. Without a manually managed transaction, it's possible to enter part of the order, and then the system fails. The rest of the order information is then lost. This results in an incomplete business transaction. The third reason why you may want to manage your own transactions is to take advantage of the benefits of distributed systems. If you have to acquire information from, or update, several different systems, perhaps of unrelated technology, then you can use Oracle database links to perform a distributed transaction. Alternatively, you can use an XA connection, which is a connection based on the X/Open XA Architecture that supports distributed transaction processing. In this chapter, we'll look at how to enable manual-transaction support, the scope of transactions, and their implications for the visibility of database changes. We'll finish with a discussion of the distributed transaction support provided by Oracle's JDBC implementation. So let's get started with a look at how to enable manual transactions. 17.1 Manual Transactions To enable manual- transaction support instead of the auto-commit mode that the JDBC driver uses by default, use the Connection object's setAutoCommit( ) method. If you pass a boolean false to setAutoCommit( ), you turn off auto-commit. You can pass a boolean true to turn it back on again. For example, if you have a Connection object named conn, code the following to turn off auto-commit: conn.setAutoCommit(false); Disabling auto-commit is simple enough, but what are the implications of handling transactions manually? When is a particular transaction complete, and when does a new one start? And what effects does a manual transaction have on the implicit locking mechanism of an Oracle database? Let's begin by defining the scope of a transaction. 17.2 Transaction Scope An existing transaction ends, and a new transaction starts, at the moment a commit or rollback command is issued. Assuming you've turned off auto-commit, a COMMIT statement makes permanent any changes you've made to the database using INSERT, UPDATE, or DELETE statements since the last time a COMMIT or ROLLBACK statement was executed. With JDBC, commit your changes manually by calling the Connection object's commit( ) method. Calling the commit( ) method sends a COMMIT to the database. The commit( ) method takes no arguments but may throw an SQLException. However, it's very rare for a commit to result in an exception. For example, to commit changes for the Connection named conn, use the following code: conn.commit( ); On the other hand, a rollback command irrevocably discards, or undoes, any INSERT, UPDATE, or DELETE statements you've executed since the last time a COMMIT or ROLLBACK statement was executed. To roll back, use the Connection object's rollback( ) method. For example, to roll back updates to the database made using the Connection named conn, use the following code: conn.rollback( ); One last note: while auto-commit is off, if a Connection is closed without committing or rolling back, or if any DDL is executed, then any uncommitted changes are automatically committed. 17.3 Implicit Locking and Visibility It's important for you to understand the implications that a transaction has on implicit locking and visibility. Implicit locking refers to how Oracle automatically locks a database row during an insert, update, or delete operation. To help you understand the effects of implicit locking, I've created a table, TEST_TRANS, that has two columns: COL1, which is the table's primary key, and COL2. Figure 17-1 is a timeline for SQL statements against table TEST_TRANS. On the left are SQL statements entered from the same transaction and session. On the right are the effects of those SQL statements on the other session. Figure 17-1. Implicit locking during manual transactions To begin, session one performs an insert on table TEST_TRANS: insert into test_trans ( col1 ) values ( 'X' ) From the moment the INSERT statement is successfully executed, Oracle places an implicit lock on the new row. Since the new row has not been committed, it is not visible to an UPDATE, DELETE, or SELECT statement from session two. However, if attempts are made in session two to insert a row with the same primary key, that session will be blocked indefinitely until the first transaction is either committed or rolled back. When session one commits, the INSERT statement in session two will fail with a duplicate key error. After the commit, session one's new row is visible to an UPDATE, DELETE, or SELECT statement from session two. Session one now executes an UPDATE statement against the newly inserted and committed row: update test_trans set col2 = 'A' where col1 = 'X' Oracle once again places an implicit lock on the row. This time, an INSERT statement from session two with the same primary key will fail immediately. Furthermore, because of the implicit lock, any DML statement issued from session two will see a copy of the row as it was before it was updated by session one's UPDATE statement. However, an UPDATE or DELETE statement from session two against the same row will be blocked indefinitely until session one commits or rolls back its changes. When session one commits its UPDATE statement, then any blocked UPDATE or DELETE statement in session two will execute immediately. After the commit, any subsequent SELECT statement from session two will see the new row values. A major concern here is that even though session one's row was locked, when the lock was released through a commit, session two's UPDATE statement did not take into consideration the impact of session one's changes. Instead, session one's changes were simply overwritten. The only way to solve this problem is to use some form of change detection, such as looking at a timestamp, at an updatestamp, or at all the columns you are modifying with your UPDATE statement. We'll cover more of this issue in Chapter 18. Keep in mind that you, as the programmer, are responsible for preventing this type of situation from happening with your code. Session one continues by executing the following DELETE statement: delete test_trans where col1 = 'X' Oracle once again places an implicit lock on the row. This time, any insert, update, or delete from session two against that row is blocked until session one commits or rolls back its DELETE statement. Meanwhile, any SELECT statement from session two continues to see the row as it existed before the delete. When session one commits its DELETE statement, any INSERT statements from session two that use the same primary key will be successful. Any UPDATE, DELETE, or SELECT statement from session two will find no rows to affect. For this scenario, I've assumed that Oracle's default transaction isolation level, which is read committed, is used. Oracle also supports a serializable level. 17.4 Isolation Levels When applied to transactions, the term isolation level refers to how well one transaction is isolated from another. Oracle's default transaction isolation level is read committed. You can get the current isolation level for a connection by using the Connection object's getTransactionIsolation( ) method. You can set the transaction isolation level by calling the Connection object's setTransactionIsolation( ) method and passing one of the two valid constants: TRANSACTION_READ_COMMITTED or TRANSACTION_SERIALIZABLE. Oracle's read serializable isolation level will give you a consistent view of data from multiple tables since the last commit or rollback. Essentially, you see a snapshot of the tables as they existed when the transaction started. This is as opposed to read committed, which allows you to see changes during your transaction as soon as other transactions commit. 17.5 Distributed Transactions A distributed transaction is a set of two or more individual transactions, or branches, managed externally by a transaction manager and committed or rolled back as a single, global transaction. When it comes to distributed transactions, you have two choices of how to implement them. If you need to implement a distributed transaction between two or more Oracle databases, you can use database links. When using database links, you act as though your distributed transaction is just another local transaction and let Oracle's two-phase commit mechanism take care of the distributed transaction process transparently. But what if you want to manage a transaction between an Oracle and a Sybase database? Or with a credit card processing center? For cases such as these, you can use the JDBC 2.0 optional package's XAConnection object instead of a standard Connection object. Oracle's XA functionality implements the JDBC 2.0 optional package's support for distributed transactions. Although distributed transaction functionality is typically supported by an application server, such as one that supports Enterprise JavaBeans (EJB), this does not mean that you can't take advantage of the XA infrastructure to manage your own distributed transactions. To create an XAConnection object, use an XADataSource. For the most part, Oracle's XADataSource is configured just like, and allocates connections just like, the DataSource and ConnectionPoolDataSource objects we covered in Chapter 7. Therefore, I won't cover much about establishing a connection using the XA facility, because you can refer to Chapter 7 and the Oracle API for most of what you need to know. Further, XA is typically implemented in a middle-tier application server, and, as an application developer, you typically use it in a somewhat transparent way. For example, if you develop EJB, the EJB container uses the XA infrastructure to manage distributed transactions for your EJB. So for most of us, the XA classes are of little use. Given that, we won't spend much time on this topic. Instead, just to ensure that you are familiar with distributed transaction concepts, I'll simply provide an example of an XADataSource and an XAConnection in a sample program. Let's take a moment to cover the typical steps taken for a distributed transaction using XA with two data sources: 1. Each data source, or branch, of the distributed transaction gets an XAConnection object, which represents a physical connection to a transaction manager, such as a database, from its XADataSource object. 2. Using the XAConnection object, each branch gets a Connection object that will be used to perform SQL manipulations. 3. Again, using the XAConnection object, each branch gets an XAResource object, which will be used to coordinate the distributed transaction with the other branches. 4. A global transaction ID is created that will be used to create Xid objects for each branch to coordinate a distributed transaction. 5. For each branch, a branch transaction ID is created that will be used along with the global ID to create an Xid object for each branch. 6. An Xid object is created for each branch using an ID format identifier, a global ID, and a branch ID. 7. Each branch starts its leg of the distributed transaction by using the XAResource object's start( ) method, passing it the branch's Xid object. Next, any desired SQL statements are executed. The branch's leg is then ended by calling the XAResource object's end( ) method. 8. After each branch of a distributed transaction completes its SQL operations, it each calls its XAResource object's prepare( ) method to prepare for a global commit or rollback operation. 9. If all branches report a successful prepare phase, then each branch calls its XAResource object's commit( ) method. Otherwise, it calls its XAResource object's rollback( ) method. Now that you have the big picture of how distributed transactions are implemented using XA, let's look at the details. We'll start with the optional package's XADataSource interface. 17.5.1 XA Data Sources The XADataSource interface is implemented with the OracleXADataSource class and uses the same properties as the DataSource and ConnectionPoolDataSource classes we discussed in Chapter 7. XADataSource objects are factories for XAConnection objects, which in turn are factories for Connection and XAResource objects. Instead of the two overloaded getConnection( ) methods found in the DataSource interface, the XADataSource interface has two overloaded getXAConnection( ) methods with the following signatures. Keep in mind that they, like all JDBC methods, can throw a SQLException. XAConnection getXAConnection( ) XAConnection getXAConnection(String username, String Password) You can configure JNDI to allocate XADataSource objects just as we did for DataSource objects in Chapter 7. After you've created an XADataSource object, your next step is to allocate an XAConnection using one of the two getXAConnection( ) methods. 17.5.2 XA Connections An XAConnection extends PooledConnection and is implemented by the OracleXA- Connection class. An XAConnection represents a physical database connection. Seeing that an XAConnection object extends a PooledConnection, it inherits all of the PooledConnection methods: addConnectionEventListener( ) close( ) getConnection( ) removeConnectionEventListener( ) In addition to the PooledConnection methods, the XAConnection interface defines one more method, getXAResource( ). Following is the additional method signature. As usual, the method can throw a SQLException. XAResource getXAResource( ) Like the connection returned by a PooledConnection object, the Connection object returned by an XAConnection is not the same type of Connection object returned by DriverManager. At least, they are not the same internally. However, the two Connection objects implement the same Connection interface, so they appear to be the same. The difference is that the close( ) method of a Connection object from an XAConnection object returns a connection to XAConnection, an intermediary that provides the hooks for distributing a transaction and does not necessarily close the physical connection to a database. Calling XAConnection object's close( ) method actually closes the physical connection to a database. 17.5.3 XA IDs The Xid interface, which is implemented by the OracleXid class, is composed of a 4-byte format identifier, a 64-byte distributed transaction ID that is shared among all the Xid objects involved in a distributed transaction, and a 64-byte transaction branch ID for each branch of a distributed transaction. A transaction manager in a middle-tier application server can use the Oracle OracleXid constructor to create a new Xid object, passing the constructor a format ID, a global or distributed transaction ID, and a branch transaction ID. The Xid interface has three getter methods: getFormatId( ) getGlobalTransactionId( ) getBranchQualifier( ). 17.5.4 XA Resources An XAResource object, which is implemented by OracleXAResource, does the actual multiphase commit for a distributed transaction. To do a multiphase commit, use a global transaction ID represented by an Xid object together with appropriate methods from XAResource. The XAResource methods have the following signatures, and all of the methods can throw an XAException: void commit(Xid xid, boolean onePhase) void end(Xid xid, int flags) void forget(Xid xid) void prepare(Xid xid) Xid[] recover(int flag) void rollback(Xid xid) void start(Xid xid, int flags) With Version 8.1.6, the forget( ) and recover( ) methods are not implemented. Using an XAResource object, you typically start, end, prepare, and commit or roll back a transaction. When using a Connection object from an XAConnection, you cannot use the connection's commit( ) or rollback( ) methods. Instead, you must use the XAResource object's commit( ) or rollback( ) methods. If you do inadvertently use a Connection object's commit( ) or rollback( ) method, you'll get a SQLException. A transaction manager, typically your database, uses XAResource objects to coordinate the individual transactions, or branches, of a distributed transaction. When you perform a distributed transaction, you start a transaction branch, execute some DML statements, end the branch, and then repeat those steps for as many branches as is required. Then prepare each branch to commit, and then commit the changes. To start a new transaction branch and associate it with a distributed transaction, use the start( ) method, passing it an Xid along with the XAResource object's constant TMNOFLAGS as a second parameter. The start( ) method transparently associates a branch with a distributed transaction, because the Xid object passed to it has a common global ID. Here is a list of all the XAResource constants that can be used with the start( ) method: TMNOFLAGS Starts a new transaction branch TMJOIN Adds to an existing transaction branch TMRESUME Resumes a previously suspended branch To end a transaction branch, use the end( ) method, passing it an Xid and one of the following XAResource constants as a second parameter. Which constant you should pass depends on whether your SQL manipulations were successful. TMSUCCESS The transaction was successful. TMFAIL The transaction failed. TMSUSPEND Suspends the transaction branch. To prepare a branch for a two-phase commit, use its XAResource object's prepare( ) method, passing it the branch's Xid. The prepare( ) method returns one of the following XAResource constants: XA_RDONLY This result notifies the application that the connection supports only read-only activities such as SELECT statements. XA_OK This result notifies the application that the update prepared successfully. A call to prepare( ) may throw an XAException if an error is encountered during any of the branches being prepared. To commit the prepared changes in a transaction branch, call its XAResource object's commit( ) method, passing the transaction branch's Xid and a boolean to direct the commit method to use a one-phase (true) or two-phase (false) commit protocol. The default, if you don't pass a boolean, is to do a two-phase commit. To roll back the changes in a transaction branch, call the rollback( ) method, passing the transaction branch's Xid. To determine whether two branches have the same resource manager, call the XAResource's sameRM( ) method, passing it another XAResource object. The sameRM( ) method returns a boolean. A true return value means that both branches indeed use the same resource manager. With this information, you can direct two branches to use the same resource manager. Now that we've talked about XAResource and have referred to an XAException several times, let's discuss XAException objects. 17.5.5 XA Exceptions XA methods throw an XAException, which is implemented by OracleXAException. Besides the standard getMessage( ) method that all exceptions have, the OracleXAException class also defines a getXAError( ) method that returns one of the XA error message constants defined in XAException. There's also a getOracleError( ) method that returns the number of the Oracle error. Table 17-1 maps common XAException error code constants to their Oracle error equivalents. Table 17-1. XA-to-Oracle error codes XAException constant Oracle error code and message XAER_RMFAIL ORA-03113 end-of-file on communication channel. XAER_RMFAIL ORA-03114 not connected to ORACLE. XAER_NOTA ORA-24756 transaction does not exist. XA_HEURCOM ORA-24764 transaction branch has been heuristically committed. XA_HEURRB ORA-24765 transaction branch has been heuristically rolled back. XA_HEURMIX ORA-24766 transaction branch has been partly committed and aborted. XA_RDONLY ORA-24767 transaction was read-only and has been committed. XA_RETRY ORA-25351 transaction is currently in use. XA_RMERR All other error codes. Now that you have an overview of Oracle's support for XA, let's take a look at some implementation details. 17.5.6 classpath and imports To use XA, you not only need the Oracle driver file classes12.zip, but you must also have the Java Transaction API file jta.zip in your classpath. Then you must add the following import statements to your Java program: import oracle.jdbc.pool.*; import oracle.jdbc.xa.OracleXid; import oracle.jdbc.xa.OracleXAException; // If yours is a client-side, middle-tier program: import oracle.jdbc.client.*; // Or for a server-side, database resident program: import oracle.jdbc.server.*; import javax.transaction.xa.*; If your Java code will reside in the database, and yet access a remote database, then don't use the imports. Instead, use the fully qualified names for both the client-side (the part that will access the remote database) and server-side portions of your program. At this point, we're ready to look at a working example of a program using XA to perform a distributed transaction. 17.5.7 An XA Example Now that we've covered Oracle's implementation of XA, let's see it in action. The following DDL is for four tables: cart, item, shipping, and billing. If you have two databases available, then create the cart and item tables on the first database, then create the shipping and billing tables on the second database. Otherwise, create all four tables on the same database. create table cart ( cart number not null primary key, ordered date not null ) tablespace users pctfree 20 storage (initial 4K next 4K pctincrease 0) / create table item ( item number not null primary key, cart number not null, quantity number not null, descr varchar2(30) not null, each number not null ) tablespace users pctfree 20 storage (initial 4K next 4K pctincrease 0) / create table shipping ( cart number not null primary key, name varchar2(30) not null, address1 varchar2(30) not null, address2 varchar2(30), city varchar2(30) not null, state varchar2(30), country varchar2(30) not null, postal varchar2(30) not null, carrier varchar2(30) not null, service varchar2(30) not null ) tablespace users pctfree 20 storage (initial 4K next 4K pctincrease 0) / create table billing ( cart number not null primary key, name varchar2(30) not null, card varchar2(30) not null, expires date not null, amount number not null ) tablespace users pctfree 20 storage (initial 4K next 4K pctincrease 0) / Update the database URLs and schema names used in the SQL statements in Example 17-1 to show where you placed the tables. Also check usernames and passwords, then compile Example 17-1 and execute it. Example 17-1. TestXA import java.sql.*; import javax.sql.*; import javax.transaction.xa.*; import oracle.jdbc.xa.OracleXid; import oracle.jdbc.xa.OracleXAException; import oracle.jdbc.xa.client.*; public class TestXA { OracleXADataSource xaDSLocal = null; OracleXADataSource xaDSRemote = null; public TestXA( ) { // Create two XA data sources. Normally, the application server // would do this for you, or your program would use JNDI try { xaDSLocal = new OracleXADataSource( ); xaDSLocal.setURL("jdbc:oracle:thin:@dssw2k01:1521:orcl"); xaDSLocal.setUser("scott"); xaDSLocal.setPassword("tiger"); xaDSRemote = new OracleXADataSource( ); xaDSRemote.setURL("jdbc:oracle:oci8:@dssw2k01"); xaDSRemote.setUser("scott"); xaDSRemote.setPassword("tiger"); } catch (SQLException e) { System.err.println(e.getMessage( )); e.printStackTrace( ); } } public static void main(String[] args) throws Exception { new TestXA().process( ); } public void process( ) throws SQLException { boolean allOk = true; Connection connLocal = null; Connection connRemote = null; int rows = 0; Statement stmt = null; XAConnection xaConnLocal = null; XAConnection xaConnRemote = null; XAResource xarLocal = null; XAResource xarRemote = null; Xid xidLocal = null; Xid xidRemote = null; try { xaConnLocal = xaDSLocal.getXAConnection( ); xaConnRemote = xaDSRemote.getXAConnection( ); connLocal = xaConnLocal.getConnection( ); connRemote = xaConnRemote.getConnection( ); xarLocal = xaConnLocal.getXAResource( ); xarRemote = xaConnRemote.getXAResource( ); // Create the Xids // Create the global ID byte[] globalTransactionId = new byte[64]; globalTransactionId[0] = (byte)1; // Create the local branch ID byte[] branchQualifierLocal = new byte[64]; branchQualifierLocal[0] = (byte)1; xidLocal = new OracleXid( 0x1234, globalTransactionId, branchQualifierLocal); // Create the remote branch ID byte[] branchQualifierRemote = new byte[64]; branchQualifierRemote[0] = (byte)2; . transaction as soon as other transactions commit. 17.5 Distributed Transactions A distributed transaction is a set of two or more individual transactions, or branches,. So let's get started with a look at how to enable manual transactions. 17.1 Manual Transactions To enable manual- transaction support instead of the

Ngày đăng: 29/09/2013, 09:20

Xem thêm

TỪ KHÓA LIÊN QUAN

TÀI LIỆU CÙNG NGƯỜI DÙNG

  • Đang cập nhật ...

TÀI LIỆU LIÊN QUAN