107

我已经使用Relation在 Room 中添加了一对多关系。我参考这篇文章为 Room 中的关系编写了以下代码。

该帖子讲述了如何从数据库中读取值,但将实体存储到数据库中导致userId为空,这意味着这两个表之间没有关系。

我不确定在有价值的同时进入数据库的理想insert方式UserList of Pet什么userId

1) 用户实体:

@Entity
public class User {
    @PrimaryKey
    public int id; // User id
}

2) 宠物实体:

@Entity
public class Pet {
    @PrimaryKey
    public int id;     // Pet id
    public int userId; // User id
    public String name;
}

3) UserWithPets POJO:

// Note: No annotation required at this class definition.
public class UserWithPets {
   @Embedded
   public User user;

   @Relation(parentColumn = "id", entityColumn = "userId", entity = Pet.class)
   public List<Pet> pets;
}

现在要从数据库中获取记录,我们使用以下命令DAO

@Dao
public interface UserDao {
    @Insert
    fun insertUser(user: User)

    @Query("SELECT * FROM User")
    public List<UserWithPets> loadUsersWithPets();
}

编辑

我在问题跟踪器上创建了这个问题https://issuetracker.google.com/issues/62848977 。希望他们会为此做点什么。

4

6 回答 6

46

您可以通过将您的 Dao 从接口更改为抽象类来做到这一点。

@Dao
public abstract class UserDao {

    public void insertPetsForUser(User user, List<Pet> pets){

        for(Pet pet : pets){
            pet.setUserId(user.getId());
        }

        _insertAll(pets);
    }

    @Insert
    abstract void _insertAll(List<Pet> pets);  //this could go in a PetDao instead...

    @Insert
    public abstract void insertUser(User user);

    @Query("SELECT * FROM User")
    abstract List<UserWithPets> loadUsersWithPets();
}

您还可以通过让User对象具有@Ignored List<Pet> pets

@Entity
public class User {
    @PrimaryKey
    public int id; // User id

    @Ignored
    public List<Pet> pets
}

然后 Dao 可以映射UserWithPets到用户:

public List<User> getUsers() {
    List<UserWithPets> usersWithPets = loadUserWithPets();
    List<User> users = new ArrayList<User>(usersWithPets.size())
    for(UserWithPets userWithPets: usersWithPets) {
        userWithPets.user.pets = userWithPets.pets;
        users.add(userWithPets.user);
    }
    return users;
}

这给你留下了完整的道:

@Dao
public abstract class UserDao {

    public void insertAll(List<User> users) {
        for(User user:users) {
           if(user.pets != null) {
               insertPetsForUser(user, user.pets);
           }
        }
        _insertAll(users);
    }

    private void insertPetsForUser(User user, List<Pet> pets){

        for(Pet pet : pets){
            pet.setUserId(user.getId());
        }

        _insertAll(pets);
    }

    public List<User> getUsersWithPetsEagerlyLoaded() {
        List<UserWithPets> usersWithPets = _loadUsersWithPets();
        List<User> users = new ArrayList<User>(usersWithPets.size())
        for(UserWithPets userWithPets: usersWithPets) {
            userWithPets.user.pets = userWithPets.pets;
            users.add(userWithPets.user);
        }
        return users;
    }


    //package private methods so that wrapper methods are used, Room allows for this, but not private methods, hence the underscores to put people off using them :)
    @Insert
    abstract void _insertAll(List<Pet> pets);

    @Insert
    abstract void _insertAll(List<User> users);

    @Query("SELECT * FROM User")
    abstract List<UserWithPets> _loadUsersWithPets();
}

您可能希望在 PetDAO 中使用insertAll(List<Pet>)insertPetsForUser(User, List<Pet>)方法……如何对 DAO 进行分区取决于您!:)

无论如何,这只是另一种选择。将 DAO 包装在 DataSource 对象中也可以。

于 2017-10-31T16:12:00.983 回答
45

