You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java及Android Room中继承类的主键外键正确实现与配置问题

你的构建报错和关联逻辑不生效,核心是两个问题:

  1. Room要求每个@Entity注解的实体类只能有一个主键,你现在的子类继承了带@Entity注解的父类Assessment,会继承父类的assessment_id主键,同时子类自己又定义了performanceID/objective_ID主键,相当于一个实体有两个主键,直接触发构建失败。
  2. 外键关联方向完全不符合你的需求:你现在配置的是子类外键指向父类主键,此时只有删除父类记录时才会级联删除子类记录,和你要求的「删除子类同步删除父类」逻辑完全相反。

以下是可直接运行的修改方案:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 19:54:04