当单值事件或事务的嵌套 ValueEventListener 失败时,如何在 Firebase Realtime DB for Android 中回滚到以前的状态?

Posted

技术标签:

【中文标题】当单值事件或事务的嵌套 ValueEventListener 失败时,如何在 Firebase Realtime DB for Android 中回滚到以前的状态?【英文标题】:How to roll back to the previous state in Firebase RealtimeDB for Android, when a nested ValueEventListener for SingleValueEvent or Transaction fails? 【发布时间】:2018-05-03 11:25:42 【问题描述】:

我正在开发一个基于 Java 和 Firebase 的原生 android 应用程序,该应用程序需要基于某些操作对不同的实时数据库引用进行多次写入(使用 SingleValueEventListener 或事务)。 是这样的:

    用户单击按钮。 在 DB ref 1 的 SingleValueEventListener1/Transaction1 中,DB 引用 1 得到更新。 一旦数据库引用 1 成功更新,在另一个嵌套在 SingleValueEventListener1/Transaction1 中的 SingleValueEventListener2/Transaction2 中,数据库引用 2 开始更新。 ...等等。

我需要什么?

只有在我尝试更新的第一个位置成功后,才更新第二个数据库位置。这些位置有助于并发更新。

初步研究:

我查看了有关数据库回滚、并发更新、多位置更新、数据库规则和安全性的各种帖子和堆栈溢出问题,但没有一个对我的事业有帮助。

我看过哪些资源?

Firebase undoable update、Firebase commit rollback、Firebase multi-write、Firebase multi-location updates

有什么问题?

引用更新没有问题。问题在于如果 DB 引用 1 更新成功,而 DB 引用 2 更新失败,那么我没有选择将 Ref 1 回滚到之前的状态。

是否需要这种方法?

在我的情况下,不幸的是。

有哪些方法可以解决这个问题?

    我可以存储 Ref 1 的先前状态,如果 Ref 2 的更新失败,我可以重写 Ref 1 的先前状态。 但是这些位置支持并发更新,当我回滚到以前的状态时,以前的状态也可能已经更新。以前的数据可能会变得陈旧,用该数据覆盖 ref 可能会导致数据丢失。解决所有这些问题需要在代码中产生不希望的复杂性,这可能根本无法保证解决问题。

    事务肯定会尝试更新 Ref 2 直到成功,但直到某个时间,并且可能是由于网络连接丢失导致无法在第二个位置写入,这解释了我的一部分担心。

    另一种选择是更改数据库设计并将所有多个数据库引用移动到一个位置,这样如果位置更新失败,Firebase 不会发生任何变化。但是,我设计数据库的方式似乎很好。事实上,为了方便解决这个问题,如果我改变我的数据库结构,并将东西转移到一个位置,那在我的情况下是绝对错误的。用类似的情况来解释这一点,例如,在 *** 中,如果我对一个答案进行投票,那么回答者的声誉会发生变化,答案的状态会随着所涉及的人员和投票数量而变化,赞成改变的人也可能是他的声誉。现在以上所有的 DB refs 都不能合并为一个。

问题与 SingleValueEventListeners 或 Transactions 无关。

出现问题的案例的简单代码示例?

为简单起见,以下说明了一个 2 级深度嵌套的 EventListeners(同样适用于 Transactions)。

ValueEventListener outerListener = new ValueEventListener() 
  @Override
  public void onDataChange(DataSnapshot dataSnapshot) 
    if (dataSnapshot.exists()) 
    //do something
    //update DBRefOne
    DBRefOne.updateChildren(newOuterValue, new DatabaseReference.CompletionListener() 
      @Override
      public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) 
        ValueEventListener innerListener = new ValueEventListener() 
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) 
          if (dataSnapshot.exists()) 
          //do something
          //update DBRefTwo
          DBRefTwo.updateChildren(newInnerValue);
          
        
        @Override
        public void onCancelled(DatabaseError databaseError) 
       // log error
      
    ;
    DBRefTwo.addListenerForSingleValueEvent(innerListener);
   
 );


  @Override
  public void onCancelled(DatabaseError databaseError) 
  // log error
  
;
DBRefOne.addListenerForSingleValueEvent(outerListener);

这对我来说是一个重要但棘手的问题,需要解决,因为大多数应用程序都是以其他方式完成的。我愿意根据您的建议进行返工。请随时提出建议,任何帮助都将不胜感激。

【问题讨论】:

我在下面就您的选项写了一个快速答案。我很欣赏你试图彻底解决你的问题,但这使得解析工作量很大。我建议编辑只关注minimal code + JSON that reproduces the problem。例如。在您当前的代码 sn-p 中,我们不知道 DBRefOneDBRefTwo 是如何相关的,也不知道 newOuterValuenewInnerValue 是什么(基于)。 【参考方案1】:

有两种方式可以事务性地更新数据:

    使用事务 使用多位置更新

没有隐藏的第三种方式,因此如果您想以事务方式更新数据,则必须使用其中一种。

如果您写入的值不是基于当前值,您应该使用多位置更新,因为它具有更好的性能特征。有关这方面的更多信息,请阅读这篇博文:Client-side fan-out for data consistency。

如果您写入的值基于当前值,则需要使用事务。在这种情况下,确保您正在写入的数据在 JSON 中彼此接近很重要,因为您实际上是在更新位置的顶部放置了一个(乐观的)锁。

【讨论】:

感谢您的回复。我会把你的建议留给以后的帖子。但是,跟进您对事务的响应(因为我的值基于当前值),我仍然要问您,当外部事务成功时会发生什么,一旦完成,嵌套的内部事务开始执行,但是它失败 - 如何恢复外部事务的上一个值,存储它不是一个选项,因为它可能会被更新?事务尝试多次,但在一个点后停止。它是一个边缘案例,但是一个有效的案例。您对此有何看法,请随时告诉我。 没有嵌套事务。您必须在单个事务中进行更新,这意味着您必须在 JSON 中可以获得两个值的最低级别运行该事务。树的位置越高,您的交易执行的就越差。

以上是关于当单值事件或事务的嵌套 ValueEventListener 失败时,如何在 Firebase Realtime DB for Android 中回滚到以前的状态?的主要内容,如果未能解决你的问题,请参考以下文章

laravel 嵌套事务

SQL 嵌套查询和使用 MAX 提取最近的事务和/或评论

知识点:Spring嵌套事务方式

SQL Server 2005 - 无法回滚嵌套事务

Angular 2 单选按钮事件

从Laravel,Yii,Thinkphp中学习php 操作数据库的事务嵌套