Transactions
All of the previously discussed insert, update, and delete operations manipulate tables and rows in a database. While each operation is atomic (will either succeed or fail on its own), it is sometimes necessary to group a set of operations together and have the set of operations be atomic. There are times when a set of related operations should be allowed to manipulate the database only if all operations succeed to maintain database integrity. For these cases, a database transaction is usually used to ensure that the set of operations is atomic. In Android, the SQLiteDatabase class contains the following methods to support transaction processing:
void beginTransaction(): Begins a transaction
void setTransactionSuccessful(): Indicates that the transaction should be committed
void endTransaction(): Ends the transaction causing a commit if setTransactionSuccessful() has been called
Using a Transaction
A transaction is started with the SQLiteDatabase.beginTransaction() method. Once a transaction is started, calls to any of the data manipulation method calls (insert(), update(), delete()) may be made. Once all of the manipulation calls have been made, the transaction is ended with SQLiteDatabase.endTransaction(). To mark the transaction as successful, allowing all the operations to be committed, SQLiteDatabase.setTransactionSuccessful() must be called before the call to SQLiteDatabase.endTransaction() is made. If endTransaction() is called without a call to setTransactionSuccessful(), the transaction will be rolled back, undoing all of the operations in the transaction.
Because the call to setTransactionSuccessful() affects what happens during the endTransaction() call, it is considered a best practice to limit the number of non-database operations between a call to setTransactionSuccessful() and endTransaction(). Additionally, do not perform any additional database manipulation operations between the call to setTransactionSuccessful() and endTransaction(). Once the call to setTransactionSuccessful() is made, the transaction is marked as clean and is committed in the call to endTransaction() even if errors have occurred after the call to setTransactionSuccessful().
Listing 5.6 shows how a transaction should be started, marked successful, and ended in Android.
Listing 5.6 Transaction Example
SQLiteDatabase db = getDatabase(); db.beginTransaction(); try { // insert/update/delete // insert/update/delete // insert/update/delete db.setTransactionSuccessful(); } finally { db.endTransaction(); }
Database operations that happen in a transaction as well as the call to setTransaction() should take place in a try block with the call to endTransaction() happening in a finally block. This ensures that the transaction will be ended even if an unhandled exception is thrown while modifying the database.
Transactions and Performance
While transactions can help maintain data integrity by ensuring that multiple data manipulation operations occur atomically, they can also be used purely to increase database performance in Android. Like any operation performed in Java, there is overhead that is associated with running SQL statements inside a transaction. While a single transaction may not inject large amounts of overhead into a data manipulation routine, it is important to remember that every call to insert(), update(), and delete() is performed in its own transaction. Thus inserting 100 records into a table would mean that 100 individual transactions will get started, cleaned, and closed. This can cause a severe slowdown when attempting to perform a large number of data manipulation method calls.
To make multiple data manipulation calls run as fast as possible, it is generally a good idea to combine them into a single transaction manually. If the Android SDK determines that a call to insert()/update()/delete() is already inside of an open transaction, it will not attempt to start another transaction for the single operation. With a few lines of code, an app can dramatically speed up data manipulation operations. It is common to see a speed increase of five to ten times when wrapping even 100 data manipulation operations into a single transaction. These performance gains can increase as the number and complexity of operations increase.