Room db与多对多关系:查询过滤数据时返回单个条目

时间:2019-06-23 02:47:07

标签: android database sqlite android-room

我有3个实体PersonPersonTagRelationTag:人员通过多对多关系与标签相关。 我还有一个dao,它具有查询数据库的多种功能。 大多数事情都运行良好,但是我有一个查询,该查询想创建一个具有特定标签的过滤用户列表,该标签无法正常工作。 例如,如果有3个人ABC,其相应标签列表分别为[ai,ml][ai][flutter]。那么我的查询应该返回 [A,B]传递标签ai时。但是它仅返回[A]。  请签出以下代码,让我知道我哪里出了错:

Person.class

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

Tag.class

@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的结果 pic1 pic2

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

2 个答案:

答案 0 :(得分:0)

我相信您的问题是如何处理从 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 而言,提取了人 A B :-

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 方法中如何使用它)。

答案 1 :(得分:0)

事实证明,这是我的愚蠢ui的过错。每次添加标签时,我都会生成一个新的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

我的ui在将其添加为新标签之前没有检查天气是否存在标签。这就是getFilteredModelList()函数无法生成包含多个条目的列表的原因。所以我最终为此写了一个额外的检查代码。此外,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);  

    ...
    }