【问题标题】:Room db with many to many relations : returns single entry on querying for filtered data具有多对多关系的房间数据库:在查询过滤数据时返回单个条目
【发布时间】:2019-06-23 02:47:07
【问题描述】:

我有 3 个实体 PersonPersonTagRelationTag:Person 通过多对多关系与标签相关联。 我还有一个用于查询数据库的多个函数的 dao。 大多数事情都运行良好,但我有这个查询,假设创建具有特定标签的用户过滤列表,这似乎不起作用。 例如,如果有 3 个人 A,B,C 对应的标签列表分别为 [ai,ml],[ai],[flutter]。那么我的查询应该返回 [A,B] 当传递标签ai 时。但它只返回[A]。 请检查以下代码,让我知道我哪里出错了:

Person.class

@Entity()
public class Person {
    @PrimaryKey
    private long presonID;
    private String personName;
...
}

标签类

@Entity
public class Tag {

    @PrimaryKey
    private long tagID;
    private String tagValue;
    ...
}

PersonTagRelation.class

@Entity(primaryKeys = {"personTagIDForPerson","personTagIDForTag"},
        foreignKeys = {
        @ForeignKey(entity = Person.class, parentColumns = "presonID", childColumns = "personTagIDForPerson"),
        @ForeignKey(entity = Tag.class, parentColumns = "tagID", childColumns = "personTagIDForTag"),
})
public class PersonTagRelation {
   private long personTagIDForPerson, personTagIDForTag;
   ...
   }

PersonDao.class

@Dao
public abstract class PersonDao {
    @Insert abstract void insertNewPerson(Person p);
    @Insert abstract void insertNewTag(Tag t);
    @Insert abstract void insertNewRelation(PersonTagRelation relation);

    @Query("SELECT * FROM Person ORDER BY presonID DESC")
    abstract List<Person> getAllPersons();

    @Query("SELECT tagID FROM Tag WHERE tagValue=:tag ORDER BY tagID DESC")
    abstract long getTagIdByTagName(String tag);

    @Query("SELECT * FROM Person" +
            " INNER JOIN PersonTagRelation ON Person.presonID=PersonTagRelation.personTagIDForPerson " +
            "WHERE PersonTagRelation.personTagIDForTag=:tagID")
    abstract List<Person> getAllPersonsForTagID(long tagID);

    @Query("SELECT * FROM Tag " +
            "INNER JOIN PersonTagRelation ON Tag.tagID=PersonTagRelation.personTagIDForTag " +
            "WHERE PersonTagRelation.personTagIDForPerson=:personID")
    abstract List<Tag> getAllTagsForPerson(long personID);


    @Transaction
    public void insertNewPersonTagData(UiModel model) {
        insertNewPerson(model.getPerson());
        for (Tag t : model.getTags()) {
            insertNewTag(t);
            insertNewRelation(new PersonTagRelation(model.getPerson().getPresonID(), t.getTagID()));
        }
    }
    @Transaction
    public List<UiModel> getFilteredModelList(String tag) {
        List<UiModel> resultlist = new ArrayList<>();
        long tagID = getTagIdByTagName(tag);
        List<Person> filteredPersonList = getAllPersonsForTagID(tagID);

        for (Person p : filteredPersonList) {
            List<Tag> associatedTags = getAllTagsForPerson(p.getPresonID());
            resultlist.add(new UiModel(p, associatedTags));
        }
        return resultlist;
    }

    @Transaction
    public List<UiModel> getAllAsModelList() {
        List<UiModel> modelList = new ArrayList<>();
        List<Person> allpersons = getAllPersons();
        for (Person p : allpersons) {
            List<Tag> associatedTags = getAllTagsForPerson(p.getPresonID());
            modelList.add(new UiModel(p, associatedTags));
        }
        return modelList;
    }

UiModel.class

public class UiModel {
    private Person person= new Person(System.currentTimeMillis(),"");
    private List<Tag> tagList = new ArrayList<>();

