【问题标题】:Android Room: Insert relation entities using RoomAndroid Room:使用 Room 插入关系实体
【发布时间】:2017-11-23 19:55:24
【问题描述】:

我使用Relation 在 Room 中添加了一对多关系。 我参考this post为Room中的关系编写了以下代码。

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

我不确定在具有userId 值的同时将insertUserList of Pet 放入数据库的理想方法是什么。

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。希望他们会为此做点什么。

【问题讨论】:

  • 所以他们说“关系”是 2.2 更新中的优先级。当前版本是 2018 年 12 月 4 日的“Room 2.1.0-alpha03”。
  • 是的,刚刚阅读了他们在问题跟踪器中的评论。这需要时间,同时您可以使用解决方法
  • 他们降低了问题的优先级(2021 年)。

标签: android android-room android-architecture-components


【解决方案1】:

您可以通过将 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&lt;Pet&gt; 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&lt;Pet&gt;)insertPetsForUser(User, List&lt;Pet&gt;) 方法……如何划分 DAO 取决于您! :)

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

【讨论】:

  • 将便捷方法放在抽象类而不是接口中的好主意。
  • 如果我们将 Pet 相关的方法移动到 PetDao 中,我们将如何在 UserDao 中引用 PetDao 方法?
  • 感谢您的回答。但是 insertPetsForUser(User user, List pets) 方法中的 pet.setUserId(user.getId()) 是如何设置 userId 的呢?假设您从 API 获取用户对象,此时它没有 id(primaryKey),因为它尚未保存到 RoomDb,因此调用 user.getId() 只会产生默认值 0,对于每个用户,因为它尚未保存。你如何处理这种情况,因为 user.getId() 总是为每个用户返回 0。谢谢
  • 自动生成id的问题有什么解决办法吗?
【解决方案2】:

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

  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。

【讨论】:

  • 我喜欢这个,因为它避免了 UserWithPets POJO。通过使用默认方法,DAO 也可以是一个接口。我看到的唯一缺点是 insertUser() 和 insertPetList() 是公共方法,但不应由客户端使用。我在这些方法的名称前加了一个下划线,表示不应该使用它们,如上图。
  • 有人可以展示如何在活动中正确实现这一点吗?知道我不知道如何正确生成 Id。
  • @Philipp 我正在处理如何生成 id,您找到解决方案了吗?
  • 感谢您的回复@Philipp,我也是这样做的 :)
  • 谢谢@Philipp 我喜欢你的回答,因为它的灵活性!对于自动生成 id,在我的情况下,我首先调用 insertUser() 以获取自动生成的 userId,然后将 userId 分配给 Pet 类中的 userId 字段,然后循环到 insertPet()
【解决方案3】:

由于 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);
}

【讨论】:

  • 我用Stream做了同样的尝试。我只是希望有更好的方法来做到这一点。
  • 我认为没有更好的方法,因为文档说他们故意将参考文献排除在库之外:developer.android.com/topic/libraries/architecture/…
  • 我在问题跟踪器上创建了这个问题issuetracker.google.com/issues/62848977。希望他们会为此做点什么。
  • 不是真正的原生解决方案,但您不必为 DAO 使用接口,您也可以使用抽象类...这意味着如果您不想要,便利方法可以位于 DAO 本身中有一个包装类。有关更多信息,请参阅我的答案...
【解决方案4】:

目前没有针对此问题的本地解决方案。我在 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);
}

【讨论】:

  • 查看其他解决方案,我看到解决此问题的方法是在创建 dao 时使用抽象类而不是接口,并在这些抽象类中添加类似的具体方法部分。但是,我仍然不明白如何在这些方法中获取子 dao 实例?例如。您如何在 userDao 中获取 petDao 实例?
【解决方案5】:

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

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

【讨论】:

    【解决方案6】:

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

       @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”。插入配方实体首先为我修复了它。

    【讨论】:

    • 哇。这个答案有效。如何使用 Long 生成 id 可返回的插入函数?是某种 hack,还是官方文档中没有的某种房间功能?
    • 这些是什么:getIngredients()recipeWithIngredients.getRecipe()
    猜你喜欢
    • 2020-05-27
    • 1970-01-01
    • 2018-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多