Android Room: Chèn các thực thể quan hệ bằng Room


88

Tôi đã thêm một vào nhiều mối quan hệ trong Room bằng cách sử dụng Relation . Tôi đã tham khảo bài đăng này để viết mã sau cho mối quan hệ trong Phòng.

Bài đăng cho biết cách đọc các giá trị từ cơ sở dữ liệu nhưng việc lưu trữ các thực thể vào cơ sở dữ liệu dẫn userIdđến trống, có nghĩa là không có mối quan hệ nào giữa 2 bảng.

Tôi không chắc chắn cách lý tưởng để là những gì insertmột UserList of Petvào cơ sở dữ liệu trong khi có userIdgiá trị.

1) Thực thể người dùng:

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

2) Thực thể thú cưng:

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

Bây giờ để tìm nạp các bản ghi từ DB, chúng tôi sử dụng như sau DAO:

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

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

BIÊN TẬP

Tôi đã tạo sự cố này https://issuetracker.google.com/issues/62848977 trên trình theo dõi sự cố. Hy vọng rằng họ sẽ làm điều gì đó liên quan đến nó.


Vì vậy, họ nói "quan hệ" là ưu tiên trong bản cập nhật 2.2. Phiên bản hiện tại là "Room 2.1.0-alpha03" từ ngày 4 tháng 12 năm 2018.
Lech Osiński

Vâng chỉ cần đọc bình luận của họ trong trình theo dõi vấn đề. Nó sẽ mất thời gian, trong khi đó bạn có thể sử dụng các biện pháp khắc phục
Akshay Chordiya

Câu trả lời:


43

Bạn có thể làm điều này bằng cách thay đổi Dao của bạn từ một giao diện thành một lớp trừu tượng.

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

Bạn cũng có thể tiến xa hơn bằng cách để một Userđối tượng có@Ignored List<Pet> pets

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

    @Ignored
    public List<Pet> pets
}

và sau đó Dao có thể ánh xạ UserWithPetstới Người dùng:

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

Điều này để lại cho bạn Đạo đầy đủ:

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

Thay vào đó, bạn có thể muốn có insertAll(List<Pet>)insertPetsForUser(User, List<Pet>)các phương thức trong PetDAO ... cách bạn phân vùng DAO của mình là tùy thuộc vào bạn! :)

Dù sao, đó chỉ là một lựa chọn khác. Gói các DAO của bạn trong các đối tượng DataSource cũng hoạt động.


Ý tưởng tuyệt vời để đặt phương thức tiện lợi trong lớp trừu tượng thay vì giao diện.
Akshay Chordiya

7
nếu chúng ta di chuyển các phương thức liên quan đến Pet vào PetDao, chúng ta sẽ tham chiếu các phương thức PetDao trong UserDao như thế nào?
sbearben,

4
cảm ơn câu trả lời của bạn. Nhưng làm cách nào pet.setUserId (user.getId ()) trong phương thức insertPetsForUser (User user, List <Pet> pet) đặt userId? giả sử bạn đang lấy đối tượng người dùng từ một API và tại thời điểm này, nó không có id (primaryKey) vì nó chưa được lưu vào RoomDb, vì vậy việc gọi user.getId () sẽ chỉ mang lại giá trị mặc định là 0, cho mỗi người dùng vì nó vẫn chưa được lưu. Bạn xử lý như thế nào vì user.getId () luôn trả về 0 cho mỗi người dùng. Cảm ơn
Ikhiloya Imokhai

38

Không có giải pháp gốc nào cho đến khi có bất kỳ bản cập nhật nào trong Thư viện Phòng nhưng bạn có thể thực hiện điều này bằng một mẹo nhỏ. Tìm dưới đây được đề cập.

  1. Chỉ tạo người dùng với vật nuôi (Bỏ qua vật nuôi). Thêm getter và setter. Lưu ý rằng chúng tôi phải đặt Id của mình theo cách thủ công sau này và không thể sử dụng autogenerate.

    @Entity
    public class User {
          @PrimaryKey
          public int id; 
    
          @Ignore
          private List<Pet> petList;
    }
    
  2. Tạo một con vật cưng.

    @Entity 
    public class Pet 
    {
        @PrimaryKey
        public int id;     
        public int userId; 
        public String name;
    }
    
  3. UserDao phải là một lớp trừu tượng thay vì một Giao diện. Sau đó, cuối cùng trong UserDao của bạn.

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

Vấn đề của bạn có thể được giải quyết bằng cách này mà không cần tạo UserWithPets POJO.


