You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Firebase Database多表关联查询:分类与翻译数据整合遇空值

问题描述

我希望在查询数据库时,将Categories表与Translations表关联,或是用Translation的值填充Categories表中的TextContentIDForName字段。我不会修改数据库,只需要在页面展示分类时显示对应的翻译文本——因为翻译文本需要单独存储,所以采用了当前的数据库架构。

目前的问题是找不到关联两张表的属性,我的数据库里有三张表:分类表、存储多语言翻译的表,以及为帖子分配ID的TextContent表。

数据库结构截图

数据库结构截图1
数据库结构截图2
数据库结构截图3

获取所有分类的原始查询代码

public void getAllCategories() {
    FirebaseDatabase db = FirebaseDatabase.getInstance();
    DatabaseReference categoriesRef = db.getReference("categories");
    DatabaseReference TranslationsRef = db.getReference("translations");

    ArrayList<Categories> categoriesArrayList = new ArrayList<>();

    Query query = categoriesRef.orderByChild("parentID").equalTo("categories");
    query.addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(@NonNull DataSnapshot snapshot) {
            categoriesArrayList.clear();
            for (DataSnapshot data : snapshot.getChildren()) {
                Categories categories = data.getValue(Categories.class);
                assert categories != null;

                categories.setKey(data.getKey());
                categoriesArrayList.add(categories);
            }
            categoriesAdapter.notifyDataSetChanged();
        }

        @Override
        public void onCancelled(@NonNull DatabaseError error) {

        }
    });
}

尝试关联翻译文本的查询代码

我尝试创建两个查询,把数据映射到通用模型CategoriesAndTranslations中,但这种方式不够灵活,还需要额外创建关联模型。而且通过这种方式映射出的所有数据均为空值,不清楚原因。

public void getAllCategories() {
        FirebaseDatabase db = FirebaseDatabase.getInstance();
        DatabaseReference categoriesRef = db.getReference("categories");
        DatabaseReference TranslationsRef = db.getReference("translations");

        Query query = categoriesRef.orderByChild("parentID").equalTo("categories");
        query.addValueEventListener(new ValueEventListener() {

            @Override
            public void onDataChange(@NonNull DataSnapshot snapshot) {
                categoriesArrayList.clear();
                for (DataSnapshot data : snapshot.getChildren()) {

                    CategoriesAndTranslations categoriesAndTranslations = data
                            .getValue(CategoriesAndTranslations.class);
                    assert categoriesAndTranslations != null;

                    categoriesAndTranslations.setKey(data.getKey());

                    categoriesArrayList.add(categoriesAndTranslations);
                    System.out.println("1234" + categoriesAndTranslations);

                    String textContentID = categoriesAndTranslations.getTextContentID();

                    Query query = TranslationsRef.orderByChild("textContentID")
                            .equalTo(textContentID);
                    query.addValueEventListener(new ValueEventListener() {
                        @Override
                        public void onDataChange(@NonNull DataSnapshot snapshot) {
                            for (DataSnapshot data : snapshot.getChildren()) {

                                CategoriesAndTranslations categoriesAndTranslations = data
                                        .getValue(CategoriesAndTranslations.class);
                                System.out.println("123" + categoriesAndTranslations);
                                categoriesArrayList.add(categoriesAndTranslations);

                            }
                            categoriesAdapter.notifyDataSetChanged();
                        }

                        @Override
                        public void onCancelled(@NonNull DatabaseError error) {

                        }
                    });
                    categoriesAdapter.notifyDataSetChanged();

                }
            }

            @Override
            public void onCancelled(@NonNull DatabaseError error) {

            }
        });
    }

问题分析与解决

数据为空的核心原因

  1. 模型映射不匹配:直接从categories节点的快照映射到CategoriesAndTranslations类,但两类节点的字段结构完全不同,导致反射赋值失败,所有字段为空。同理,translations节点的快照也无法映射到该类。
  2. 数据逻辑混乱:嵌套查询中把翻译数据直接添加到分类列表,导致列表同时包含两类结构不兼容的数据,显示时必然出现空值。
  3. 适配器刷新时机错误:循环内和嵌套查询内重复调用刷新,导致列表未加载完所有数据就提前渲染。

