Android Room:使用Room插入关系实体


88

我已使用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。希望他们会为此做些事情。


因此他们说“关系”是2.2更新中的优先级。当前版本为“ Room 2.1.0-alpha03”,
始于

是的,只是在问题跟踪器中阅读了他们的评论。这将需要一些时间,同时您可以使用替代方法
Akshay Chordiya

Answers:


43

您可以通过将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到User:

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>)andinsertPetsForUser(User, List<Pet>)方法...如何划分DAO取决于您!:)

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


将便捷方法放在抽象类而不是接口中的好主意。
Akshay Chordiya '17

7
如果我们要将Pet相关方法移到PetDao中,我们将如何在UserDao中引用PetDao方法?
sbearben

4
感谢您的回答。但是insertPetsForUser(用户用户,List <Pet> pets)方法中的pet.setUserId(user.getId())如何设置userId?假设您正在从API获取用户对象,并且由于尚未将其保存到RoomDb,因此此时它没有id(primaryKey),因此调用user.getId()只会为每个对象产生默认值0用户,因为尚未保存。您如何处理此类问题,因为user.getId()始终为每个用户返回0。谢谢
Ikhiloya Imokhai

38

在Room Library中进行任何更新之前,没有本机解决方案,但是您可以通过技巧来完成。找到下面提到的。

  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应该是一个抽象类,而不是一个Interface。然后最后在您的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。


2
我喜欢这一点,因为它避免了UserWithPets POJO。通过使用默认方法,DAO也可以是接口。我唯一看到的缺点是insertUser()和insertPetList()是公共方法,但不应由客户端使用。我在这些方法的名称前加了下划线,以表明不应使用它们,如上所示。
guglhupf

1
有人可以显示如何在活动中正确实现吗?知道我不知道如何正确生成ID。
菲利普

@Philipp我正在处理如何生成ID,您找到解决方案了吗?
sochas

1
感谢您的回复@Philipp,我以同样的方式做到了:)
sochas

2
谢谢@Philipp,我喜欢您的回答,它具有灵活性!对于自动生成的ID,在我的情况下,我insertUser()首先调用获取自动生成的ID,userId然后将其分配给userIdPet类中的userId字段,然后循环到insertPet()
罗伯特·

11

由于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。我只是希望有更好的方法。
Akshay Chordiya

我认为没有更好的方法,因为文档说他们有意将引用从库中
删除

我已在问题跟踪器上创建了此问题issuetracker.google.com/issues/62848977。希望他们会为此做些事情。
Akshay Chordiya

1
并不是真正的本机解决方案,但是您不必为DAO使用接口,也可以使用抽象类……这意味着,如果您不想拥有包装器类,则便利方法可以放在DAO本身中。请参阅我的答案以获取更多信息...
基兰·麦克唐纳

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

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

5

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

这关系映射操作被重新查询做出来的盒子https://github.com/requery/requery Additionaly它没有问题与插入枚举和对其他复杂类型,如URI点转换器。


2

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

   @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来获得自动增加的值(长期使用目的)。这是我的解决方案:

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.