    public UiModel() {
    }
}

图1:getAllAsModelList()的结果
图 2:getFilteredModelList()JS 被传递时的结果

更新:
@MikeT 提供的测试套件非常好,但事实证明数据库和查询功能完全正确。有关详细信息,请参阅下面的答案。

【问题讨论】:

  • 我将首先使用 Android Studio 的设备文件资源管理器从您的设备或模拟器中复制数据库。接下来,使用 SQLite 客户端(例如,sqlitebrowser.org)来确认您的数据库内容是否符合您的预期。然后,使用该数据库客户端手动执行您的INNER JOIN SQL 并查看是否获得预期结果。如果你这样做了,那么正如 MikeT 所建议的那样,问题可能出在你用 Java 进行的后处理中。如果你不这样做,那么你可以使用 SQL 直到找到满足你想要的 SELECT 语句,然后调整你的 DAO 以匹配。

标签: android database sqlite android-room


【解决方案1】:

我相信您的问题是您如何处理从 getFilteredModelList 方法返回的 UiModel 列表。

考虑为调试目的添加的以下方法(在本示例中,添加到 MainActivity.java)。 :-

public static void logUiModel(List<UiModel> uiModelList) {
    StringBuilder sb = new StringBuilder();
    for (UiModel u: uiModelList) {
        sb.append("\n\tUIModel Person ID = " + String.valueOf(u.getPerson().getPresonID()) + " Person Name = " + u.getPerson().getPersonName());
        for (Tag t: u.getTags()) {
            sb.append("\n\t\tTag ID =" + String.valueOf(t.getTagID()) + " Value = " + t.getTagValue());
        }
    }
    Log.d("UIMODELINFO(LIST)",sb.toString());
}

public static void logUiModel(UiModel uiModel) {
    StringBuilder sb = new StringBuilder().append("\nUIMODEL Person ID = " +String.valueOf(uiModel.getPerson().getPresonID()) + " Person Name = " + uiModel.getPerson().getPersonName());
    for (Tag t: uiModel.getTags()) {
        sb.append("\n\t\tTag ID =" + String.valueOf(t.getTagID()) + " Value = " + t.getTagValue());
    }
    Log.d("UIMODELINFO",sb.toString());
}

public static void logPersons(List<Person> personList) {
    StringBuilder sb = new StringBuilder().append("People are :-");
    for (Person p: personList) {
        sb.append("\n\tPerson ID is " + String.valueOf(p.getPresonID()) + " Name is" + p.getPersonName());
    }
    Log.d("PERSONINFO",sb.toString());
}

public static void logTags(List<Tag> tagList) {
    StringBuilder sb = new StringBuilder().append("Tags are");
    for (Tag t: tagList) {
        sb.append("\n\tTag ID is " + String.valueOf(t.getTagID()) + " Value is " + t.getTagValue());
    }
    Log.d("TAGINFO",sb.toString());
}

getFilteredModelList 方法的以下更改:-

@Transaction
public List<UiModel> getFilteredModelList(String tag) {
    List<UiModel> resultList = new ArrayList<>();
    long tagID = getTagIdByTagName(tag);
    Log.d("GETFLTRMDLLST","Processing TAG where value is " + tag + " ID is " + String.valueOf(tagID));
    List<Person> filteredPersonList = getAllPersonsForTagID(tagID);

    for (Person p : filteredPersonList) {
        Log.d("GETFLTRDMDLLST","Persons are :-");
        MainActivity.logPersons(filteredPersonList);
        List<Tag> associatedTags = getAllTagsForPerson(p.getPresonID());
        Log.d("GETFLTRDMDLLST","Tags are :-");
        MainActivity.logTags(associatedTags);
        UiModel u = new UiModel(p,associatedTags);
        Log.d("GETFLTRDMDLLST","UIModels are :-");
        MainActivity.logUiModel(u);
        resultList.add(u);
    }
    return resultList;
}

以及以下测试代码(同样在 MainActivity.java 中):-

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mPDB = Room.databaseBuilder(this,PersonDatabase.class,"person.db").allowMainThreadQueries().build();
    addDataIfNone();
    List<Person> allPeople = mPDB.personDao().getAllPersons();
    logPersons(allPeople);
    List<Tag> allTags1 = mPDB.personDao().getAllTags();
    logTags(allTags1);
    List<PersonTagRelation> allPTRs = mPDB.personDao().getAllPersonTagRelations();
    logPersonTagRelations(allPTRs);
    List<UiModel> uiModelList = mPDB.personDao().getAllAsModelList();
    StringBuilder sb = new StringBuilder().append("UIMODELS are :-");
    for (UiModel u: uiModelList) {
        sb.append("\n\tPerson ID = " + String.valueOf(u.getPerson().getPresonID()) + " Name = " + u.getPerson().getPersonName());
        for (Tag t: u.getTagList()) {
            sb.append("\n\t\tTag ID = " + String.valueOf(t.getTagID()) + " Value = " + t.getTagValue());
        }
    }
    Log.d("UIMODELINFO",sb.toString());

