Android Room: Sisipkan entitas relasi menggunakan Room

90

Saya telah menambahkan satu ke banyak hubungan di Room menggunakan Relation . Saya mengacu posting ini untuk menulis kode berikut untuk relasi di Room.

Postingan tersebut memberi tahu cara membaca nilai dari database tetapi menyimpan entitas ke dalam database yang dihasilkan userId kosong yang berarti tidak ada hubungan antara 2 tabel tersebut.

Saya tidak yakin apa yang adalah cara yang ideal untuk insertsebuah UserdanList of Pet ke dalam database sementara memiliki userIdnilai.

1) Entitas Pengguna:

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

2) Entitas Hewan Peliharaan:

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

Sekarang untuk mengambil record dari DB kita menggunakan yang berikut ini DAO:

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

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

EDIT

Saya telah membuat masalah ini https://issuetracker.google.com/issues/62848977 di pelacak masalah. Mudah-mudahan mereka akan melakukan sesuatu tentang itu.

Akshay Chordiya
sumber
Jadi mereka mengatakan "hubungan" adalah prioritas dalam pembaruan 2.2. Versi saat ini adalah "Room 2.1.0-alpha03" dari 4 Desember 2018.
Lech Osiński
Ya, baca saja komentar mereka di pelacak masalah. Ini akan memakan waktu, sementara Anda dapat menggunakan solusi
Akshay Chordiya

Jawaban:

44

Anda dapat melakukan ini dengan mengubah Dao Anda dari antarmuka ke kelas abstrak.

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

Anda juga dapat melangkah lebih jauh dengan membuat Userbenda memiliki ekstensi@Ignored List<Pet> pets

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

    @Ignored
    public List<Pet> pets
}

dan kemudian Dao dapat memetakan UserWithPetske Pengguna:

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

Ini membuat Anda memiliki Dao penuh:

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

Anda mungkin ingin memiliki insertAll(List<Pet>)dan insertPetsForUser(User, List<Pet>)metode di PetDAO sebagai gantinya ... bagaimana Anda mempartisi DAO Anda terserah Anda! :)

Bagaimanapun, itu hanya pilihan lain. Membungkus DAO Anda di objek DataSource juga berfungsi.

Kieran Macdonald-Hall
sumber
Ide bagus untuk meletakkan metode kemudahan di kelas abstrak daripada antarmuka.
Akshay Chordiya
7
jika kita memindahkan metode terkait Pet ke dalam PetDao, bagaimana kita akan mereferensikan metode PetDao dalam UserDao?
sbearben
4
Terima kasih atas jawaban anda. Tapi bagaimana pet.setUserId (user.getId ()) dalam metode insertPetsForUser (User user, List <Pet> pets) menyetel userId? anggap saja Anda mendapatkan objek pengguna dari API dan pada titik ini tidak memiliki id (primaryKey) karena belum disimpan ke RoomDb, jadi memanggil user.getId () hanya akan menghasilkan nilai default 0, untuk masing-masing pengguna karena belum disimpan. Bagaimana Anda menangani seperti itu karena user.getId () selalu mengembalikan 0 untuk setiap pengguna. Terima kasih
Ikhiloya Imokhai
40

Tidak ada solusi asli hingga pembaruan apa pun di Perpustakaan Kamar tetapi Anda dapat melakukannya dengan trik. Temukan yang disebutkan di bawah ini.

  1. Cukup Buat Pengguna dengan Hewan Peliharaan (Abaikan hewan peliharaan). Tambahkan pengambil dan penyetel. Perhatikan bahwa kita harus mengatur Id kita secara manual nanti dan tidak dapat digunakan autogenerate.

    @Entity
    public class User {
          @PrimaryKey
          public int id; 
    
          @Ignore
          private List<Pet> petList;
    }
    
  2. Buat Hewan Peliharaan.

    @Entity 
    public class Pet 
    {
        @PrimaryKey
        public int id;     
        public int userId; 
        public String name;
    }
    
  3. User Dao harus berupa kelas abstrak, bukan Interface. Lalu akhirnya di 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;
    }
    

Masalah Anda dapat diselesaikan dengan ini tanpa membuat UserWithPets POJO.

