如何使 ListView 加载 parent 和由 ForeignKey 关联的子项?
How can I make a ListView load parent and childs related by ForeignKey?
现在,我有一个工作的 ListView,它成功加载了 ListView 中的每个“parent”object,检索 parent 属性并在每个元素中显示它们。由于我试图遵循 Android's Model-View-ViewModel architecture patterns,我正在使用 RoomDb、存储库 class 和 ViewModel class(viewmodel class 包含 parent 和children;我想知道这是否是反模式;但这可能是另一个问题的主题)。
无论如何,由于我必须查询检索包含 POJO 列表的 LiveData object,这将我可以在 ListView 上显示的数据限制为仅 parent 的字段,因为 POJO 只包含 parent table 的字段;并查询每个 parentID 以在某种循环中检索所有 child objects 出于某种原因听起来不太好。因此,在互联网上搜索此内容时,我发现(但我不记得在哪里)这种制作某种“关系实体”的方法同时包含 parent 和 children:
import androidx.room.Embedded;
import androidx.room.Relation;
import java.io.Serializable;
import java.util.List;
public class ParentWithChildren implements Serializable {
@Embedded public Parent parent;
@Relation(
parentColumn = "id",
entityColumn = "parent_id"
)
public List<Child> children;
}
然后,在我的 ParentDao 中,我添加了这个:
// XXX The old DAO searcher that already works for retrieving
// just the parent POJOs
@Query("SELECT * FROM parent WHERE parent_field1 = :search OR parent_field2 = :search_no_accents")
LiveData<List<Parent>> searchParents(String search, String search_no_accents);
// XXX The new method I'd like to create
@Transaction
@Query("SELECT * FROM parent WHERE parent_field1 = :search OR parent_field2 = :search_no_accents")
LiveData<List<ParentWithChildren>> searchParentsWithChildren(String search, String search_no_accents);
并且,在我的存储库中 class:
public LiveData<List<ParentWithChildren>> searchParentsWithChildren(String search, String search_no_accents) {
return parent_dao.searchParentsWithChildren(search, search_no_accents);
}
...这是我的 ParentViewModel 从存储库中检索列表的方法:
public LiveData<List<ParentWithChildren>> searchParentsWithChildren(String search) {
// I remove accents from user search with and ad-hoc method
String search_no_accents = removeAccents(search);
return repository.searchParentsWithChildren(search, search_no_accents);
}
在我的 MainActivity 中,我有一个处理用户搜索的 SearchView:
SearchView search_view = findViewById(R.id.searchView);
search_view.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
if (query.equals("")) {
loadFullListFragment();
} else {
search_results = parent_viewmodel.searchParents(query);
//search_with_children = parent_viewmodel.searchParentsWithChildren(query);
observeSearchResults(search_results);
//observeSearchResults(search_with_children);
}
return true;
}
});
我的 observeSearchResults
方法,也在 MainActivity 中,根据结果列表的大小加载适当的片段:
private void observeSearchResults(LiveData<List<Parent>> parents_results) {
//private void observeSearchResults(LiveData<List<ParentWithChildren>> parents_with_children) {
parents_results.observe(this, new Observer<List<Parent>>() {
//parents_with_children.observe(this, new Observer<List<ParentWithChildren>>() {
@Override
//public void onChanged(List<ParentWithChildren> parents) {
public void onChanged(List<Parent> parents) {
if ( parents.size() > 1 ) {
// ## List view of multiple results
listFragment = ParentsListFragment.newInstance( (ArrayList)parents );
transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragmentContainer, listFragment);
transaction.addToBackStack(null);
transaction.commit();
}
else if ( parents.size() > 0 ) {
// TODO XXX This is kinda hacky
loadParentProfileFragment(parents.get(0));
} else {
transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragmentContainer, insertFragment);
transaction.addToBackStack(null);
transaction.commit();
}
}
});
}
嗯,你看到那 commented-out 行代码了吗? 这是我要介绍的新更改,以开始使用新的 ParentWithChildren 实体。 它们是 commented-out,因为它们正上方或下方的那些已经在努力在 ListView 中显示 parent 的 object。因此,当我尝试实施更改时,我 comment-out 旧的和“dis-comment-out”具有 ParentWithChildren
更改的那些。我必须更改这么多 classes 才能实现 一个更改 ...嗯...这里闻起来像 Shotgun Surgery...:-(
...好吧,长话短说,我尝试在加载 ListView 的片段中将之前的 List<Parent>
替换为 List<ParentWithChildren>
,因此每个列表元素也将能够检索parent 相关 child object 并显示它们。但是,上次我尝试以这种方式实现 ParentWithChildren
实体时,它因以下异常而崩溃(这绝对让我大吃一惊):
java.lang.NoClassDefFoundError: Failed resolution of: com/mydomain/myapp/daos/ParentDao_Impl;
这个异常的Traceback从这个auto-generatedParentDao_Implement
的某行开始,Traceback的下一行指向我的新Repository.searchParentsWithChildren
方法。 我完全不知道是我做错了,还是怎么处理这个奇怪的故障。
我的 ArrayAdapter 以及我希望它变成的样子
这个片段显示了我当前工作的 ArrayAdapter,commented-out 行代码或多或少显示了我想要引入的更改:
public class ParentsArrayAdapter extends ArrayAdapter<Parent> {
//public class ParentsArrayAdapter extends ArrayAdapter<ParentWithChildren> {
//public ParentsArrayAdapter(Context context, List<ParentWithChildren> parents) {
public ParentsArrayAdapter(Context context, List<Parent> parents) {
super(context, 0, parents);
}
@Override
public View getView(int position, View convertView, ViewGroup vgroup) {
// Get the data item for this position
Parent parent = getItem(position);
//ParentWithChildren parent = getItem(position);
// XXX TODO Load child attributes list
// Check if an existing view is being reused, otherwise inflate the view
if (convertView == null) {
convertView = LayoutInflater.from(getContext())
.inflate(R.layout.listview_parents, vgroup, false);
}
// Lookup view for data population
TextView parentField1 = convertView.findViewById(R.id.list_parentField1);
// Populate the data into the template view using the data object
parentField1.setText(parent.getField1());
//parentField1.setText(parent.parent.getField1());
TextView parentField2 = convertView.findViewById(R.id.list_parentField2);
parentField2.setText(parent.getField2());
//pinyinText.setText(parent.parent.getField2());
// TODO Load first 5-10 childs here (when ParentWithChildren works)
TextView childrenCommaListField = convertView.findViewById(R.id.list_childsCommaList);
/*
¿¿?? SebasSBM is confused
*/
// Return the completed view to render on screen
return convertView;
}
}
在 PC 应用程序中,这就像做某种 sqlSELECT p.*, c.* FROM parent AS p INNER JOIN child as c ON p.id = c.parent_id WHERE bla bla bla
一样简单:但是对于 RoomDB,我感觉关系数据库的一切都复杂得多。
那么,如何让 ListView 在每个 parent 列表的元素中加载 children 的字段,使其看起来或多或少是这样的?感谢您的帮助:
sorry, I am terrible making examples sometimes
------------
parent1 ---> parent_field1 ---> child1, child2, child3
------------
parent2 ---> parent_field1 ---> child4, child5, child6
数据库的Table结构(或多或少)
Parent table
+------+--------+--------+-
| ID | field1 | field2 | [...]
+------+--------+--------+-
| | | |
+------+--------+--------+-
Child table (relation One2Many by FK)
+------+-----------+--------+---------+-
| ID | parent_id | field1 | field2 | [...]
+------+-----------+--------+---------+-
| | | | |
+------+-----------+--------+---------+-
In a PC app, this would be as simple as doing some kind of sql SELECT p., c. FROM parent AS p INNER JOIN child as c ON p.id = c.parent_id WHERE bla bla bla: but with RoomDB I have the sensation everything with relational databases is way much more complicated.
您可以对房间执行此操作,只需拥有一个具有
的 POJO
@Embedded
Parent parent;
@Embedded
Child child;
如果有任何列名与您需要消除列歧义的列名相同,这会变得有点困难。你可以使用@Embedded(prefix = "a suitable prefix").
这是一个工作示例。
Parentclass/entity:-
@Entity
class Parent {
public static final String PREFIX = "parent_";
@PrimaryKey
Long id=null;
String name;
Parent(){}
@Ignore
Parent(String name) {
this.name = name;
}
}
Childclass/entity:-
@Entity
class Child {
public static final String PREFIX = "child_";
@PrimaryKey
Long id = null;
long parent;
String name;
Child(){}
@Ignore
Child(long parent,String name) {
this.parent = parent;
this.name = name;
}
}
加入 parent 与 child 的笛卡尔积的 POJO :-
class ParentChildCartesian {
@Embedded(prefix = Parent.PREFIX)
Parent parent;
@Embedded(prefix = Child.PREFIX)
Child child;
}
Dao 的(抽象 class 而不是接口,如此抽象....对于 dao 方法):-
@Dao
abstract class AllDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(Parent parent);
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(Child child);
@Query("SELECT parent.id AS " + Parent.PREFIX + "id, " +
"parent.name AS " + Parent.PREFIX + "name, " +
"child.id AS " + Child.PREFIX + "id," +
"child.parent AS " + Child.PREFIX + "parent," +
"child.name AS " + Child.PREFIX + "name " +
" FROM parent JOIN child ON child.parent = parent.id"
)
abstract List<ParentChildCartesian> getParentChildCartesianList();
}
显然是一个合适的@Database class,这里是TheDatabase(不需要显示)。
最后,演示上面的使用:-
db = TheDatabase.getInstance(this);
dao = db.getAllDao();
long p1 = dao.insert(new Parent("Parent1"));
long p2 = dao.insert(new Parent("Parent2"));
dao.insert(new Child(p1,"Child1"));
dao.insert(new Child(p1,"Child2"));
dao.insert(new Child(p1,"Child3"));
dao.insert(new Child(p2,"Child4"));
for(ParentChildCartesian pc: dao.getParentChildCartesianList()) {
Log.d(
"DBINFO",
"Parent name is " + pc.parent.name + " id is" + pc.parent.id +
". Child name is " + pc.child.name + " id is " + pc.child.id + " parent id is " + pc.child.parent);
}
日志中的结果:-
D/DBINFO: Parent name is Parent1 id is1. Child name is Child1 id is 1 parent id is 1
D/DBINFO: Parent name is Parent1 id is1. Child name is Child2 id is 2 parent id is 1
D/DBINFO: Parent name is Parent1 id is1. Child name is Child3 id is 3 parent id is 1
D/DBINFO: Parent name is Parent2 id is2. Child name is Child4 id is 4 parent id is 2
但是,我认为您希望所有 children 的名字与 parent 排在一行中。这可以利用上面的 PJO,但是 GROUP 的数据允许使用 group_concat 聚合函数的不同查询,例如:-
@Query("SELECT parent.id AS " + Parent.PREFIX + "id, " +
"parent.name AS " + Parent.PREFIX + "name, " +
"child.id AS " + Child.PREFIX + "id," + /* note will be an arbitrary id */
"child.parent AS " + Child.PREFIX + "parent," +
"group_concat(child.name) AS " + Child.PREFIX + "name " +
"FROM parent " +
"JOIN child ON child.parent = parent.id " +
"GROUP BY parent.id"
)
abstract List<ParentChildCartesian> getParentwithCSV();
与 :-
一起使用时
for (ParentChildCartesian pc: dao.getParentwithCSV()) {
Log.d(
"DBINFO",
"Parent is " + pc.parent.name+ " children are " + pc.child.name
);
}
日志包括:-
D/DBINFO: Parent is Parent1 children are Child1,Child2,Child3
D/DBINFO: Parent is Parent2 children are Child4
不用@Embedded,方便包含object.
的成员变量
所以考虑 POJO :-
class WithoutEmbedded {
long parentId;
String parentName;
String childrenNameCSV;
String childrenIdCSV;
}
您必须将成员变量名称与列名称相匹配,这样您就可以拥有,例如:-
@Query("SELECT parent.id AS parentId, " +
"parent.name AS parentName, " +
"group_concat(child.name) AS childrenNameCSV, " +
"group_concat(child.id) AS childrenIdCSV " +
"FROM parent " +
"JOIN child ON child.parent = parent.id " +
"GROUP BY parent.id"
)
abstract List<WithoutEmbedded> getParentsWithchildrenCSVandChildrenIdCSV();
使用:-
for(WithoutEmbedded pc: dao.getParentsWithchildrenCSVandChildrenIdCSV()) {
Log.d(
"DBINFO",
"Parent is " + pc.parentName + " children are " + pc.childrenNameCSV + " children ids are " + pc.childrenIdCSV
);
}
日志中的结果将是:-
D/DBINFO: Parent is Parent1 children are Child1,Child2,Child3 children ids are 1,2,3
D/DBINFO: Parent is Parent2 children are Child4 children ids are 4
简而言之,@Relation 通过对所有相关的 children 使用基础查询来实现相当于连接的功能,因此建议使用 @Transaction。
虽然@Embedded 期望查询结果具有与嵌入式classes 的成员变量名称相匹配的列,因此可以使用JOINS(包括过滤)。因为这一切都在一个查询中完成,所以不需要 @Transaction。
@Embedded 基本上是一种便利,因此可以通过手动包含成员变量来省略。 (同样不需要@Transaction)。
额外
重新评论。现在你想要什么更清楚了。简而言之,您的原始问题就是线索。您可以根据需要使用@Embedded 和@Realtion(如果我理解正确的话)。
例如来自一个工作示例:-
Mary 和 Fred 是 parents(由于几次测试运行而重复行)。
适配器应遵循 :-
class MyAdapter extends ArrayAdapter<ParentWithChildren> ....
其中 ParentWithChildren 是 @Embedded .... @Realation POJO 例如:-
class ParentWithChildren {
@Embedded
Parent parent;
@Relation(
entity = Child.class,
parentColumn = Parent.COL_PARENT_ID,
entityColumn = Child.COL_PARENT_MAP
)
List<Child> children;
}
Child 是(在示例的情况下):-
@Entity(tableName = Child.TABLE_NAME,
foreignKeys = {
@ForeignKey(
entity = Parent.class,
parentColumns = {Parent.COL_PARENT_ID},
childColumns = {Child.COL_PARENT_MAP},
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
)
}
)
class Child {
public static final String TABLE_NAME = "child";
public static final String COL_CHILD_ID = "childid";
public static final String COL_PARENT_MAP = "parentmap";
public static final String COL_CHILD_NAME = "childname";
@PrimaryKey
@ColumnInfo(name = COL_CHILD_ID)
Long childId = null;
@ColumnInfo(name = COL_PARENT_MAP)
Long parentMap;
@ColumnInfo(name = COL_CHILD_NAME, index = true)
String childName;
Child(){}
@Ignore
Child(String name, long parentMap) {
this.childName = name;
this.parentMap = parentMap;
}
}
和Parent:-
@Entity(tableName = Parent.TABLE_NAME)
class Parent {
public static final String TABLE_NAME = "parent";
public static final String COL_PARENT_ID = "parentid";
public static final String COL_PARENT_NAME = "parentname";
@PrimaryKey
@ColumnInfo(name = COL_PARENT_ID)
Long parentId=null;
@ColumnInfo(name = COL_PARENT_NAME, index = true)
String parentName;
Parent(){}
@Ignore
Parent(String name) {
this.parentName = name;
}
}
@Dao class AllDao :-
@Dao
abstract class AllDao {
@Insert
abstract long insert(Parent parent);
@Insert
abstract long insert(Child child);
@Transaction
@Query("SELECT * FROM parent")
abstract List<ParentWithChildren> getAllParentsWithChildren();
}
@Database TheDatabase 是:-
@Database(entities = {Parent.class,Child.class},version = TheDatabase.DATABASE_VERSION)
abstract class TheDatabase extends RoomDatabase {
abstract AllDao getAllDao();
public static final String DATABASE_NAME = "my.db";
public static final int DATABASE_VERSION = 1;
private static volatile TheDatabase instance = null;
public static TheDatabase getInstance(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(context,TheDatabase.class,DATABASE_NAME)
.allowMainThreadQueries()
.build();
}
return instance;
}
}
适配器的布局 parentwithchildre.xml 最多 5 children :-
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/parent_name"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
>
</TextView>
<TextView
android:id="@+id/child1"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
>
</TextView>
<TextView
android:id="@+id/child2"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
>
</TextView>
<TextView
android:id="@+id/child3"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
>
</TextView>
<TextView
android:id="@+id/child4"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
>
</TextView>
<TextView
android:id="@+id/child5"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
>
</TextView>
</LinearLayout>
activity MainActity 是:-
public class MainActivity extends AppCompatActivity {
TheDatabase db;
AllDao dao;
ListView lv1;
MyAdapter adapter;
List<ParentWithChildren> baseList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lv1 = this.findViewById(R.id.lv1);
db = TheDatabase.getInstance(this);
dao = db.getAllDao();
long p1 = dao.insert(new Parent("Mary"));
long p2 = dao.insert(new Parent("Fred"));
dao.insert(new Child("Tom",p1));
dao.insert(new Child("Alice",p1));
dao.insert(new Child("Jane",p1));
dao.insert(new Child("Bob",p1));
dao.insert(new Child("Susan",p2));
dao.insert(new Child("Alan",p2));
setOrRefreshAdapter();
setOrRefreshAdapter();
}
private void setOrRefreshAdapter() {
baseList = dao.getAllParentsWithChildren();
if (adapter == null) {
adapter = new MyAdapter(this,R.layout.parentwithchildren, baseList);
lv1.setAdapter(adapter);
} else {
adapter.clear();
adapter.addAll(baseList);
adapter.notifyDataSetChanged();
}
}
}
最后也是大多数 MyAdapter :-
class MyAdapter extends ArrayAdapter<ParentWithChildren> {
public MyAdapter(@NonNull Context context, int resource, @NonNull List<ParentWithChildren> objects) {
super(context, resource, objects);
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
ParentWithChildren pwc = getItem(position);
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(
R.layout.parentwithchildren,parent,false
);
TextView parent_textview = convertView.findViewById(R.id.parent_name);
parent_textview.setText(pwc.parent.parentName);
TextView child1_textview = convertView.findViewById(R.id.child1);
TextView child2_textview = convertView.findViewById(R.id.child2);
TextView child3_textview = convertView.findViewById(R.id.child3);
TextView child4_textview = convertView.findViewById(R.id.child4);
TextView child5_textview = convertView.findViewById(R.id.child5);
if(pwc.children.size() >= 1) {
child1_textview.setText(pwc.children.get(0).childName);
}
if (pwc.children.size() >= 2) {
child2_textview.setText(pwc.children.get(1).childName);
}
if (pwc.children.size() >=3) {
child3_textview.setText(pwc.children.get(2).childName);
}
if (pwc.children.size() >= 4) {
child4_textview.setText(pwc.children.get(3).childName);
}
if (pwc.children.size() >= 5) {
child5_textview.setText(pwc.children.get(4).childName);
}
}
return convertView;
//return super.getView(position, convertView, parent);
}
}
因此,在 getView 中传递一个列表后,您会得到一个 ParentWithChild object,下面它被命名为 pwc,如下所示ParentWithChildren pwc = getItem(position);
(即列表中第n个ParentWithChildren)
parent 的名字是 pwc.parent.parentName
children的名字是通过:-
获得的
pwc.children.get(?).childName
在哪里?是 children 数的数字 0,但您会将值限制为观看次数 - 1.
现在,我有一个工作的 ListView,它成功加载了 ListView 中的每个“parent”object,检索 parent 属性并在每个元素中显示它们。由于我试图遵循 Android's Model-View-ViewModel architecture patterns,我正在使用 RoomDb、存储库 class 和 ViewModel class(viewmodel class 包含 parent 和children;我想知道这是否是反模式;但这可能是另一个问题的主题)。
无论如何,由于我必须查询检索包含 POJO 列表的 LiveData object,这将我可以在 ListView 上显示的数据限制为仅 parent 的字段,因为 POJO 只包含 parent table 的字段;并查询每个 parentID 以在某种循环中检索所有 child objects 出于某种原因听起来不太好。因此,在互联网上搜索此内容时,我发现(但我不记得在哪里)这种制作某种“关系实体”的方法同时包含 parent 和 children:
import androidx.room.Embedded;
import androidx.room.Relation;
import java.io.Serializable;
import java.util.List;
public class ParentWithChildren implements Serializable {
@Embedded public Parent parent;
@Relation(
parentColumn = "id",
entityColumn = "parent_id"
)
public List<Child> children;
}
然后,在我的 ParentDao 中,我添加了这个:
// XXX The old DAO searcher that already works for retrieving
// just the parent POJOs
@Query("SELECT * FROM parent WHERE parent_field1 = :search OR parent_field2 = :search_no_accents")
LiveData<List<Parent>> searchParents(String search, String search_no_accents);
// XXX The new method I'd like to create
@Transaction
@Query("SELECT * FROM parent WHERE parent_field1 = :search OR parent_field2 = :search_no_accents")
LiveData<List<ParentWithChildren>> searchParentsWithChildren(String search, String search_no_accents);
并且,在我的存储库中 class:
public LiveData<List<ParentWithChildren>> searchParentsWithChildren(String search, String search_no_accents) {
return parent_dao.searchParentsWithChildren(search, search_no_accents);
}
...这是我的 ParentViewModel 从存储库中检索列表的方法:
public LiveData<List<ParentWithChildren>> searchParentsWithChildren(String search) {
// I remove accents from user search with and ad-hoc method
String search_no_accents = removeAccents(search);
return repository.searchParentsWithChildren(search, search_no_accents);
}
在我的 MainActivity 中,我有一个处理用户搜索的 SearchView:
SearchView search_view = findViewById(R.id.searchView);
search_view.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
if (query.equals("")) {
loadFullListFragment();
} else {
search_results = parent_viewmodel.searchParents(query);
//search_with_children = parent_viewmodel.searchParentsWithChildren(query);
observeSearchResults(search_results);
//observeSearchResults(search_with_children);
}
return true;
}
});
我的 observeSearchResults
方法,也在 MainActivity 中,根据结果列表的大小加载适当的片段:
private void observeSearchResults(LiveData<List<Parent>> parents_results) {
//private void observeSearchResults(LiveData<List<ParentWithChildren>> parents_with_children) {
parents_results.observe(this, new Observer<List<Parent>>() {
//parents_with_children.observe(this, new Observer<List<ParentWithChildren>>() {
@Override
//public void onChanged(List<ParentWithChildren> parents) {
public void onChanged(List<Parent> parents) {
if ( parents.size() > 1 ) {
// ## List view of multiple results
listFragment = ParentsListFragment.newInstance( (ArrayList)parents );
transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragmentContainer, listFragment);
transaction.addToBackStack(null);
transaction.commit();
}
else if ( parents.size() > 0 ) {
// TODO XXX This is kinda hacky
loadParentProfileFragment(parents.get(0));
} else {
transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragmentContainer, insertFragment);
transaction.addToBackStack(null);
transaction.commit();
}
}
});
}
嗯,你看到那 commented-out 行代码了吗? 这是我要介绍的新更改,以开始使用新的 ParentWithChildren 实体。 它们是 commented-out,因为它们正上方或下方的那些已经在努力在 ListView 中显示 parent 的 object。因此,当我尝试实施更改时,我 comment-out 旧的和“dis-comment-out”具有 ParentWithChildren
更改的那些。我必须更改这么多 classes 才能实现 一个更改 ...嗯...这里闻起来像 Shotgun Surgery...:-(
...好吧,长话短说,我尝试在加载 ListView 的片段中将之前的 List<Parent>
替换为 List<ParentWithChildren>
,因此每个列表元素也将能够检索parent 相关 child object 并显示它们。但是,上次我尝试以这种方式实现 ParentWithChildren
实体时,它因以下异常而崩溃(这绝对让我大吃一惊):
java.lang.NoClassDefFoundError: Failed resolution of: com/mydomain/myapp/daos/ParentDao_Impl;
这个异常的Traceback从这个auto-generatedParentDao_Implement
的某行开始,Traceback的下一行指向我的新Repository.searchParentsWithChildren
方法。 我完全不知道是我做错了,还是怎么处理这个奇怪的故障。
我的 ArrayAdapter 以及我希望它变成的样子
这个片段显示了我当前工作的 ArrayAdapter,commented-out 行代码或多或少显示了我想要引入的更改:
public class ParentsArrayAdapter extends ArrayAdapter<Parent> {
//public class ParentsArrayAdapter extends ArrayAdapter<ParentWithChildren> {
//public ParentsArrayAdapter(Context context, List<ParentWithChildren> parents) {
public ParentsArrayAdapter(Context context, List<Parent> parents) {
super(context, 0, parents);
}
@Override
public View getView(int position, View convertView, ViewGroup vgroup) {
// Get the data item for this position
Parent parent = getItem(position);
//ParentWithChildren parent = getItem(position);
// XXX TODO Load child attributes list
// Check if an existing view is being reused, otherwise inflate the view
if (convertView == null) {
convertView = LayoutInflater.from(getContext())
.inflate(R.layout.listview_parents, vgroup, false);
}
// Lookup view for data population
TextView parentField1 = convertView.findViewById(R.id.list_parentField1);
// Populate the data into the template view using the data object
parentField1.setText(parent.getField1());
//parentField1.setText(parent.parent.getField1());
TextView parentField2 = convertView.findViewById(R.id.list_parentField2);
parentField2.setText(parent.getField2());
//pinyinText.setText(parent.parent.getField2());
// TODO Load first 5-10 childs here (when ParentWithChildren works)
TextView childrenCommaListField = convertView.findViewById(R.id.list_childsCommaList);
/*
¿¿?? SebasSBM is confused
*/
// Return the completed view to render on screen
return convertView;
}
}
在 PC 应用程序中,这就像做某种 sqlSELECT p.*, c.* FROM parent AS p INNER JOIN child as c ON p.id = c.parent_id WHERE bla bla bla
一样简单:但是对于 RoomDB,我感觉关系数据库的一切都复杂得多。
那么,如何让 ListView 在每个 parent 列表的元素中加载 children 的字段,使其看起来或多或少是这样的?感谢您的帮助:
sorry, I am terrible making examples sometimes
------------
parent1 ---> parent_field1 ---> child1, child2, child3
------------
parent2 ---> parent_field1 ---> child4, child5, child6
数据库的Table结构(或多或少)
Parent table
+------+--------+--------+-
| ID | field1 | field2 | [...]
+------+--------+--------+-
| | | |
+------+--------+--------+-
Child table (relation One2Many by FK)
+------+-----------+--------+---------+-
| ID | parent_id | field1 | field2 | [...]
+------+-----------+--------+---------+-
| | | | |
+------+-----------+--------+---------+-
In a PC app, this would be as simple as doing some kind of sql SELECT p., c. FROM parent AS p INNER JOIN child as c ON p.id = c.parent_id WHERE bla bla bla: but with RoomDB I have the sensation everything with relational databases is way much more complicated.
您可以对房间执行此操作,只需拥有一个具有
的 POJO@Embedded
Parent parent;
@Embedded
Child child;
如果有任何列名与您需要消除列歧义的列名相同,这会变得有点困难。你可以使用@Embedded(prefix = "a suitable prefix").
这是一个工作示例。
Parentclass/entity:-
@Entity
class Parent {
public static final String PREFIX = "parent_";
@PrimaryKey
Long id=null;
String name;
Parent(){}
@Ignore
Parent(String name) {
this.name = name;
}
}
Childclass/entity:-
@Entity
class Child {
public static final String PREFIX = "child_";
@PrimaryKey
Long id = null;
long parent;
String name;
Child(){}
@Ignore
Child(long parent,String name) {
this.parent = parent;
this.name = name;
}
}
加入 parent 与 child 的笛卡尔积的 POJO :-
class ParentChildCartesian {
@Embedded(prefix = Parent.PREFIX)
Parent parent;
@Embedded(prefix = Child.PREFIX)
Child child;
}
Dao 的(抽象 class 而不是接口,如此抽象....对于 dao 方法):-
@Dao
abstract class AllDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(Parent parent);
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(Child child);
@Query("SELECT parent.id AS " + Parent.PREFIX + "id, " +
"parent.name AS " + Parent.PREFIX + "name, " +
"child.id AS " + Child.PREFIX + "id," +
"child.parent AS " + Child.PREFIX + "parent," +
"child.name AS " + Child.PREFIX + "name " +
" FROM parent JOIN child ON child.parent = parent.id"
)
abstract List<ParentChildCartesian> getParentChildCartesianList();
}
显然是一个合适的@Database class,这里是TheDatabase(不需要显示)。
最后,演示上面的使用:-
db = TheDatabase.getInstance(this);
dao = db.getAllDao();
long p1 = dao.insert(new Parent("Parent1"));
long p2 = dao.insert(new Parent("Parent2"));
dao.insert(new Child(p1,"Child1"));
dao.insert(new Child(p1,"Child2"));
dao.insert(new Child(p1,"Child3"));
dao.insert(new Child(p2,"Child4"));
for(ParentChildCartesian pc: dao.getParentChildCartesianList()) {
Log.d(
"DBINFO",
"Parent name is " + pc.parent.name + " id is" + pc.parent.id +
". Child name is " + pc.child.name + " id is " + pc.child.id + " parent id is " + pc.child.parent);
}
日志中的结果:-
D/DBINFO: Parent name is Parent1 id is1. Child name is Child1 id is 1 parent id is 1
D/DBINFO: Parent name is Parent1 id is1. Child name is Child2 id is 2 parent id is 1
D/DBINFO: Parent name is Parent1 id is1. Child name is Child3 id is 3 parent id is 1
D/DBINFO: Parent name is Parent2 id is2. Child name is Child4 id is 4 parent id is 2
但是,我认为您希望所有 children 的名字与 parent 排在一行中。这可以利用上面的 PJO,但是 GROUP 的数据允许使用 group_concat 聚合函数的不同查询,例如:-
@Query("SELECT parent.id AS " + Parent.PREFIX + "id, " +
"parent.name AS " + Parent.PREFIX + "name, " +
"child.id AS " + Child.PREFIX + "id," + /* note will be an arbitrary id */
"child.parent AS " + Child.PREFIX + "parent," +
"group_concat(child.name) AS " + Child.PREFIX + "name " +
"FROM parent " +
"JOIN child ON child.parent = parent.id " +
"GROUP BY parent.id"
)
abstract List<ParentChildCartesian> getParentwithCSV();
与 :-
一起使用时 for (ParentChildCartesian pc: dao.getParentwithCSV()) {
Log.d(
"DBINFO",
"Parent is " + pc.parent.name+ " children are " + pc.child.name
);
}
日志包括:-
D/DBINFO: Parent is Parent1 children are Child1,Child2,Child3
D/DBINFO: Parent is Parent2 children are Child4
不用@Embedded,方便包含object.
的成员变量所以考虑 POJO :-
class WithoutEmbedded {
long parentId;
String parentName;
String childrenNameCSV;
String childrenIdCSV;
}
您必须将成员变量名称与列名称相匹配,这样您就可以拥有,例如:-
@Query("SELECT parent.id AS parentId, " +
"parent.name AS parentName, " +
"group_concat(child.name) AS childrenNameCSV, " +
"group_concat(child.id) AS childrenIdCSV " +
"FROM parent " +
"JOIN child ON child.parent = parent.id " +
"GROUP BY parent.id"
)
abstract List<WithoutEmbedded> getParentsWithchildrenCSVandChildrenIdCSV();
使用:-
for(WithoutEmbedded pc: dao.getParentsWithchildrenCSVandChildrenIdCSV()) {
Log.d(
"DBINFO",
"Parent is " + pc.parentName + " children are " + pc.childrenNameCSV + " children ids are " + pc.childrenIdCSV
);
}
日志中的结果将是:-
D/DBINFO: Parent is Parent1 children are Child1,Child2,Child3 children ids are 1,2,3
D/DBINFO: Parent is Parent2 children are Child4 children ids are 4
简而言之,@Relation 通过对所有相关的 children 使用基础查询来实现相当于连接的功能,因此建议使用 @Transaction。
虽然@Embedded 期望查询结果具有与嵌入式classes 的成员变量名称相匹配的列,因此可以使用JOINS(包括过滤)。因为这一切都在一个查询中完成,所以不需要 @Transaction。
@Embedded 基本上是一种便利,因此可以通过手动包含成员变量来省略。 (同样不需要@Transaction)。
额外
重新评论。现在你想要什么更清楚了。简而言之,您的原始问题就是线索。您可以根据需要使用@Embedded 和@Realtion(如果我理解正确的话)。
例如来自一个工作示例:-
Mary 和 Fred 是 parents(由于几次测试运行而重复行)。
适配器应遵循 :-
class MyAdapter extends ArrayAdapter<ParentWithChildren> ....
其中 ParentWithChildren 是 @Embedded .... @Realation POJO 例如:-
class ParentWithChildren {
@Embedded
Parent parent;
@Relation(
entity = Child.class,
parentColumn = Parent.COL_PARENT_ID,
entityColumn = Child.COL_PARENT_MAP
)
List<Child> children;
}
Child 是(在示例的情况下):-
@Entity(tableName = Child.TABLE_NAME,
foreignKeys = {
@ForeignKey(
entity = Parent.class,
parentColumns = {Parent.COL_PARENT_ID},
childColumns = {Child.COL_PARENT_MAP},
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
)
}
)
class Child {
public static final String TABLE_NAME = "child";
public static final String COL_CHILD_ID = "childid";
public static final String COL_PARENT_MAP = "parentmap";
public static final String COL_CHILD_NAME = "childname";
@PrimaryKey
@ColumnInfo(name = COL_CHILD_ID)
Long childId = null;
@ColumnInfo(name = COL_PARENT_MAP)
Long parentMap;
@ColumnInfo(name = COL_CHILD_NAME, index = true)
String childName;
Child(){}
@Ignore
Child(String name, long parentMap) {
this.childName = name;
this.parentMap = parentMap;
}
}
和Parent:-
@Entity(tableName = Parent.TABLE_NAME)
class Parent {
public static final String TABLE_NAME = "parent";
public static final String COL_PARENT_ID = "parentid";
public static final String COL_PARENT_NAME = "parentname";
@PrimaryKey
@ColumnInfo(name = COL_PARENT_ID)
Long parentId=null;
@ColumnInfo(name = COL_PARENT_NAME, index = true)
String parentName;
Parent(){}
@Ignore
Parent(String name) {
this.parentName = name;
}
}
@Dao class AllDao :-
@Dao
abstract class AllDao {
@Insert
abstract long insert(Parent parent);
@Insert
abstract long insert(Child child);
@Transaction
@Query("SELECT * FROM parent")
abstract List<ParentWithChildren> getAllParentsWithChildren();
}
@Database TheDatabase 是:-
@Database(entities = {Parent.class,Child.class},version = TheDatabase.DATABASE_VERSION)
abstract class TheDatabase extends RoomDatabase {
abstract AllDao getAllDao();
public static final String DATABASE_NAME = "my.db";
public static final int DATABASE_VERSION = 1;
private static volatile TheDatabase instance = null;
public static TheDatabase getInstance(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(context,TheDatabase.class,DATABASE_NAME)
.allowMainThreadQueries()
.build();
}
return instance;
}
}
适配器的布局 parentwithchildre.xml 最多 5 children :-
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/parent_name"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
>
</TextView>
<TextView
android:id="@+id/child1"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
>
</TextView>
<TextView
android:id="@+id/child2"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
>
</TextView>
<TextView
android:id="@+id/child3"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
>
</TextView>
<TextView
android:id="@+id/child4"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
>
</TextView>
<TextView
android:id="@+id/child5"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
>
</TextView>
</LinearLayout>
activity MainActity 是:-
public class MainActivity extends AppCompatActivity {
TheDatabase db;
AllDao dao;
ListView lv1;
MyAdapter adapter;
List<ParentWithChildren> baseList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lv1 = this.findViewById(R.id.lv1);
db = TheDatabase.getInstance(this);
dao = db.getAllDao();
long p1 = dao.insert(new Parent("Mary"));
long p2 = dao.insert(new Parent("Fred"));
dao.insert(new Child("Tom",p1));
dao.insert(new Child("Alice",p1));
dao.insert(new Child("Jane",p1));
dao.insert(new Child("Bob",p1));
dao.insert(new Child("Susan",p2));
dao.insert(new Child("Alan",p2));
setOrRefreshAdapter();
setOrRefreshAdapter();
}
private void setOrRefreshAdapter() {
baseList = dao.getAllParentsWithChildren();
if (adapter == null) {
adapter = new MyAdapter(this,R.layout.parentwithchildren, baseList);
lv1.setAdapter(adapter);
} else {
adapter.clear();
adapter.addAll(baseList);
adapter.notifyDataSetChanged();
}
}
}
最后也是大多数 MyAdapter :-
class MyAdapter extends ArrayAdapter<ParentWithChildren> {
public MyAdapter(@NonNull Context context, int resource, @NonNull List<ParentWithChildren> objects) {
super(context, resource, objects);
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
ParentWithChildren pwc = getItem(position);
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(
R.layout.parentwithchildren,parent,false
);
TextView parent_textview = convertView.findViewById(R.id.parent_name);
parent_textview.setText(pwc.parent.parentName);
TextView child1_textview = convertView.findViewById(R.id.child1);
TextView child2_textview = convertView.findViewById(R.id.child2);
TextView child3_textview = convertView.findViewById(R.id.child3);
TextView child4_textview = convertView.findViewById(R.id.child4);
TextView child5_textview = convertView.findViewById(R.id.child5);
if(pwc.children.size() >= 1) {
child1_textview.setText(pwc.children.get(0).childName);
}
if (pwc.children.size() >= 2) {
child2_textview.setText(pwc.children.get(1).childName);
}
if (pwc.children.size() >=3) {
child3_textview.setText(pwc.children.get(2).childName);
}
if (pwc.children.size() >= 4) {
child4_textview.setText(pwc.children.get(3).childName);
}
if (pwc.children.size() >= 5) {
child5_textview.setText(pwc.children.get(4).childName);
}
}
return convertView;
//return super.getView(position, convertView, parent);
}
}
因此,在 getView 中传递一个列表后,您会得到一个 ParentWithChild object,下面它被命名为 pwc,如下所示ParentWithChildren pwc = getItem(position);
(即列表中第n个ParentWithChildren)
parent 的名字是 pwc.parent.parentName
children的名字是通过:-
获得的 pwc.children.get(?).childName
在哪里?是 children 数的数字 0,但您会将值限制为观看次数 - 1.