【问题标题】:Very simple Firestore transaction fails非常简单的 Firestore 事务失败
【发布时间】:2018-04-27 14:37:55
【问题描述】:

我正在为一个超级简单的交易而苦苦挣扎。它总是失败并显示“Transaction failed all retries”消息,但除了 logcat 上没有错误消息。

当我调试它时,我看到它被重试了几次。我真的不知道为什么,因为其他交易运行没有问题。

我只想将一个文档从一个集合克隆到另一个集合。从“videos”思考到“favorites”(我知道这可以在@Alex 指出的事务之外完成,但这只是失败的部分,真正的事务更长)

private void copy(
    final DocumentReference SOURCEDOCREF, 
    final CollectionReference TARGETCOLREF) {

        Transaction.Function<? extends Void> transaction = new Transaction.Function<Void>() {

            @Nullable
            @Override
            public Void apply(@NonNull Transaction transaction) throws FirebaseFirestoreException {

                DocumentSnapshot doc = transaction.get(SOURCEDOCREF);
                if (doc.exists()) {
                    DocumentReference favoriteRef = TARGETCOLREF.document("FV_" + doc.getId());
                    Map<String, Object> data = doc.getData();
                    transaction.set(favoriteRef, data);
                    return null;

                    // NOTE: This is reached, ie. the source doc exists
                    // the data recovered, and set into the transaction.
                } else
                    throw new FirebaseFirestoreException("Item does not exist", FirebaseFirestoreException.Code.NOT_FOUND);
            }
        };

        setMode(MODE_SPLASH);
        FirebaseFirestore.getInstance().runTransaction(transaction)
                .addOnSuccessListener(
                        (Activity) getContext(),
                        new OnSuccessListener<Object>() {
                            @Override
                            public void onSuccess(Object aVoid) {
                                setMode(MODE_FOLLOW);
                            }
                        })
                .addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {
                        hide();
                        DialogHelper.customToast(getContext(), e.getMessage());
                    }
                });
    }

【问题讨论】:

    标签: java android firebase google-cloud-firestore


    【解决方案1】:

    根据documentation about transactions

    如果一个事务读取文档并且另一个客户端修改了任何一个 那些文档,Cloud Firestore 会重试交易。此功能 确保事务在最新且一致的数据上运行。

    因此,如果在事务完成之前源文档已被修改,您可以预期您的事务将被重试。

    您也可以预期交易可能会失败。

    交易可能因以下原因而失败:

    • 事务在写操作之后包含读操作。读取操作必须始终在任何写入操作之前进行。
    • 事务读取了在事务之外修改的文档。在这种情况下,事务会自动再次运行。 事务被重试有限次。

    一个失败的事务返回一个错误并且不写任何东西到 数据库。您不需要回滚事务;云 Firestore 会自动执行此操作。

    【讨论】:

    • 谢谢,道格。如你所见,我不会做那样的事情。这就是为什么我很高兴如此简单的交易总是失败,而我有非常复杂的交易从未失败。
    • 如果您认为这里有错误,请创建MCVE 并提交包含该信息的错误报告。 firebase.google.com/support/contact/bugs-features
    • 我不确定,只是想问问有经验的人是否发现我的代码有问题
    • @DougStevenson 有没有办法在特定重试后中止事务。如果我没记错的话,事务的最大重试次数是 25。所以我想知道在 5 次重试后是否有任何方法可以停止事务。在我的情况下,事务尝试失败时不会调用 OnFailureListener。
    【解决方案2】:

    在这种情况下不需要使用事务。要将文档从一个位置复制到另一个位置,请使用以下方法:

    public void cloneFirestoreDocument(DocumentReference fromPath, final DocumentReference toPath) {
        fromPath.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
            @Override
            public void onComplete(@NonNull Task<DocumentSnapshot> task) {
                if (task.isSuccessful()) {
                    DocumentSnapshot document = task.getResult();
                    if (document != null) {
                        toPath.set(document.getData())
                            .addOnSuccessListener(new OnSuccessListener<Void>() {
                                @Override
                                public void onSuccess(Void aVoid) {
                                    Log.d(TAG, "DocumentSnapshot successfully written!");
                                }
                            })
                            .addOnFailureListener(new OnFailureListener() {
                                @Override
                                public void onFailure(@NonNull Exception e) {
                                    Log.w(TAG, "Error writing document", e);
                                }
                            });
                    } else {
                        Log.d(TAG, "No such document");
                    }
                } else {
                    Log.d(TAG, "get failed with ", task.getException());
                }
            }
        });
    }
    

    其中fromPath 是您要移动的文档的位置,toPath 是您要移动文档的位置。

    流程如下:

    1. Get 来自 fromPath 位置的文档。
    2. Write 将文档发送到 toPath 位置。

    【讨论】:

    • 将尽快尝试。但是,您能想到为什么我之前的问题不起作用的原因吗?它的作用完全相同,只是在事务内部。
    • 此外,我认为可能需要进行交易(不确定?),因为一个用户可能正在删除该文档,或者类似的事情。
    • 不是。您正在使用这行代码:setMode(MODE_FOLLOW); inside addOnSuccessListener,它具有异步行为。这意味着当你运行事务时,那行代码还没有被调用。
    • 请查看official documentation 了解交易的内容。
    • 试试看,让我知道。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-17
    • 2016-03-26
    • 2020-03-12
    相关资源
    最近更新 更多