Dipendra Sharma
sumber
2
Saya suka yang ini, karena menghindari POJO UserWithPets. Dengan menggunakan metode default, DAO juga bisa menjadi antarmuka. Satu-satunya downside yang saya lihat adalah bahwa insertUser () dan insertPetList () adalah metode publik, tetapi tidak boleh digunakan oleh klien. Saya meletakkan garis bawah sebelum nama metode ini untuk menunjukkan bahwa mereka tidak boleh digunakan, seperti yang ditunjukkan di atas.
guglhupf
1
Dapatkah seseorang menunjukkan cara mengimplementasikan ini dengan benar dalam sebuah Aktivitas? Benar tahu saya tidak tahu cara menghasilkan Id dengan benar.
Philipp
@Philipp Saya berurusan dengan cara membuat id, apakah Anda menemukan solusinya?
sochas
1
Terima kasih atas balasan Anda @Philipp, saya melakukannya dengan cara yang sama :)
sochas
2
Terima kasih @Philipp. Saya suka jawaban Anda karena fleksibilitasnya! Untuk id yang menghasilkan otomatis, dalam kasus saya, saya menelepon insertUser()terlebih dahulu untuk mendapatkan auto-generated userId, kemudian menetapkan itu userIdke bidang userId di kelas Pet, kemudian mengulang ke insertPet().
Robert
11

Karena Room tidak mengelola Relasi entitas, Anda harus mengatur userIdsendiri setiap hewan peliharaan dan menyimpannya. Selama tidak terlalu banyak hewan peliharaan sekaligus, saya akan menggunakan insertAllmetode untuk membuatnya tetap pendek.

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

Saya tidak berpikir ada cara yang lebih baik saat ini.

Untuk mempermudah penanganan, saya akan menggunakan abstraksi di lapisan di atas DAO:

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

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

    petDao.insertAll(pets);
}
tknell
sumber
Saya mencoba menggunakan yang sama Stream. Saya hanya berharap ada cara yang lebih baik untuk melakukannya.
Akshay Chordiya
Saya rasa tidak ada cara yang lebih baik, karena dokumentasi mengatakan bahwa mereka sengaja meninggalkan referensi dari perpustakaan: developer.android.com/topic/libraries/architecture/…
tknell
Saya telah membuat masalah ini Issuetracker.google.com/issues/62848977 di pelacak masalah. Mudah-mudahan mereka akan melakukan sesuatu tentang itu.
Akshay Chordiya
1
Sebenarnya bukan solusi asli, tetapi Anda tidak harus menggunakan antarmuka untuk DAO, Anda juga dapat menggunakan kelas abstrak ... yang berarti metode praktis dapat ditempatkan di dalam DAO itu sendiri jika Anda tidak ingin memiliki kelas pembungkus. Lihat jawaban saya untuk info lebih lanjut ...
Kieran Macdonald-Hall
6

Saat ini tidak ada solusi asli untuk masalah ini. Saya telah membuat https://issuetracker.google.com/issues/62848977 ini di pelacak masalah Google dan Tim Komponen Arsitektur mengatakan mereka akan menambahkan solusi asli di atau setelah v1.0 dari perpustakaan Room.

Solusi Sementara:

Sementara itu Anda dapat menggunakan solusi yang disebutkan oleh tknell .

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

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

    petDao.insertAll(pets);
}
Akshay Chordiya
sumber
Melihat solusi lain, saya melihat cara untuk melakukannya adalah menggunakan kelas abstrak daripada antarmuka saat membuat dao dan menambahkan bagian metode konkret serupa dari kelas abstrak tersebut. Tapi, saya masih tidak mengerti bagaimana cara mendapatkan instance dao anak di dalam metode ini? misalnya. bagaimana Anda bisa mendapatkan instance petDao di dalam userDao?
Purush Pawar
5

Sekarang di v2.1.0 Room tampaknya tidak cocok untuk model dengan relasi bertingkat. Diperlukan banyak kode boilerplate untuk memeliharanya. Misalnya, memasukkan daftar secara manual, membuat dan memetakan ID lokal.

Operasi pemetaan relasi ini dilakukan di luar kotak oleh Requery https://github.com/requery/requery Additionaly itu tidak memiliki masalah dengan memasukkan Enum dan memiliki beberapa konverter untuk jenis kompleks lainnya seperti URI.

Aktifitas utama
sumber
2

Saya berhasil memasukkannya dengan benar dengan solusi yang relatif sederhana. Inilah entitas saya:

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

Saya menggunakan autoGenerate untuk nilai auto-increment (panjang digunakan dengan purpoes). Inilah solusi saya:

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

Saya mengalami masalah saat menautkan entitas, secara otomatis menghasilkan "resepId = 0" sepanjang waktu. Memasukkan entitas resep terlebih dahulu memperbaikinya untuk saya.

stefan.georgiev.uzunov
sumber