Android Room: вставка сущностей отношений с помощью Room

88

Я добавил отношения "один ко многим" в Room с помощью Relation . Я сослался на этот пост, чтобы написать следующий код для отношения в Room.

В сообщении рассказывается, как читать значения из базы данных, но сохранять объекты в базе данных, в результате чего 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) POJO UserWithPets:

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

а затем Дао может отображаться 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
Если бы мы переместили методы, связанные с Pet, в PetDao, как бы мы могли ссылаться на методы PetDao в UserDao?
sbearben
4
спасибо за Ваш ответ. Но как pet.setUserId (user.getId ()) в методе insertPetsForUser (User user, List <Pet> pets) устанавливает 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 должен быть абстрактным классом, а не интерфейсом. Затем, наконец, в вашем 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;
    }
    

Таким образом, ваша проблема может быть решена без создания POJO UserWithPets.

Дипендра Шарма
источник
2
Мне нравится этот, потому что он избегает POJO UserWithPets. Используя методы по умолчанию, DAO также может быть интерфейсом. Единственным недостатком, который я вижу, является то, что insertUser () и insertPetList () являются общедоступными методами, но не должны использоваться клиентом. Я поставил подчеркивание перед названиями этих методов, чтобы показать, что их не следует использовать, как показано выше.
guglhupf
1
Может ли кто-нибудь показать, как правильно реализовать это в Activity? Правильно знаю, что я не знаю, как правильно генерировать идентификаторы.
Филипп
@Philipp Я занимаюсь созданием идентификаторов, вы нашли решение?
sochas
1
Спасибо за ответ @Philipp, я сделал то же самое :)
sochas
2
Спасибо @Philipp. Мне нравится твой ответ за его гибкость! Для автоматической генерации идентификаторов в моем случае я insertUser()сначала вызываю, чтобы автоматически сгенерировать userId, затем назначаю это userIdполю userId в классе Pet, а затем зацикливаюсь на 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);
}
tknell
источник
Я пробовал то же самое, используя Stream. Я просто надеюсь, что есть способ лучше.
Акшай Чордия
Я не думаю, что есть лучший способ, поскольку в документации говорится, что они намеренно оставили ссылки из библиотеки: developer.android.com/topic/libraries/architecture/…
tknell
Я создал эту проблему issueetracker.google.com/issues/62848977 в системе отслеживания проблем. Надеюсь, они что-то с этим сделают.
Акшай Чордия
1
На самом деле это не собственное решение, но вам не нужно использовать интерфейсы для DAO, вы также можете использовать абстрактные классы ... это означает, что удобные методы могут находиться внутри самого DAO, если вы не хотите иметь класс-оболочку. См. Мой ответ для получения дополнительной информации ...
Киран Макдональд-Холл
6

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

Временное решение:

Тем временем вы можете использовать решение, упомянутое tknell .

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

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

    petDao.insertAll(pets);
}
Акшай Чордия
источник
Глядя на другие решения, я вижу, что это можно сделать, используя абстрактный класс вместо интерфейса при создании dao и добавлении аналогичных конкретных методов в эти абстрактные классы. Но я все еще не могу понять, как получить дочерний экземпляр dao внутри этих методов? например. как вы можете получить экземпляр petDao внутри userDao?
Пуруш Павар,
5

Теперь в v2.1.0 Room кажется не подходящим для моделей с вложенными отношениями. Для их поддержки требовалось много шаблонного кода. Например, вставка списков вручную, создание и отображение локальных идентификаторов.

Эти операции сопоставления отношений выполняются из коробки Requery https://github.com/requery/requery. Дополнительно у него нет проблем со вставкой перечислений и есть некоторые преобразователи для других сложных типов, таких как 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". Вставка объекта рецепта сначала исправила это для меня.

stefan.georgiev.uzunov
источник