    List<Tag> allTags = mPDB.personDao().getAllTags();
    List<UiModel> uiListOfModelsViaFilter;
    for (Tag t: allTags) {
        Log.d("TAGFILTERINF","Filtering with a TAG value of " + t.getTagValue() );
        uiListOfModelsViaFilter = mPDB.personDao().getFilteredModelList(String.valueOf(t.getTagValue()));
        logUiModel(uiListOfModelsViaFilter);
    }
}

private void addDataIfNone() {
    List<Person> people = mPDB.personDao().getAllPersons();
    if (people.size() > 0) return;

    Person[] peopleToAdd = new Person[]{
            new Person("A"),
            new Person("B"),
            new Person("C")
    };
    mPDB.personDao().insertNewPersons(peopleToAdd);

    Tag[] tagsToAdd = new Tag[] {
            new Tag("ai"),
            new Tag("ML"),
            new Tag("Flutter"),
            new Tag("JS"),
            new Tag("Kotlin"),
            new Tag("Dart")
    };
    mPDB.personDao().insertNewTags(tagsToAdd);

    PersonTagRelation[] ptr = new PersonTagRelation[]{
            new PersonTagRelation(1,1),
            new PersonTagRelation(1,2),
            new PersonTagRelation(2,1),
            new PersonTagRelation(3,3)
    };
    mPDB.personDao().insertNewRelations(ptr);
}

越相关的结果表明,对于标签 ai,人 AB 被提取:-

06-24 11:27:53.365 9889-9889/? D/TAGFILTERINF: Filtering with a TAG value of ai
06-24 11:27:53.366 9889-9889/? D/GETFLTRMDLLST: Processing TAG where value is ai ID is 1
06-24 11:27:53.367 9889-9889/? D/GETFLTRDMDLLST: Persons are :-
06-24 11:27:53.367 9889-9889/? D/PERSONINFO: People are :-
        Person ID is 1 Name isA
        Person ID is 2 Name isB
06-24 11:27:53.367 9889-9889/? D/GETFLTRDMDLLST: Tags are :-
06-24 11:27:53.367 9889-9889/? D/TAGINFO: Tags are
        Tag ID is 1 Value is ai
        Tag ID is 2 Value is ML
06-24 11:27:53.367 9889-9889/? D/GETFLTRDMDLLST: UIModels are :-
06-24 11:27:53.367 9889-9889/? D/UIMODELINFO: UIMODEL Person ID = 1 Person Name = A
            Tag ID =1 Value = ai
            Tag ID =2 Value = ML
06-24 11:27:53.367 9889-9889/? D/GETFLTRDMDLLST: Persons are :-
06-24 11:27:53.367 9889-9889/? D/PERSONINFO: People are :-
        Person ID is 1 Name isA
        Person ID is 2 Name isB
06-24 11:27:53.367 9889-9889/? D/GETFLTRDMDLLST: Tags are :-
06-24 11:27:53.367 9889-9889/? D/TAGINFO: Tags are
        Tag ID is 1 Value is ai
