2

Accordording to the room documentation transaction rollsback every query in a transaction in case an error occurs.

So here I tested the rollback out:

@Transaction
public Completable updateTable1(Table table1) {
    return Completable.fromAction(
            () -> 
                table1.setNotice("why am i updateed????");
                synchronousUpdate(table1);
                table1.setForeignKeyId(90); //some random foreign key which does not exsist so it throws an exception
                synchronousUpdate(table1);
            }
    );
}

@Update
void synchronousUpdate(T obj);

my table1 still gets updated even when the second update throws an error.

What can the cause be? I is cause I return a completable rather than a synchronous return value like void?

Funny Moments
  • 421
  • 2
  • 13

1 Answers1

1

The documentations says

The transaction will be marked as successful unless an exception is thrown in the method body.

It doesn't actually say that the exception will be managed, although the ROLLBACK will i.e. the setTransactionSuccessful is skipped and the endTransaction is executed. If you look at the generated code, say for example the coded method is :-

@Transaction
public boolean update4(Bookmark b1, Bookmark b2) {
    if (update(b1) > 0) {
        if (update(b2) < 1) {
            abort(b1);
            return false;
        }
    } else {
            abort(b1);
        return false;
    }
    return true;
}

The code generated by Room is :-

  @Override
  public boolean update4(final Bookmark b1, final Bookmark b2) {
    __db.beginTransaction();
    try {
      boolean _result = AllDao_Impl.super.update4(b1, b2);
      __db.setTransactionSuccessful();
      return _result;
    } finally {
      __db.endTransaction();
    }
  }

there is no catch, so you need to handle exceptions accordingly.

Consider the following (modified version of the previous answer given):-

    b1.setPostUrl("YetAnotherURL");
    b5.setPostTitle("P5");
    b5.setPostUrl("U5");
    try {
        dao.update4(b1, b5);
    }
    catch (Exception e) {
        e.printStackTrace();
    }
    logInfo("-D1");
    dao.update4(b1,b5);
    logInfo("-D2");

Basically the same update, which will not update (return 0) as the id doesn't exist, run twice. The difference being that the first is in a try/catch block.

The result from the log :-

2021-07-09 12:04:38.344 W/System.err: android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: mylist_data.ID (code 1555 SQLITE_CONSTRAINT_PRIMARYKEY)
2021-07-09 12:04:38.348 W/System.err:     at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)
2021-07-09 12:04:38.349 W/System.err:     at android.database.sqlite.SQLiteConnection.executeForLastInsertedRowId(SQLiteConnection.java:796)
2021-07-09 12:04:38.349 W/System.err:     at android.database.sqlite.SQLiteSession.executeForLastInsertedRowId(SQLiteSession.java:788)
2021-07-09 12:04:38.349 W/System.err:     at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:86)
2021-07-09 12:04:38.349 W/System.err:     at androidx.sqlite.db.framework.FrameworkSQLiteStatement.executeInsert(FrameworkSQLiteStatement.java:51)
2021-07-09 12:04:38.349 W/System.err:     at androidx.room.EntityInsertionAdapter.insertAndReturnId(EntityInsertionAdapter.java:114)
2021-07-09 12:04:38.349 W/System.err:     at a.a.so67958704javaroomconvertexistingdb.AllDao_Impl.abort(AllDao_Impl.java:133)
2021-07-09 12:04:38.349 W/System.err:     at a.a.so67958704javaroomconvertexistingdb.AllDao.update4(AllDao.java:40)
2021-07-09 12:04:38.350 W/System.err:     at a.a.so67958704javaroomconvertexistingdb.AllDao_Impl.access$001(AllDao_Impl.java:22)
2021-07-09 12:04:38.350 W/System.err:     at a.a.so67958704javaroomconvertexistingdb.AllDao_Impl.update4(AllDao_Impl.java:159)
2021-07-09 12:04:38.350 W/System.err:     at a.a.so67958704javaroomconvertexistingdb.MainActivity.onCreate(MainActivity.java:78)
....
2021-07-09 12:04:38.353 D/BMINFO-D1: ID = 1 PostTitle = P1 PostURL =AnotherURL
2021-07-09 12:04:38.353 D/BMINFO-D1: ID = 2 PostTitle = P2 PostURL =U2
2021-07-09 12:04:38.353 D/BMINFO-D1: ID = 3 PostTitle = P3 PostURL =U3
2021-07-09 12:04:38.353 D/BMINFO-D1: ID = 4 PostTitle = P4 PostURL =U4
2021-07-09 12:04:38.355 D/AndroidRuntime: Shutting down VM
2021-07-09 12:04:38.358 E/AndroidRuntime: FATAL EXCEPTION: main
    Process: a.a.so67958704javaroomconvertexistingdb, PID: 14230
    java.lang.RuntimeException: Unable to start activity ComponentInfo{a.a.so67958704javaroomconvertexistingdb/a.a.so67958704javaroomconvertexistingdb.MainActivity}: android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: mylist_data.ID (code 1555 SQLITE_CONSTRAINT_PRIMARYKEY)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913)

Noting that the first exception is caught and printed and is NOT a FATAL EXCEPTION, the 2nd is.

MikeT
  • 51,415
  • 16
  • 49
  • 68
  • I see, so since room does manage an occured exception it "assumes" the transaction is valid? – Funny Moments Jul 09 '21 at 06:23
  • 1
    @FunnyMoments I'd put it more as that it manages the transaction correctly i.e. not leaving an incomplete transaction, no matter what. – MikeT Jul 09 '21 at 06:50
  • conserning the abort function in your example. how exactly does abort work in this case? is that like predefined function of room? – Funny Moments Jul 09 '21 at 22:12
  • @FunnyMoments just an attempt to insert a duplicate with onConflict Strategy of ABORT (as per my previous answer) i.e. `@Insert(onConflict = OnConflictStrategy.ABORT) abstract long abort(Bookmark bookmark);`. It's not a Room function. – MikeT Jul 09 '21 at 22:37