直到房间库中的任何更新都没有本机解决方案,但您可以通过一个技巧来做到这一点。找到下面提到的。

  1. 只需创建一个带宠物的用户(忽略宠物)。添加 getter 和 setter。请注意,我们必须稍后手动设置我们的 ID,并且不能使用autogenerate.

    @Entity
    public class User {
          @PrimaryKey
          public int id; 
    
          @Ignore
          private List<Pet> petList;
    }
    
  2. 创建宠物。

    @Entity 
    public class Pet 
    {
        @PrimaryKey
        public int id;     
        public int userId; 
        public String name;
    }
    
  3. UserDao 应该是一个抽象类而不是一个接口。最后在你的 UserDao 中。

    @Insert
    public abstract void insertUser(User user);
    
    @Insert
    public abstract void insertPetList(List<Pet> pets);
    
    @Query("SELECT * FROM User WHERE id =:id")
    public abstract User getUser(int id);
    
    @Query("SELECT * FROM Pet WHERE userId =:userId")
    public abstract List<Pet> getPetList(int userId);
    
    public void insertUserWithPet(User user) {
        List<Pet> pets = user.getPetList();
        for (int i = 0; i < pets.size(); i++) {
            pets.get(i).setUserId(user.getId());
        }
        insertPetList(pets);
        insertUser(user);
    }
    
    public User getUserWithPets(int id) {
        User user = getUser(id);
        List<Pet> pets = getPetList(id);
        user.setPetList(pets);
        return user;
    }
    

您的问题可以通过此解决,而无需创建 UserWithPets POJO。

于 2017-11-04T16:31:33.563 回答
13

由于 Room 不管理实体的关系,您必须自己设置userId每个宠物并保存它们。只要一次没有太多宠物,我会使用一种insertAll方法来保持简短。

@Dao
public interface PetDao {
    @Insert
    void insertAll(List<Pet> pets);
}

我认为目前没有更好的方法。

为了使处理更容易,我将在 DAO 上方的层中使用抽象:

public void insertPetsForUser(User user, List<Pet> pets){

    for(Pet pet : pets){
        pet.setUserId(user.getId());
    }

    petDao.insertAll(pets);
}
于 2017-06-21T05:23:10.017 回答
6

目前没有针对此问题的本地解决方案。我在 Google 的问题跟踪器上创建了这个https://issuetracker.google.com/issues/62848977,架构组件团队表示他们将在 Room 库 v1.0 或之后添加本机解决方案。

临时解决方法:

同时,您可以使用tknell提到的解决方案。

public void insertPetsForUser(User user, List<Pet> pets){

    for(Pet pet : pets){
        pet.setUserId(user.getId());
    }

    petDao.insertAll(pets);
}
于 2017-09-04T13:11:22.813 回答
6

现在在 v2.1.0 Room 似乎不适合具有嵌套关系的模型。它需要大量的样板代码来维护它们。例如手动插入列表、创建和映射本地 ID。

这种关系映射操作由 Requery https://github.com/requery/requery开箱即用地完成,此外它在插入 Enums 时没有问题,并且对于其他复杂类型(如 URI)有一些转换器。

于 2019-08-01T13:49:02.483 回答
5

我设法通过一个相对简单的解决方法正确插入它。这是我的实体:

   @Entity
public class Recipe {
    @PrimaryKey(autoGenerate = true)
    public long id;
    public String name;
    public String description;
    public String imageUrl;
    public int addedOn;
   }


  @Entity
public class Ingredient {
   @PrimaryKey(autoGenerate = true)
   public long id;
   public long recipeId; 
   public String name;
   public String quantity;
  }

public class RecipeWithIngredients {
   @Embedded
   public  Recipe recipe;
   @Relation(parentColumn = "id",entityColumn = "recipeId",entity = Ingredient.class)
   public List<Ingredient> ingredients;

我将 autoGenerate 用于自动增量值(long 用于目的)。这是我的解决方案:

  @Dao
public abstract class RecipeDao {

  public  void insert(RecipeWithIngredients recipeWithIngredients){
    long id=insertRecipe(recipeWithIngredients.getRecipe());
    recipeWithIngredients.getIngredients().forEach(i->i.setRecipeId(id));
    insertAll(recipeWithIngredients.getIngredients());
  }

public void delete(RecipeWithIngredients recipeWithIngredients){
    delete(recipeWithIngredients.getRecipe(),recipeWithIngredients.getIngredients());
  }

 @Insert
 abstract  void insertAll(List<Ingredient> ingredients);
 @Insert
 abstract long insertRecipe(Recipe recipe); //return type is the key here.

 @Transaction
 @Delete
 abstract void delete(Recipe recipe,List<Ingredient> ingredients);

 @Transaction
 @Query("SELECT * FROM Recipe")
 public abstract List<RecipeWithIngredients> loadAll();
 }

我在链接实体时遇到问题,自动生成一直生成的“recipeId=0”。插入配方实体首先为我修复了它。

于 2020-04-24T20:35:08.240 回答