正确实现方案

无需创建额外关联模型,直接在Categories类中添加翻译字段,通过textContentID关联查询并填充:

1. 修改Categories类

添加翻译存储字段:

public class Categories {
    private String key;
    private String parentID;
    private String textContentID;
    // 新增翻译字段,存储对应语言的翻译文本
    private String nameTranslation;

    // Firebase序列化必须的无参构造
    public Categories() {}

    // Getter & Setter
    public String getKey() { return key; }
    public void setKey(String key) { this.key = key; }

    public String getParentID() { return parentID; }
    public void setParentID(String parentID) { this.parentID = parentID; }

    public String getTextContentID() { return textContentID; }
    public void setTextContentID(String textContentID) { this.textContentID = textContentID; }

    public String getNameTranslation() { return nameTranslation; }
    public void setNameTranslation(String nameTranslation) { this.nameTranslation = nameTranslation; }
}

2. 优化查询代码

先获取所有分类,再批量查询对应翻译,统一填充后刷新适配器:

public void getAllCategories() {
    FirebaseDatabase db = FirebaseDatabase.getInstance();
    DatabaseReference categoriesRef = db.getReference("categories");
    DatabaseReference translationsRef = db.getReference("translations");

    ArrayList<Categories> categoriesArrayList = new ArrayList<>();

    Query query = categoriesRef.orderByChild("parentID").equalTo("categories");
    query.addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(@NonNull DataSnapshot snapshot) {
            categoriesArrayList.clear();
            // 用CountDownLatch等待所有嵌套查询完成
            CountDownLatch latch = new CountDownLatch((int) snapshot.getChildrenCount());

            for (DataSnapshot data : snapshot.getChildren()) {
                Categories category = data.getValue(Categories.class);
                if (category == null) {
                    latch.countDown();
                    continue;
                }
                category.setKey(data.getKey());

                String textContentID = category.getTextContentID();
                if (textContentID != null && !textContentID.isEmpty()) {
                    // 用单次查询替代持续监听,避免不必要的更新
                    translationsRef.orderByChild("textContentID").equalTo(textContentID)
                            .addListenerForSingleValueEvent(new ValueEventListener() {
                                @Override
                                public void onDataChange(@NonNull DataSnapshot transSnapshot) {
                                    if (transSnapshot.exists()) {
                                        // 假设每个textContentID对应一条翻译,取第一个结果
                                        DataSnapshot transData = transSnapshot.getChildren().iterator().next();
                                        // 根据你的translations表结构,替换为实际的翻译字段名(比如"value")
                                        String translation = transData.child("value").getValue(String.class);
                                        category.setNameTranslation(translation);
                                    }
                                    categoriesArrayList.add(category);
                                    latch.countDown();
                                }

                                @Override
                                public void onCancelled(@NonNull DatabaseError error) {
                                    categoriesArrayList.add(category);
                                    latch.countDown();
                                }
                            });
                } else {
                    categoriesArrayList.add(category);
                    latch.countDown();
                }
            }

            // 所有查询完成后,在主线程刷新适配器
            new Thread(() -> {
                try {
                    latch.await();
                    runOnUiThread(() -> categoriesAdapter.notifyDataSetChanged());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }

        @Override
        public void onCancelled(@NonNull DatabaseError error) {
            // 处理查询错误
        }
    });
}

关键优化点

  • 使用addListenerForSingleValueEvent避免持续监听翻译数据,减少资源消耗。
  • 用CountDownLatch确保所有分类的翻译都加载完成后再刷新适配器,保证数据完整性。
  • 复用原有Categories类,无需额外模型,逻辑更简洁。

内容的提问来源于stack exchange,提问作者Droidbane

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 15:15:31