Cursor中关系数据处理:SQLite多表关联实现RecyclerView人员带宠列表
嘿,这个问题我做Android项目时也碰到过!就是想在列表每个条目里展示一个人加上他的所有宠物,但直接左连接会出多行,多次查询又容易搞砸UI更新时机对吧?下面给你几个完全符合关系数据库规范的解决方案,绝对不用那种加逗号分隔列的歪招:
方案一:用SQLite聚合函数直接生成单人行数据
这是最省事的办法,利用SQLite的GROUP_CONCAT函数,把同一个人的所有宠物信息合并成一个字符串,查询结果天然就是每人一行,完美适配RecyclerView的CursorAdapter。
具体SQL语句如下:
SELECT p.person_id, p.first_name || ' ' || p.last_name AS full_name, GROUP_CONCAT(pet.name || ' (' || pet.breed || ')') AS pets_info FROM person p LEFT JOIN pet ON p.person_id = pet.person_id GROUP BY p.person_id, p.first_name, p.last_name
优势
- 仅需一次查询,拿到的Cursor每一行就是完整的人员+宠物信息条目,直接丢给CursorAdapter即可,不用处理多Cursor或数据拆分问题。
- 可自定义拼接格式,比如把宠物信息改成
"旺财 - 金毛",或者把分隔符换成换行符'\n',在ViewHolder里直接setText就能显示多行宠物信息。
注意点
如果某个用户的宠物特别多,拼接字符串可能过长,但移动端场景下这种情况极少,完全不用担心。真遇到的话,也可以在SQL里加LIMIT子句限制宠物数量。
方案二:内存中组装自定义数据模型(解决方案2的更新时机问题)
如果不想用SQL聚合,想保留更清晰的数据结构,可以把Cursor转成自定义实体类,在后台线程完成所有数据组装,彻底解决Adapter更新时机混乱的问题。
实现步骤
- 定义实体类存储人员和宠物数据:
public class PersonWithPets { public int personId; public String fullName; public List<Pet> pets = new ArrayList<>(); } public class Pet { public String name; public String breed; }
- 在后台线程完成数据组装(比如用Loader的
loadInBackground方法、Coroutine或AsyncTask):- 先查询所有人员:
SELECT * FROM person - 遍历每个人员,根据
person_id查询对应宠物:SELECT name, breed FROM pet WHERE person_id = ? - 将每个宠物添加到对应
PersonWithPets的pets列表中
- 先查询所有人员:
- 所有数据组装完成后,把
List<PersonWithPets>传给RecyclerView的Adapter,调用notifyDataSetChanged()更新UI。
解决原方案问题的核心
原方案2的问题是边查询边更新UI导致时机混乱,现在把所有查询和组装逻辑放到后台线程,等全部数据准备好再一次性更新UI,就不会出现Adapter数据不全或重复更新的情况。用ListAdapter还能利用DiffUtil做局部更新,性能更优。
方案三:用Room Persistence Library(推荐给新项目)
如果你的项目还没用到Room,强烈建议试试!Room是Google官方ORM框架,原生支持一对多关系映射,完全不用手动处理Cursor和数据组装的麻烦事。
快速实现步骤
- 定义实体类:
@Entity(tableName = "person") data class Person( @PrimaryKey val person_id: Int, val first_name: String, val last_name: String ) @Entity(tableName = "pet", foreignKeys = [ForeignKey(entity = Person::class, parentColumns = ["person_id"], childColumns = ["person_id"])]) data class Pet( @PrimaryKey val pet_id: Int, val person_id: Int, val name: String, val breed: String )
- 定义组合数据类关联Person和Pets:
data class PersonWithPets( @Embedded val person: Person, @Relation( parentColumn = "person_id", entityColumn = "person_id" ) val pets: List<Pet> )
- 在Dao中编写查询方法:
@Dao interface PersonDao { @Transaction @Query("SELECT * FROM person") fun getPersonsWithPets(): List<PersonWithPets> }
Room会自动完成所有关联查询和数据组装,返回的是每个Person带着对应Pets列表的List,直接给RecyclerView的ListAdapter用就行,所有操作都在后台线程执行,不会阻塞UI。
内容的提问来源于stack exchange,提问作者ClintG




