Android - when to call db.setTransactionSuccessful()?
Asked Answered
S

4

15

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);
    }
}
Singleaction answered 5/11, 2014 at 11:26 Comment(0)
A
17

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.

Alanis answered 5/11, 2014 at 11:37 Comment(1)
Thanks for all the answers. Laalto, I have updated my code (and added it to my question) in accordance with your input. Cheers.Singleaction
F
1

Normally setTransactionSuccessful is done just before endTransaction.

You can also call just after all your db operation are done for the transaction. But then there may not be any logical reason for delaying the endTransaction. Depends on your logic. If you want to make sure that some section of java code should execute successfully before committing the chages to db, you can move the setTransactionSuccessful and endTransaction furthur down.

In case of any exception in the logic writted between startTransaction and endTransation, then it may end up with open transaction and starvation (from your code it is clear that dbHelper is member variable. so you will be using same dbHelper within all your content provider calls). same dbHelper instance implies same SqliteDatabase instance.

So you will have to add try catch and end the transaction in finally.

Famine answered 5/11, 2014 at 11:37 Comment(0)
L
1

No need to check the condition for db.setTransactionSuccessful(). After insert statement we can write db.setTransactionSuccessful() statement. It will handle the transaction.

Sample Code:

public void insertActivity(ActivityModel mModel){
    db = getWritableDatabase();
    ContentValues cv = new ContentValues();
    try {
        db.beginTransaction();

        cv.put("id",mModel.getForm_id());
        cv.put("_name",mModel.getForm_name());
        cv.put("version_id",mModel.getLog_version_id());

        db.insert("activity_type_status_mapping", null, cv);
        db.setTransactionSuccessful();

    } catch (Exception e){
        e.printStackTrace();
    } finally {
        db.endTransaction();
        db.close(); 
    }   
}
Launch answered 5/11, 2014 at 11:58 Comment(0)
L
0

Those methods are now deprecated, so you can use this snippet instead:

roomDB.runInTransaction(() -> {
     // do your job here
});
Lasala answered 24/12, 2022 at 6:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.