Android Room: Вставте об’єкти зв’язку за допомогою Room


88

Я додав одне до багатьох стосунків у Кімната, використовуючи Relation . Я звернувся до цього допису, щоб написати наступний код для відношення в кімнаті.

Пост розповідає, як читати значення з бази даних, але зберігання сутностей у базі даних призвело userIdдо того, що вони були порожніми, що означає, що між двома таблицями немає зв'язку.

Я не впевнений , що це ідеальний спосіб і в базу даних, маючи значення.insertUserList of PetuserId

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. Поточна версія - "Кімната 2.1.0-alpha03" від 4 грудня 2018 р.
Лех Осінський,

Так, просто прочитайте їх коментар у трекері випусків. Це займе час, тим часом ви можете скористатися
обхідними шляхами

Відповіді:


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користувача:

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

Ви можете натомість мати методи insertAll(List<Pet>)і insertPetsForUser(User, List<Pet>)методи в PetDAO ... як ви розподіляєте свої DAO, залежить від вас! :)

У будь-якому випадку, це просто інший варіант. Обернення ваших DAO в об’єкти DataSource також працює.


Чудова ідея помістити зручний метод в абстрактний клас замість інтерфейсу.
Акшай Чордія

7
якби ми перенесли пов'язані з домашніми тваринами методи в PetDao, як би ми посилалися на методи PetDao в UserDao?
sbearben

4
Дякую за вашу відповідь. Але як pet.setUserId (user.getId ()) у методі insertPetsForUser (Користувач користувача, Список <Pet> домашніх тварин) встановлює userId? припустимо, ви отримуєте об’єкт користувача з API, і на даний момент він не має ідентифікатора (primaryKey), оскільки він не був збережений у RoomDb, тому виклик user.getId () дасть лише значення за замовчуванням 0 для кожного користувач, оскільки його ще не збережено. Як ви впораєтеся з цим, оскільки user.getId () завжди повертає 0 для кожного користувача. Дякуємо
Іхілоя Імохай

38

До будь-якого оновлення бібліотеки кімнат не існує власного рішення, але ви можете зробити це за допомогою хитрості. Знайдіть нижче.

  1. Просто створіть користувача з домашніми тваринами (ігнорувати домашніх тварин). Додати геттер і сеттер. Зверніть увагу, що пізніше ми повинні встановити ідентифікатори вручну і не можемо їх використовувати 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
Мені подобається цей, оскільки він уникає POJO UserWithPets. За допомогою методів за замовчуванням DAO також може бути інтерфейсом. Єдиним мінусом, який я бачу, є те, що insertUser () та insertPetList () є загальнодоступними методами, але не повинні використовуватися клієнтом. Я ставлю підкреслення перед назвою цих методів, щоб показати, що їх не слід використовувати, як показано вище.
guglhupf

1
Хтось може показати, як правильно впровадити це в Діяльність? Право знаю, я не знаю, як правильно створювати ідентифікатори.
Філіпп

@Philipp Я маю справу з тим, як генерувати ідентифікатори, ти знайшов рішення?
sochas

1
Так за вашу відповідь @Philipp, я зробив це так само :)
sochas

2
Дякую @Philipp. Мені подобається ваша відповідь за гнучкість! Для автоматичного створення ідентифікаторів, у моєму випадку я insertUser()спочатку дзвоню, щоб отримати автоматично згенеровані userId, потім присвоюю це userIdполе userId у класі Pet, а потім цикл до insertPet().
Роберт

11

Оскільки Кімната не управляє відносинами суб’єктів, ви повинні налаштувати 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/…
tknell

Я створив цю проблему issueetracker.google.com/issues/62848977 на засобі відстеження проблем. Сподіваємось, вони щось з цим зроблять.
Акшай Чордія

1
Насправді не рідне рішення, але вам не потрібно використовувати інтерфейси для DAO, ви також можете використовувати абстрактні класи ... це означає, що методи зручності можуть розміщуватися в самому DAO, якщо ви не хочете мати клас обгортки. Дивіться мою відповідь для отримання додаткової інформації ...
Кіран Макдональд-Холл

6

В даний час не існує власного рішення цієї проблеми. Я створив це https://issuetracker.google.com/issues/62848977 на інструменті відстеження випусків Google, і команда Архітектурних компонентів заявила, що додасть власне рішення у версії 1.0 або після бібліотеки Room.

Тимчасове рішення:

Тим часом ви можете використовувати рішення, згадане tknell .

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

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

    petDao.insertAll(pets);
}

Дивлячись на інші рішення, я бачу спосіб зробити це, використовуючи абстрактний клас замість інтерфейсу під час створення дао і додаючи подібні конкретні методи, частина цих абстрактних класів. Але, я все ще не можу зрозуміти, як отримати примірник дао дао всередині цих методів? напр. як ви можете отримати екземпляр petDao всередині userDao?
Пуруш Павар

5

Зараз у версії 2.1.0 Кімната здається непридатною для моделей із вкладеними відносинами. Для їх підтримки було потрібно багато типових кодів. Наприклад, вставка списків вручну, створення та відображення локальних ідентифікаторів.

Ці операції зіставлення відносин виконуються нестандартно Requery https://github.com/requery/requery. Крім того, він не має проблем із вставкою Enum і має деякі конвертори для інших складних типів, таких як 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 для значення автоматичного збільшення (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". Вставка сутності рецепта спочатку це виправила для мене.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.