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

Aks*_*iya 54 android android-room android-architecture-components

我在Room使用Relation添加了一对多的关系.我在这篇文章中提到了在Room中为关系编写以下代码.

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

我不知道什么是理想的途径,insert一个UserList of Pet到数据库中,同时具有userId价值.

1)用户实体:

@Entity
public class User {
    @PrimaryKey
    public int id; // User id
}
Run Code Online (Sandbox Code Playgroud)

2)宠物实体:

@Entity
public class Pet {
    @PrimaryKey
    public int id;     // Pet id
    public int userId; // User id
    public String name;
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

现在要从DB获取记录,我们使用以下内容DAO:

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

    @Query("SELECT * FROM User")
    public List<UserWithPets> loadUsersWithPets();
}
Run Code Online (Sandbox Code Playgroud)

编辑

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

Kie*_*all 30

您可以通过将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();
}
Run Code Online (Sandbox Code Playgroud)

你也可以通过拥有一个User对象来进一步发展@Ignored List<Pet> pets

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

    @Ignored
    public List<Pet> pets
}
Run Code Online (Sandbox Code Playgroud)

然后道可以映射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;
}
Run Code Online (Sandbox Code Playgroud)

这将为您留下完整的Dao:

@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();
}
Run Code Online (Sandbox Code Playgroud)

你可能想要在PetDAO中使用insertAll(List<Pet>)insertPetsForUser(User, List<Pet>)方法......你如何对你的DAO进行分区取决于你!:)

无论如何,这只是另一种选择.在DataSource对象中包装DAO也很有效.

  • 感谢您的回答。但是 insertPetsForUser(User user, List&lt;Pet&gt; pets) 方法中的 pet.setUserId(user.getId()) 是如何设置 userId 的呢?让我们假设您从 API 获取用户对象,此时它没有 id(primaryKey),因为它尚未保存到 RoomDb,因此调用 user.getId() 只会产生默认值 0,对于每个用户,因为它尚未保存。您如何处理这种情况,因为 user.getId() 始终为每个用户返回 0。谢谢 (6认同)
  • 如果我们将Pet相关的方法移到PetDao中,我们如何在UserDao中引用PetDao方法? (4认同)

Dip*_*rma 24

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

  1. 只需创建一个带宠物的用户(忽略宠物).

    @Entity
    public class User {
          @PrimaryKey
          public int id; 
    
          @Ignore
          private List<Pet> petList;
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 创造宠物.

    @Entity 
    public class Pet 
    {
        @PrimaryKey
        public int id;     
        public int userId; 
        public String name;
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 现在终于在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;
    }
    
    Run Code Online (Sandbox Code Playgroud)

您可以通过此方法解决问题,而无需创建UserWithPets POJO.

  • 我喜欢这一点,因为它避免了UserWithPets POJO。通过使用默认方法,DAO也可以是接口。我唯一看到的缺点是insertUser()和insertPetList()是公共方法,但不应由客户端使用。我在这些方法的名称前加了下划线,以表明不应使用它们,如上所示。 (2认同)
  • 谢谢@Philipp,我喜欢你的答案,因为它的灵活性!对于自动生成 id,在我的例子中,我首先调用“insertUser()”来获取自动生成的“userId”,然后将该“userId”分配给 Pet 类中的 userId 字段,然后循环到“insertPet()”。 (2认同)

tkn*_*ell 7

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

@Dao
public interface PetDao {
    @Insert
    void insertAll(List<Pet> pets);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

    petDao.insertAll(pets);
}
Run Code Online (Sandbox Code Playgroud)


Aks*_*iya 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);
}
Run Code Online (Sandbox Code Playgroud)


Ser*_*rge 6

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

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