如何使用Room的@Relation注解结合复合键构建实体模型?
处理Room中复合主键/外键的@Relation关联问题
确实,早期版本的Room的@Relation注解看起来只支持单键关联,但其实从Room 2.2.0版本开始,官方已经支持通过字符串数组来指定复合主键/外键的关联条件,正好匹配你想要的写法!下面分两种情况给你具体解决方案:
方案1:使用Room 2.2.0+版本(推荐)
如果你的项目已经升级到Room 2.2.0或更高版本,直接就能用你预想的方式构建模型类,只需要注意注解参数的语法(Java/Kotlin略有不同):
第一步:定义实体类
先确保你的复合主键实体和带复合外键的子实体定义正确:
父实体(复合主键)
@Entity(primaryKeys = {"id_part_1", "id_part_2"}) public class ParentEntity { public String id_part_1; public String id_part_2; // 其他业务字段... }
子实体(复合外键)
@Entity( foreignKeys = { @ForeignKey( entity = ParentEntity.class, parentColumns = {"id_part_1", "id_part_2"}, childColumns = {"compose_key_part_1", "compose_key_part_2"}, onDelete = ForeignKey.CASCADE ) } ) public class ItemEntity { @PrimaryKey(autoGenerate = true) public long itemId; public String compose_key_part_1; public String compose_key_part_2; // 其他业务字段... }
第二步:构建模型类
在包含关联列表的模型类中,直接使用@Relation的数组参数指定复合键关联:
public class ParentModel { @Embedded public ParentEntity parent; @Relation( parentColumns = {"id_part_1", "id_part_2"}, // 父实体的复合主键列 entityColumns = {"compose_key_part_1", "compose_key_part_2"}, // 子实体的复合外键列 entity = ItemEntity.class ) public List<ItemModel> actions; // 这里如果不需要额外转换,直接用List<ItemEntity>也可以 }
第三步:DAO层查询
只需要在DAO中写一个带@Transaction注解的查询方法,Room会自动帮你关联查询并组装模型:
@Dao public interface ParentDao { @Transaction @Query("SELECT * FROM ParentEntity WHERE id_part_1 = :id1 AND id_part_2 = :id2") LiveData<ParentModel> getParentWithItems(String id1, String id2); }
方案2:旧版本Room(低于2.2.0)的兼容方案
如果因为项目限制无法升级Room版本,你可以通过自定义JOIN查询+手动映射或者**@Transaction分两次查询**来实现:
方式A:自定义JOIN查询
直接在DAO中写JOIN语句,然后通过Room的POJO映射来组装数据:
@Dao public interface ParentDao { @Query(""" SELECT p.*, i.* FROM ParentEntity p LEFT JOIN ItemEntity i ON p.id_part_1 = i.compose_key_part_1 AND p.id_part_2 = i.compose_key_part_2 WHERE p.id_part_1 = :id1 AND p.id_part_2 = :id2 """) List<ParentWithItemRaw> getParentWithItemsRaw(String id1, String id2); }
然后定义一个中间POJO接收原始查询结果,再手动转换成你需要的ParentModel(可以写一个工具类来分组子实体):
public class ParentWithItemRaw { @Embedded public ParentEntity parent; @Embedded(prefix = "item_") public ItemEntity item; }
方式B:@Transaction分两次查询
在DAO中用@Transaction注解包裹两个查询,先查父实体,再根据复合主键查对应的子实体:
@Dao public interface ParentDao { @Transaction default ParentModel getParentWithItems(String id1, String id2) { ParentEntity parent = getParent(id1, id2); List<ItemEntity> items = getItemsByParent(id1, id2); ParentModel model = new ParentModel(); model.parent = parent; model.actions = convertToItemModel(items); // 转换为ItemModel的逻辑 return model; } @Query("SELECT * FROM ParentEntity WHERE id_part_1 = :id1 AND id_part_2 = :id2") ParentEntity getParent(String id1, String id2); @Query("SELECT * FROM ItemEntity WHERE compose_key_part_1 = :id1 AND compose_key_part_2 = :id2") List<ItemEntity> getItemsByParent(String id1, String id2); }
注意事项
- 不管用哪种方案,都要确保复合主键和外键的列名对应关系正确,避免关联错误。
- 使用
@Transaction注解可以保证查询的原子性,避免数据不一致的问题。
内容的提问来源于stack exchange,提问作者Ecnill
相关产品推荐
相关产品推荐