2
Tôi thích cái này, vì nó tránh được POJO của UserWithPets. Bằng cách sử dụng các phương pháp mặc định, DAO cũng có thể là một giao diện. Nhược điểm duy nhất mà tôi thấy là insertUser () và insertPetList () là các phương thức công khai, nhưng không nên được sử dụng bởi máy khách. Tôi đặt dấu gạch dưới trước tên của các phương thức này để cho thấy rằng chúng không nên được sử dụng, như được hiển thị ở trên.
guglhupf

1
Ai đó có thể chỉ cách thực hiện điều này đúng cách trong một Hoạt động không? Phải biết rằng tôi không biết cách tạo Id đúng cách.
Philipp

@Philipp Tôi đang giải quyết cách tạo id, bạn đã tìm ra giải pháp chưa?
sochas

1
Cảm ơn câu trả lời của bạn @Philipp, tôi đã làm điều đó theo cách tương tự :)
sochas

2
Cảm ơn @Philipp Tôi thích câu trả lời của bạn vì tính linh hoạt của nó! Đối với id tự động tạo, trong trường hợp của tôi, tôi gọi insertUser()trước để được tạo tự động userId, sau đó gán trường đó userIdcho trường userId trong lớp Pet, sau đó lặp đến insertPet().
Robert

11

Vì Room không quản lý Mối quan hệ của các thực thể, bạn phải tự thiết lập userIdtừng vật nuôi và lưu chúng. Miễn là không có quá nhiều vật nuôi cùng một lúc, tôi sẽ sử dụng một insertAllphương pháp để giữ cho nó ngắn.

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

Tôi không nghĩ có cách nào tốt hơn vào lúc này.

Để làm cho việc xử lý dễ dàng hơn, tôi sẽ sử dụng một phần trừu tượng trong lớp phía trên các DAO:

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

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

    petDao.insertAll(pets);
}

Tôi đã thử sử dụng tương tự Stream. Tôi chỉ hy vọng có một cách tốt hơn để làm điều đó.
Akshay Chordiya

Tôi không nghĩ rằng có một cách tốt hơn, như các tài liệu nói rằng họ cố tình để lại tài liệu tham khảo ra khỏi thư viện: developer.android.com/topic/libraries/architecture/...
tknell

Tôi đã tạo sự cố này Issuetracker.google.com/issues/62848977 trên trình theo dõi sự cố. Hy vọng rằng họ sẽ làm điều gì đó liên quan đến nó.
Akshay Chordiya

1
Không thực sự là một giải pháp gốc, nhưng bạn không phải sử dụng giao diện cho DAO, bạn cũng có thể sử dụng các lớp trừu tượng ... nghĩa là các phương thức tiện lợi có thể nằm trong chính DAO nếu bạn không muốn có một lớp bao bọc. Xem câu trả lời của tôi để biết thêm thông tin ...
Kieran Macdonald-Hall

6

Hiện tại không có giải pháp gốc cho vấn đề này. Tôi đã tạo https://issuetracker.google.com/issues/62848977 này trên trình theo dõi sự cố của Google và Nhóm thành phần kiến ​​trúc cho biết họ sẽ thêm giải pháp gốc vào hoặc sau phiên bản 1.0 của thư viện Room.

Giải pháp tạm thời:

Trong khi đó bạn có thể sử dụng giải pháp được đề cập bởi tknell .

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

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

    petDao.insertAll(pets);
}

Xem xét các giải pháp khác, tôi thấy cách để thực hiện điều này là sử dụng một lớp trừu tượng thay vì một giao diện trong khi tạo dao và thêm các phương thức cụ thể tương tự vào một phần của các lớp trừu tượng đó. Tuy nhiên, tôi vẫn không thể hiểu làm thế nào để lấy cá thể dao con bên trong các phương thức này? ví dụ. làm cách nào để bạn có thể lấy được cá thể petDao bên trong userDao?
Purush Pawar

5

Bây giờ ở v2.1.0 Room dường như không phù hợp với các mô hình có quan hệ lồng nhau. Nó cần rất nhiều mã soạn sẵn để duy trì chúng. Ví dụ: chèn thủ công danh sách, tạo và ánh xạ ID cục bộ.

Hoạt động ánh xạ quan hệ này được thực hiện bên ngoài bởi Request https://github.com/requery/requery Thêm vào đó, nó không có vấn đề với việc chèn Enums và có một số bộ chuyển đổi cho các loại phức tạp khác như URI.


2

Tôi đã quản lý để chèn nó đúng cách với một cách giải quyết tương đối đơn giản. Đây là các thực thể của tôi:

   @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;

Tôi đang sử dụng AutoGenerate cho giá trị tự động tăng dần (long được sử dụng với một thanh lọc). Đây là giải pháp của tôi:

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

Tôi đã gặp sự cố khi liên kết các thực thể, luôn tự động tạo "cookId = 0". Việc chèn thực thể công thức trước tiên đã sửa nó cho tôi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.