Java及Android Room中继承类的主键外键正确实现与配置问题
你的构建报错和关联逻辑不生效,核心是两个问题:
- Room要求每个
@Entity注解的实体类只能有一个主键,你现在的子类继承了带@Entity注解的父类Assessment,会继承父类的assessment_id主键,同时子类自己又定义了performanceID/objective_ID主键,相当于一个实体有两个主键,直接触发构建失败。 - 外键关联方向完全不符合你的需求:你现在配置的是子类外键指向父类主键,此时只有删除父类记录时才会级联删除子类记录,和你要求的「删除子类同步删除父类」逻辑完全相反。
以下是可直接运行的修改方案:
1. 公共字段抽象剥离(解决多主键冲突)
先把父类和子类的公共字段抽成一个不带@Entity注解的抽象基类,避免实体继承带来的主键冲突,同时保留多态特性的基础:
// 抽象基类,仅存放公共字段,不加@Entity注解 public abstract class BaseAssessment { protected String assessmentName; protected String assessmentStart; protected String assessmentEnd; protected int courseID; // 公共构造方法、getter、setter自行补充即可 }
2. 调整父类Assessment代码
父类表新增子类类型标识、子类主键外键字段,满足你「子类主键作为父表外键」的需求:
@Entity(tableName = "assessment_table") public class Assessment extends BaseAssessment { @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "assessment_id") private int assessmentId; // 标识关联的子类类型:1=PerformanceAssessment 2=ObjectiveAssessment private int subType; // 关联对应子类的主键,作为外键 private int subId; public Assessment(int assessmentId, String assessmentName, String assessmentStart, String assessmentEnd, int courseID, int subType, int subId) { this.assessmentId = assessmentId; this.assessmentName = assessmentName; this.assessmentStart = assessmentStart; this.assessmentEnd = assessmentEnd; this.courseID = courseID; this.subType = subType; this.subId = subId; } // getter、setter自行补充 }
3. 调整子类代码
两个子类不再直接继承Assessment,改为继承BaseAssessment,保留自己的独立主键,不需要额外配置外键(外键统一放在父类表侧维护):
PerformanceAssessment.java
@Entity(tableName = "performance_assessment") public class PerformanceAssessment extends BaseAssessment { @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "performance_id") private int performanceId; private String type; public PerformanceAssessment(int performanceId, String assessmentName, String assessmentStart, String assessmentEnd, int courseID, String type) { this.performanceId = performanceId; this.assessmentName = assessmentName; this.assessmentStart = assessmentStart; this.assessmentEnd = assessmentEnd; this.courseID = courseID; this.type = type; } // getter、setter自行补充 }
ObjectiveAssessment.java
@Entity(tableName = "objective_assessment") public class ObjectiveAssessment extends BaseAssessment { @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "objective_id") private int objectiveId; private String type; public ObjectiveAssessment(int objectiveId, String assessmentName, String assessmentStart, String assessmentEnd, int courseID, String type) { this.objectiveId = objectiveId; this.assessmentName = assessmentName; this.assessmentStart = assessmentStart; this.assessmentEnd = assessmentEnd; this.courseID = courseID; this.type = type; } // getter、setter自行补充 }
4. 调整新增考核的插入逻辑
Room的insert方法如果传入主键为0的实体,会返回自动生成的主键值,利用这个特性即可实现父子表主键自动关联:
public void saveAssessment(View view) { assessmentTitle = editName.getText().toString(); assessmentStart = editStart.getText().toString(); assessmentEnd = editEnd.getText().toString(); if (assessmentTitle.isEmpty() || assessmentStart.isEmpty() || assessmentEnd.isEmpty()) { Toast.makeText(AddAssessmentScreen.this, "请填写必填字段", Toast.LENGTH_LONG).show(); return; } // 开异步线程执行事务插入,避免数据关联不一致 new Thread(() -> { if (assessment_type) { // 第一步:先插入子类,拿到自动生成的子类主键 PerformanceAssessment performance = new PerformanceAssessment(0, assessmentTitle, assessmentStart, assessmentEnd, currentCourseID, selectedString); long performanceId = repository.insertPerformance(performance); // 第二步:插入父类,把生成的子类主键传入关联 Assessment assessment = new Assessment(0, assessmentTitle, assessmentStart, assessmentEnd, currentCourseID, 1, (int) performanceId); repository.insertAssessment(assessment); } else { // 第一步:先插入子类,拿到自动生成的子类主键 ObjectiveAssessment objective = new ObjectiveAssessment(0, assessmentTitle, assessmentStart, assessmentEnd, currentCourseID, selectedString); long objectiveId = repository.insertObjective(objective); // 第二步:插入父类,把生成的子类主键传入关联 Assessment assessment = new Assessment(0, assessmentTitle, assessmentStart, assessmentEnd, currentCourseID, 2, (int) objectiveId); repository.insertAssessment(assessment); } runOnUiThread(() -> Toast.makeText(AddAssessmentScreen.this, "新增考核成功,请刷新上一页", Toast.LENGTH_LONG).show()); }).start(); }
5. 实现删除子类同步删除父类
在DAO层给删除方法加@Transaction注解,保证操作原子性:
@Dao public abstract class AssessmentDao { // 删除Performance类型考核 @Transaction public void deletePerformance(PerformanceAssessment performance) { // 先删子类记录 delete(performance); // 再删除关联的父类记录 deleteAssessmentBySubId(performance.getPerformanceId()); } // 删除Objective类型考核 @Transaction public void deleteObjective(ObjectiveAssessment objective) { delete(objective); deleteAssessmentBySubId(objective.getObjectiveId()); } @Delete abstract void delete(PerformanceAssessment performance); @Delete abstract void delete(ObjectiveAssessment objective); @Query("DELETE FROM assessment_table WHERE subId = :subId") abstract void deleteAssessmentBySubId(int subId); }
如果需要保留子类直接继承Assessment的多态写法,只需要去掉子类自己定义的主键,改为用父类的assessment_id作为子类的主键和外键即可,后续逻辑调整方式和上述方案一致。
内容的提问来源于stack exchange,提问作者JayM
相关产品推荐
相关产品推荐

