Firebase Database多表关联查询:分类与翻译数据整合遇空值
问题描述
我希望在查询数据库时,将Categories表与Translations表关联,或是用Translation的值填充Categories表中的TextContentIDForName字段。我不会修改数据库,只需要在页面展示分类时显示对应的翻译文本——因为翻译文本需要单独存储,所以采用了当前的数据库架构。
目前的问题是找不到关联两张表的属性,我的数据库里有三张表:分类表、存储多语言翻译的表,以及为帖子分配ID的TextContent表。
数据库结构截图



获取所有分类的原始查询代码
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) { } }); }
问题分析与解决
数据为空的核心原因
- 模型映射不匹配:直接从
categories节点的快照映射到CategoriesAndTranslations类,但两类节点的字段结构完全不同,导致反射赋值失败,所有字段为空。同理,translations节点的快照也无法映射到该类。 - 数据逻辑混乱:嵌套查询中把翻译数据直接添加到分类列表,导致列表同时包含两类结构不兼容的数据,显示时必然出现空值。
- 适配器刷新时机错误:循环内和嵌套查询内重复调用刷新,导致列表未加载完所有数据就提前渲染。
正确实现方案
无需创建额外关联模型,直接在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
相关产品推荐
相关产品推荐

