同时更新两个表,但如果更新失败则回滚
Updating two Tables at same time but rollback if update fails
假设我们有上图。我们有两个表,我想 update.In 如果一个更新失败,我想回滚两个更新。
我想要最好的性能,所以我意识到我能想到的解决方案只有一个:
viewModel.updateTable1(
updatedId1 ->{
viewModel.updateTable2(
updatedId2 ->{
Toast.makeText(this,"great, both succes!!!! :)",Toast.LENGTH_LONG);
},
er->{
//somehow a rollback
}
},
er->{
Log.e("error",er.getMessage())
}
);
然而,到目前为止,我并不是我的解决方案的忠实拥护者,因为我需要等到 table1 得到更新,然后我才能更新 table2。所以我的问题是,是否可以更新 2 个或更多表,但在一次更新失败时回滚两个事务?
您最好的选择是 运行 在单个事务中进行两个更新。
一个示例,尽管在 Java 中,但故意设计为失败(在第二次更新时),从而回滚第一次成功的更新。
所以首先我们有一个更新方法(Kotlin 中的函数),它利用和更新 AllDao
接口中的 Dao @Update int update(Bookmark bookmark);
:-
public static void updateTwo(TheDatabase db, Bookmark bookmark1, Bookmark bookmark2) {
boolean updateOk = true;
AllDao dao = db.getAllDao();
SupportSQLiteDatabase sdb = db.getOpenHelper().getWritableDatabase();
sdb.beginTransaction();
if (dao.update(bookmark1) > 0) {
if (dao.update(bookmark2) < 1) {
Log.d("UPDATETWO","Second Update failed");
updateOk = false;
}
} else {
Log.d("UPDATETWO","First Update failed to update anything.");
updateOk = false;
}
if (updateOk) {
Log.d("UPDATETWO","Both updates OK");
sdb.setTransactionSuccessful(); //<<<<< ONLY SET TRANSACTION OK if both updates (aka ROLLBACK if not both OK)
}
sdb.endTransaction();
}
- 注意使用了 SupportSQliteDatabase 但实际上
runInTransaction
应该使用(正在考虑使用它,所以我可能会更新答案)
- 包含用于显示结果的日志记录。
- P.S。我允许 运行ning 在主线程上用于 convenience/brevity
并且在 Activity 中我有设计为不更新的代码(即更新失败(尽管它不只是不更新,我认为这就是你所说的失败而不是比抛出异常)):-
public class MainActivity extends AppCompatActivity {
TheDatabase db;
AllDao dao;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
db = TheDatabase.getInstance(this);
dao = db.getAllDao();
dao.deleteAll();
Bookmark b1 = new Bookmark(), b2 = new Bookmark();
// Insert Row
b1.setPostTitle("P1");
b1.setPostUrl("U1");
b1.setId(dao.insert(b1)); // sets the id so row can be updated
// Prepare for update
b1.setPostUrl("URL1"); // change so there is an update to be performed i.e. change U1 to URL1
// Prepare a second (no-existent so doomed to fail) Row for update
b2.setPostTitle("P2");
b2.setPostUrl("U2");
// Do the update
Bookmark.updateTwo(db,b1,b2);
for (Bookmark b: dao.getAll()) {
Log.d("BOOKMARKINFO","ID = " + b.getId() + " PostTitle = " + b.getPostTitle() + " PostURL =" + b.getPostUrl());
}
}
}
结果
日志:-
D/UPDATETWO: Second Update failed
D/BOOKMARKINFO: ID = 1 PostTitle = P1 PostURL =U1
即该行返回到 U1 而不是 URL1(又名 ROLLED BACK)
如果在更新之前添加行 b2.setId(dao.insert(b2));
(因此第二行存在并因此将被更新(即使实际上没有数据发生变化))那么日志是:-
D/UPDATETWO: Both updates OK
D/BOOKMARKINFO: ID = 1 PostTitle = P1 PostURL =URL1
D/BOOKMARKINFO: ID = 2 PostTitle = P2 PostURL =U2
即第一行是 URL1(又名 NOT ROLLED BACK)
- 以上仅适用于单行更新,因为它只检查是否进行了任何更新,而不是更新匹配的数量。
更新
这是使用 运行InTransaction 的等效项。似乎“除非在 Runnable 中抛出异常,否则事务将被标记为成功”。仅当抛出 SQLite 异常时。因此添加了一个不稳定的 dao 来强制异常,即
@Insert(onConflict = OnConflictStrategy.ABORT)
long abort(Bookmark bookmark);
- 不稳定,因为它假设书签存在,因此无法插入,因为违反了 UNIQUE 约束(id 列)。
替代代码为:-
public static void update2(TheDatabase db,Bookmark b1, Bookmark b2) {
try {
db.runInTransaction(new Runnable() {
@Override
public void run() {
try {
upd(db, b1, b2);
} catch (Exception e) {
}
}
});
} catch (Exception e) {}
}
private static void upd(TheDatabase db, Bookmark b1, Bookmark b2) throws Exception {
boolean updateOk = true;
AllDao dao = db.getAllDao();
if (dao.update(b1) > 0) {
if(dao.update(b2) < 1) {
updateOk = false;
Log.d("UPDATETWO","Second Update failed");
}
} else {
Log.d("UPDATETWO","First Update failed to update anything.");
}
if (!updateOk) {
dao.abort(b1); // Force SQLite Exception to ROLLBACK
} else {
Log.d("UPDATETWO","Both updates OK");
}
}
activity 中的以下代码用于测试:-
dao.deleteAll();
Bookmark b1 = new Bookmark(), b2 = new Bookmark(), b3 = new Bookmark();
// Insert Row
b1.setPostTitle("P1");
b1.setPostUrl("U1");
b1.setId(dao.insert(b1)); // sets the id so row can be updated
// Prepare for update
b1.setPostUrl("URL1"); // change so there is an update to be performed i.e. change U1 to URL1
// Prepare a second (no-existent so doomed to fail) Row for update
b2.setPostTitle("P2");
b2.setPostUrl("U2");
// Do the update
Bookmark.updateTwo(db,b1,b2);
logInfo("-A1");
b2.setId(dao.insert(b2));
Bookmark.updateTwo(db,b1,b2);
logInfo("-A2");
b1.setPostUrl("U R L 1");
b3.setPostTitle("P3");
b3.setPostUrl("U3");
Bookmark.update2(db,b1,b3);
logInfo("-B1");
b3.setId(dao.insert(b3));
Bookmark.update2(db,b1,b3);
logInfo("-B2");
}
private void logInfo(String extra) {
for (Bookmark b: dao.getAll()) {
Log.d("BMINFO" + extra,"ID = " + b.getId() + " PostTitle = " + b.getPostTitle() + " PostURL =" + b.getPostUrl());
}
}
这使用两种方法 -A(根据日志)使用 SupportSQliteDatabase
-B 使用 runInStransaction
.日志:-
2021-07-01 13:19:50.944 D/UPDATETWO: Second Update failed
2021-07-01 13:19:50.946 D/BMINFO-A1: ID = 1 PostTitle = P1 PostURL =U1
2021-07-01 13:19:50.949 D/UPDATETWO: Both updates OK
2021-07-01 13:19:50.951 D/BMINFO-A2: ID = 1 PostTitle = P1 PostURL =URL1
2021-07-01 13:19:50.951 D/BMINFO-A2: ID = 2 PostTitle = P2 PostURL =U2
2021-07-01 13:19:50.953 D/UPDATETWO: Second Update failed
2021-07-01 13:19:50.963 D/BMINFO-B1: ID = 1 PostTitle = P1 PostURL =URL1
2021-07-01 13:19:50.963 D/BMINFO-B1: ID = 2 PostTitle = P2 PostURL =U2
2021-07-01 13:19:50.966 D/UPDATETWO: Both updates OK
2021-07-01 13:19:50.969 D/BMINFO-B2: ID = 1 PostTitle = P1 PostURL =U R L 1
2021-07-01 13:19:50.969 D/BMINFO-B2: ID = 2 PostTitle = P2 PostURL =U2
2021-07-01 13:19:50.969 D/BMINFO-B2: ID = 3 PostTitle = P3 PostURL =U3
编辑 重新评论:-
Wouldnt't the anotation Transaction safe me the "boilercode"
尝试使用:-
@Transaction
public static boolean update3(AllDao dao,Bookmark b1, Bookmark b2) {
boolean updateOk = true;
if (dao.update(b1) > 0) {
if (dao.update(b2) < 1) {
updateOk = false;
}
}
if (!updateOk) {
dao.abort(b1);
}
return updateOk;
}
但是,中止会导致崩溃,例如:-
E/AndroidRuntime: FATAL EXCEPTION: main
Process: a.a.so67958704javaroomconvertexistingdb, PID: 6136
java.lang.RuntimeException: Unable to start activity ComponentInfo{blah....}: android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: mylist_data.ID (code 1555 SQLITE_CONSTRAINT_PRIMARYKEY)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913)
....
所以我认为 @Transaction
与 runInTransaction
不同,它不是为回滚而设计的。
假设我们有上图。我们有两个表,我想 update.In 如果一个更新失败,我想回滚两个更新。
我想要最好的性能,所以我意识到我能想到的解决方案只有一个:
viewModel.updateTable1(
updatedId1 ->{
viewModel.updateTable2(
updatedId2 ->{
Toast.makeText(this,"great, both succes!!!! :)",Toast.LENGTH_LONG);
},
er->{
//somehow a rollback
}
},
er->{
Log.e("error",er.getMessage())
}
);
然而,到目前为止,我并不是我的解决方案的忠实拥护者,因为我需要等到 table1 得到更新,然后我才能更新 table2。所以我的问题是,是否可以更新 2 个或更多表,但在一次更新失败时回滚两个事务?
您最好的选择是 运行 在单个事务中进行两个更新。
一个示例,尽管在 Java 中,但故意设计为失败(在第二次更新时),从而回滚第一次成功的更新。
所以首先我们有一个更新方法(Kotlin 中的函数),它利用和更新 AllDao
接口中的 Dao @Update int update(Bookmark bookmark);
:-
public static void updateTwo(TheDatabase db, Bookmark bookmark1, Bookmark bookmark2) {
boolean updateOk = true;
AllDao dao = db.getAllDao();
SupportSQLiteDatabase sdb = db.getOpenHelper().getWritableDatabase();
sdb.beginTransaction();
if (dao.update(bookmark1) > 0) {
if (dao.update(bookmark2) < 1) {
Log.d("UPDATETWO","Second Update failed");
updateOk = false;
}
} else {
Log.d("UPDATETWO","First Update failed to update anything.");
updateOk = false;
}
if (updateOk) {
Log.d("UPDATETWO","Both updates OK");
sdb.setTransactionSuccessful(); //<<<<< ONLY SET TRANSACTION OK if both updates (aka ROLLBACK if not both OK)
}
sdb.endTransaction();
}
- 注意使用了 SupportSQliteDatabase 但实际上
runInTransaction
应该使用(正在考虑使用它,所以我可能会更新答案) - 包含用于显示结果的日志记录。
- P.S。我允许 运行ning 在主线程上用于 convenience/brevity
并且在 Activity 中我有设计为不更新的代码(即更新失败(尽管它不只是不更新,我认为这就是你所说的失败而不是比抛出异常)):-
public class MainActivity extends AppCompatActivity {
TheDatabase db;
AllDao dao;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
db = TheDatabase.getInstance(this);
dao = db.getAllDao();
dao.deleteAll();
Bookmark b1 = new Bookmark(), b2 = new Bookmark();
// Insert Row
b1.setPostTitle("P1");
b1.setPostUrl("U1");
b1.setId(dao.insert(b1)); // sets the id so row can be updated
// Prepare for update
b1.setPostUrl("URL1"); // change so there is an update to be performed i.e. change U1 to URL1
// Prepare a second (no-existent so doomed to fail) Row for update
b2.setPostTitle("P2");
b2.setPostUrl("U2");
// Do the update
Bookmark.updateTwo(db,b1,b2);
for (Bookmark b: dao.getAll()) {
Log.d("BOOKMARKINFO","ID = " + b.getId() + " PostTitle = " + b.getPostTitle() + " PostURL =" + b.getPostUrl());
}
}
}
结果
日志:-
D/UPDATETWO: Second Update failed
D/BOOKMARKINFO: ID = 1 PostTitle = P1 PostURL =U1
即该行返回到 U1 而不是 URL1(又名 ROLLED BACK)
如果在更新之前添加行 b2.setId(dao.insert(b2));
(因此第二行存在并因此将被更新(即使实际上没有数据发生变化))那么日志是:-
D/UPDATETWO: Both updates OK
D/BOOKMARKINFO: ID = 1 PostTitle = P1 PostURL =URL1
D/BOOKMARKINFO: ID = 2 PostTitle = P2 PostURL =U2
即第一行是 URL1(又名 NOT ROLLED BACK)
- 以上仅适用于单行更新,因为它只检查是否进行了任何更新,而不是更新匹配的数量。
更新
这是使用 运行InTransaction 的等效项。似乎“除非在 Runnable 中抛出异常,否则事务将被标记为成功”。仅当抛出 SQLite 异常时。因此添加了一个不稳定的 dao 来强制异常,即
@Insert(onConflict = OnConflictStrategy.ABORT)
long abort(Bookmark bookmark);
- 不稳定,因为它假设书签存在,因此无法插入,因为违反了 UNIQUE 约束(id 列)。
替代代码为:-
public static void update2(TheDatabase db,Bookmark b1, Bookmark b2) {
try {
db.runInTransaction(new Runnable() {
@Override
public void run() {
try {
upd(db, b1, b2);
} catch (Exception e) {
}
}
});
} catch (Exception e) {}
}
private static void upd(TheDatabase db, Bookmark b1, Bookmark b2) throws Exception {
boolean updateOk = true;
AllDao dao = db.getAllDao();
if (dao.update(b1) > 0) {
if(dao.update(b2) < 1) {
updateOk = false;
Log.d("UPDATETWO","Second Update failed");
}
} else {
Log.d("UPDATETWO","First Update failed to update anything.");
}
if (!updateOk) {
dao.abort(b1); // Force SQLite Exception to ROLLBACK
} else {
Log.d("UPDATETWO","Both updates OK");
}
}
activity 中的以下代码用于测试:-
dao.deleteAll();
Bookmark b1 = new Bookmark(), b2 = new Bookmark(), b3 = new Bookmark();
// Insert Row
b1.setPostTitle("P1");
b1.setPostUrl("U1");
b1.setId(dao.insert(b1)); // sets the id so row can be updated
// Prepare for update
b1.setPostUrl("URL1"); // change so there is an update to be performed i.e. change U1 to URL1
// Prepare a second (no-existent so doomed to fail) Row for update
b2.setPostTitle("P2");
b2.setPostUrl("U2");
// Do the update
Bookmark.updateTwo(db,b1,b2);
logInfo("-A1");
b2.setId(dao.insert(b2));
Bookmark.updateTwo(db,b1,b2);
logInfo("-A2");
b1.setPostUrl("U R L 1");
b3.setPostTitle("P3");
b3.setPostUrl("U3");
Bookmark.update2(db,b1,b3);
logInfo("-B1");
b3.setId(dao.insert(b3));
Bookmark.update2(db,b1,b3);
logInfo("-B2");
}
private void logInfo(String extra) {
for (Bookmark b: dao.getAll()) {
Log.d("BMINFO" + extra,"ID = " + b.getId() + " PostTitle = " + b.getPostTitle() + " PostURL =" + b.getPostUrl());
}
}
这使用两种方法 -A(根据日志)使用 SupportSQliteDatabase
-B 使用 runInStransaction
.日志:-
2021-07-01 13:19:50.944 D/UPDATETWO: Second Update failed
2021-07-01 13:19:50.946 D/BMINFO-A1: ID = 1 PostTitle = P1 PostURL =U1
2021-07-01 13:19:50.949 D/UPDATETWO: Both updates OK
2021-07-01 13:19:50.951 D/BMINFO-A2: ID = 1 PostTitle = P1 PostURL =URL1
2021-07-01 13:19:50.951 D/BMINFO-A2: ID = 2 PostTitle = P2 PostURL =U2
2021-07-01 13:19:50.953 D/UPDATETWO: Second Update failed
2021-07-01 13:19:50.963 D/BMINFO-B1: ID = 1 PostTitle = P1 PostURL =URL1
2021-07-01 13:19:50.963 D/BMINFO-B1: ID = 2 PostTitle = P2 PostURL =U2
2021-07-01 13:19:50.966 D/UPDATETWO: Both updates OK
2021-07-01 13:19:50.969 D/BMINFO-B2: ID = 1 PostTitle = P1 PostURL =U R L 1
2021-07-01 13:19:50.969 D/BMINFO-B2: ID = 2 PostTitle = P2 PostURL =U2
2021-07-01 13:19:50.969 D/BMINFO-B2: ID = 3 PostTitle = P3 PostURL =U3
编辑 重新评论:-
Wouldnt't the anotation Transaction safe me the "boilercode"
尝试使用:-
@Transaction
public static boolean update3(AllDao dao,Bookmark b1, Bookmark b2) {
boolean updateOk = true;
if (dao.update(b1) > 0) {
if (dao.update(b2) < 1) {
updateOk = false;
}
}
if (!updateOk) {
dao.abort(b1);
}
return updateOk;
}
但是,中止会导致崩溃,例如:-
E/AndroidRuntime: FATAL EXCEPTION: main
Process: a.a.so67958704javaroomconvertexistingdb, PID: 6136
java.lang.RuntimeException: Unable to start activity ComponentInfo{blah....}: android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: mylist_data.ID (code 1555 SQLITE_CONSTRAINT_PRIMARYKEY)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913)
....
所以我认为 @Transaction
与 runInTransaction
不同,它不是为回滚而设计的。