Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android - when to call db.setTransactionSuccessful()?

Code examples of Android SQLite transactions that I've seen appear to automatically call db.setTransactionSuccessful() right before db.endTransaction().

I am wondering if that is actually best practice or whether there should be some conditional check before calling db.setTransactionSuccessful().

In my case, I am overriding ContentProvider's bulkInsert() method and if I use a conditional check as described, my method will look like this...

    @Override
public int bulkInsert(Uri uri, ContentValues[] valuesArray) {

    // Open a read / write database to support the transaction.
    SQLiteDatabase db = dbHelper.getWritableDatabase();

    switch (uriMatcher.match(uri)) {
        case BRANDS_SEARCH:

            int numInserts = 0;

            db.beginTransaction();

            for (int i = 0; i < valuesArray.length; i++) {

                // Insert the values into the table
                long rowId = db.insert(BRAND_NAMES_TABLE, null, valuesArray[i]);

                if (rowId > -1) {

                    // Increment numInserts
                    numInserts++;

                    // Construct the URI of the newly inserted row.
                    Uri insertedId = ContentUris.withAppendedId(CONTENT_URI_BRANDS, rowId);

                    // Notify any observers of the change in the data set.
                    getContext().getContentResolver().notifyChange(insertedId, null);

                }

            }

            boolean allInsertAttemptsWereSuccessful = (numInserts == valuesArray.length);

            if (allInsertAttemptsWereSuccessful) {
                db.setTransactionSuccessful(); //todo - should this be conditional?
            }
            else {
                //todo - ???
            }

            db.endTransaction();

            return numInserts;

        default:
            //break;
            throw new IllegalArgumentException("Unsupported URI: " + uri);
    }
}

...is this the correct approach?

And what action should I take in the case where allInsertAttemptsWereSuccessful == false??

(I have looked in the Android docs, but very little info is provided.)

Update - New Code...

Thanks to laalto's answer, this is my new (correct) code...

/**
 * Attempts a bulk insert. Outcome will either be all inserts succeeded
 * or all inserts failed.
 */
@Override
public int bulkInsert(Uri uri, ContentValues[] valuesArray) {

    /*
     *  Open a read / write database to support the transaction.
     */
    SQLiteDatabase db = dbHelper.getWritableDatabase();

    switch (uriMatcher.match(uri)) {
        case BRANDS_SEARCH:

            /*
             * Begin the transaction
             */
            db.beginTransaction();

            try {

                for (int i = 0; i < valuesArray.length; i++) {

                    /*
                     *  Insert the values into the table
                     */
                    long rowId = db.insert(BRAND_NAMES_TABLE, null, valuesArray[i]);

                    if (rowId > -1) {

                        /*
                         *  Construct the URI of the newly inserted row.
                         */
                        Uri insertedId = ContentUris.withAppendedId(CONTENT_URI_BRANDS, rowId);

                        /*
                         *  Notify any observers of the change in the data set.
                         */
                        getContext().getContentResolver().notifyChange(insertedId, null);

                    }
                    else {

                        /*
                         * Give up (as we need all insert attempts to succeed)
                         */
                        throw new Exception("Could not insert row");
                    }

                }

                /*
                 * All insert attempts succeeded
                 */
                db.setTransactionSuccessful();

                return valuesArray.length;
            }
            catch(Exception e) {

                /*
                 * If any insert attempt failed, then setTransactionSuccessful() will not be called so no rows will actually be inserted
                 */
                return 0;

            }
            finally {

                /*
                 * Always end the transaction
                 */
                db.endTransaction();
            }

        default:
            //break;
            throw new IllegalArgumentException("Unsupported URI: " + uri);
    }
}
like image 462
ban-geoengineering Avatar asked Nov 05 '14 11:11

ban-geoengineering


1 Answers

The canonical pattern for transactions:

beginTransaction();
try {
    //db operations ...

    setTransactionSuccessful();
} finally {
    endTransaction();
}

This ensures that there endTransaction() is always called (no dangling transactions left behind), and the transaction is rolled back when an exception occurs in some database operation. If you want to abort the transaction for a reason of your own, just don't call setTransactionSuccessful(), or throw an exception.

like image 118
laalto Avatar answered Oct 18 '22 10:10

laalto