06-24 11:27:53.367 9889-9889/? D/GETFLTRDMDLLST: UIModels are :-
06-24 11:27:53.367 9889-9889/? D/UIMODELINFO: UIMODEL Person ID = 2 Person Name = B
            Tag ID =1 Value = ai
06-24 11:27:53.369 9889-9889/? D/UIMODELINFO(LIST):     UIModel Person ID = 1 Person Name = A
            Tag ID =1 Value = ai
            Tag ID =2 Value = ML
        UIModel Person ID = 2 Person Name = B
            Tag ID =1 Value = ai 

编辑

这是完整的 PersonTagRelation 类:-

@Entity(primaryKeys = {"personTagIDForPerson","personTagIDForTag"},
        foreignKeys = {
                @ForeignKey(entity = Person.class, parentColumns = "presonID", childColumns = "personTagIDForPerson"),
                @ForeignKey(entity = Tag.class, parentColumns = "tagID", childColumns = "personTagIDForTag"),
        })
public class PersonTagRelation {
    private long personTagIDForPerson, personTagIDForTag;

    public PersonTagRelation() {
    }

    public PersonTagRelation(long personTagIDForPerson, long personTagIDForTag) {
        this.personTagIDForPerson = personTagIDForPerson;
        this.personTagIDForTag = personTagIDForTag;
    }

    public long getPersonTagIDForPerson() {
        return personTagIDForPerson;
    }

    public void setPersonTagIDForPerson(long personTagIDForPerson) {
        this.personTagIDForPerson = personTagIDForPerson;
    }

    public long getPersonTagIDForTag() {
        return personTagIDForTag;
    }

    public void setPersonTagIDForTag(long personTagIDForTag) {
        this.personTagIDForTag = personTagIDForTag;
    }
}
  • 注意采用这两个值的构造函数(以及它在 addDataIfNone 方法中的使用方式)。

【讨论】:

  • 我添加了 logcat。我猜 PersonTagRelations 中的关系实体没有正确添加
  • @anshsachdeva 您需要控制它们的添加方式(请参阅我如何在代码中添加它们,注意我对 id 进行了硬编码(您必须从相应的 Person/Tags 中检索它们)) .
  • @anshsachdeva 我已经编辑了答案并添加了 PersonTagRelation 类,这似乎是您的问题所在。
  • 谢谢,但我实际上使用的是完全相同的 PersonTagRelation.class。为了简洁起见,我只写了一部分。但是查看addDataIfNone 让我意识到我的错误。每次我添加新标签时,我的 UI 都会为标签生成一个唯一 ID!所以如果两个人有相同的标签ai,他们仍然没有任何共同点,因为标签ai对他们两个有不同的ID¯_(ツ)_/¯
【解决方案2】:

事实证明,这是我愚蠢的用户界面的错。我每次添加标签时都会生成一个新 ID,即使它已经存在。因此,如果我的数据库有以下条目(从它的样子来看):

Person --- Tags
A      --- ai ,ml
B      --- ml
C      --- flutter

原来是这样的:

id---person     id---tag        id_person-----------id_tag
101 -  A        11 - ai             101      --       11 
102 -  B        12 - ml             101      --       12
103 -  C        13 - ml //! wrong   102      --       13 //wrong!!  
                14 - flutter        103      --       14

在将标签添加为新标签之前,我的用户界面没有检查标签是否存在。这就是getFilteredModelList() 函数无法生成包含超过 1 个条目的列表的原因。所以我最终为此编写了一个额外的检查器代码。此外,dao 最终进行了以下微小更改(对所有三个内部插入函数进行额外的 onConflict 检查):


@Dao
public abstract class PersonDao {
    private static final String TAG = "personDao>>";

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    abstract void insertNewPerson(Person... p); 

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    abstract void insertNewTag(Tag... t);          

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    abstract void insertNewRelation(PersonTagRelation... relation);  

    ...
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-08-31
    • 2020-03-10
    • 2013-05-22
    • 1970-01-01
    • 2018-06-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多