如何在Android Room中实现可复用的邻接列表数据库?
Android Room 通用邻接列表实现方案
核心思路
针对项目、任务的嵌套结构需求,通过抽象基类+子类继承实现通用邻接列表实体,DAO则通过抽象DAO类+子类重写/泛型动态SQL适配不同表名,既复用通用逻辑,又满足多表独立存储的要求。
实体类实现
先定义抽象基类封装通用字段,子类继承后指定各自表名与外键约束:
// 抽象基类:定义邻接列表通用字段,不添加@Entity注解 public abstract class BaseAdjacencyList { @PrimaryKey(autoGenerate = true) public long id; // 根节点的parentId可设为-1或0,表示无父节点 public long parentId; } // 项目邻接列表实体:指定专属表名与同表外键 @Entity( tableName = "project_adjacency", foreignKeys = @ForeignKey( entity = ProjectAdjacencyList.class, parentColumns = "id", childColumns = "parentId", onDelete = CASCADE, onUpdate = CASCADE ) ) public class ProjectAdjacencyList extends BaseAdjacencyList {} // 任务邻接列表实体:同理定义专属表 @Entity( tableName = "task_adjacency", foreignKeys = @ForeignKey( entity = TaskAdjacencyList.class, parentColumns = "id", childColumns = "parentId", onDelete = CASCADE, onUpdate = CASCADE ) ) public class TaskAdjacencyList extends BaseAdjacencyList {}
DAO实现
方式1:抽象DAO基类+子类重写(推荐,Room兼容性更好)
通过抽象基类封装通用CRUD,子类继承后指定具体表名的查询语句:
// 抽象DAO基类:定义通用操作签名 @Dao public abstract class BaseAdjacencyDao<T extends BaseAdjacencyList> { @Insert public abstract long insert(T item); @Update public abstract int update(T item); @Delete public abstract int delete(T item); // 子类需实现的表专属查询 public abstract List<T> getAll(); public abstract List<T> getChildren(long parentId); } // 项目邻接列表DAO:绑定project_adjacency表 @Dao public abstract class ProjectAdjacencyDao extends BaseAdjacencyDao<ProjectAdjacencyList> { @Query("SELECT * FROM project_adjacency") @Override public abstract List<ProjectAdjacencyList> getAll(); @Query("SELECT * FROM project_adjacency WHERE parentId = :parentId") @Override public abstract List<ProjectAdjacencyList> getChildren(long parentId); } // 任务邻接列表DAO:绑定task_adjacency表 @Dao public abstract class TaskAdjacencyDao extends BaseAdjacencyDao<TaskAdjacencyList> { @Query("SELECT * FROM task_adjacency") @Override public abstract List<TaskAdjacencyList> getAll(); @Query("SELECT * FROM task_adjacency WHERE parentId = :parentId") @Override public abstract List<TaskAdjacencyList> getChildren(long parentId); }
方式2:泛型+@RawQuery(更灵活,适合复杂动态SQL)
通过反射获取子类表名,结合@RawQuery实现完全通用的查询逻辑:
@Dao public abstract class BaseAdjacencyDao<T extends BaseAdjacencyList> { private final String tableName; // 构造时通过反射获取子类的表名 public BaseAdjacencyDao(Class<T> entityClass) { Entity entityAnnotation = entityClass.getAnnotation(Entity.class); this.tableName = entityAnnotation.tableName(); } @Insert public abstract long insert(T item); @Update public abstract int update(T item); @Delete public abstract int delete(T item); // 基础RawQuery方法,供上层封装 @RawQuery protected abstract List<T> executeRawQuery(SupportSQLiteQuery query); // 通用查询所有节点 public List<T> getAll() { String sql = "SELECT * FROM " + tableName; return executeRawQuery(new SimpleSQLiteQuery(sql)); } // 通用查询指定父节点的子节点 public List<T> getChildren(long parentId) { String sql = "SELECT * FROM " + tableName + " WHERE parentId = ?"; return executeRawQuery(new SimpleSQLiteQuery(sql, new Object[]{parentId})); } } // 使用时实例化DAO // ProjectAdjacencyDao projectDao = new BaseAdjacencyDao<>(ProjectAdjacencyList.class) {};
关键注意事项
- 外键约束的
entity参数必须指向子类自身,因为邻接列表的父节点属于同一张表; - 根节点的
parentId建议用特殊值(如-1)标识,避免与正常节点ID冲突; - Room支持实体类继承,父类的字段会自动映射到子类的表结构中,无需重复定义。
内容的提问来源于stack exchange,提问作者ndaniel
相关产品推荐
相关产品推荐

