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

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

В сообщении рассказывается, как читать значения из базы данных, но сохранение сущностей в базе данных привело к тому, что userId будет пустым, что означает отсутствие связи между двумя таблицами.

Я не уверен, каков идеальный способ insert a User и List of Pet в базу данных, имея значение userId.

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 по проблеме трекер. Надеюсь, они что-то с этим сделают.


person Akshay Chordiya    schedule 21.06.2017    source источник
comment
Так они говорят, что отношения в обновлении 2.2 являются приоритетом. Текущая версия - Room 2.1.0-alpha03 от 4 декабря 2018 года.   -  person Lech Osiński    schedule 08.01.2019
comment
Да, просто прочтите их комментарий в системе отслеживания проблем. Это займет время, а пока вы можете использовать обходные пути   -  person Akshay Chordiya    schedule 11.01.2019
comment
Они снизили приоритетность этой проблемы (2021 год).   -  person Chandrahas Aroori    schedule 17.01.2021


Ответы (6)


Вы можете сделать это, изменив свой Дао с интерфейса на абстрактный класс.

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

person Kieran Macdonald-Hall    schedule 31.10.2017
comment
Отличная идея поместить удобный метод в абстрактный класс вместо интерфейса. - person Akshay Chordiya; 31.10.2017
comment
если бы мы переместили связанные с Pet методы в PetDao, как бы мы могли ссылаться на методы PetDao в UserDao? - person sbearben; 31.10.2018
comment
спасибо за Ваш ответ. Но как pet.setUserId (user.getId ()) в методе insertPetsForUser (User user, List ‹Pet› pets) устанавливает userId? предположим, что вы получаете объект пользователя из API, и на данный момент у него нет идентификатора (primaryKey), поскольку он не был сохранен в RoomDb, поэтому вызов user.getId () даст только значение по умолчанию 0 для каждого пользователя, так как он еще не сохранен. Как вы справляетесь с этим, потому что user.getId () всегда возвращает 0 для каждого пользователя. Спасибо - person Ikhiloya Imokhai; 11.03.2019

В библиотеке комнат нет собственного решения до тех пор, пока не появятся обновления, но это можно сделать хитростью. Найдите ниже упомянутое.

  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.

person Dipendra Sharma    schedule 04.11.2017
comment
Мне нравится этот, потому что он избегает POJO UserWithPets. Используя методы по умолчанию, DAO также может быть интерфейсом. Единственным недостатком, который я вижу, является то, что insertUser () и insertPetList () являются общедоступными методами, но не должны использоваться клиентом. Я поставил подчеркивание перед названиями этих методов, чтобы показать, что их не следует использовать, как показано выше. - person guglhupf; 23.11.2017
comment
Может ли кто-нибудь показать, как правильно реализовать это в Activity? Правильно знаю, что я не знаю, как правильно генерировать идентификаторы. - person Philipp; 26.11.2019
comment
@Philipp Я занимаюсь созданием идентификаторов, вы нашли решение? - person sochas; 04.12.2019
comment
@sochas Не совсем, я создал их в своей деятельности, потому что не нашел лучшего способа. - person Philipp; 05.12.2019
comment
Спасибо за ответ @Philipp, я сделал то же самое :) - person sochas; 05.12.2019
comment
Спасибо @Philipp. Мне нравится твой ответ за его гибкость! Для автоматического создания идентификаторов в моем случае я сначала вызываю insertUser(), чтобы получить автоматически сгенерированный userId, затем назначаю этот userId полю userId в классе Pet, а затем зацикливаюсь на insertPet(). - person Robert; 02.01.2020
comment
Как вы можете позвонить insertPetList(pets); перед insertUser(user)? Разве запись домашнего животного не будет ссылаться на несуществующую запись пользователя до insertUser(user)? - person ericn; 29.04.2021

Поскольку 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);
}
person tknell    schedule 21.06.2017
comment
Я пробовал то же самое с Stream. Я просто надеюсь, что есть способ лучше. - person Akshay Chordiya; 21.06.2017
comment
Я не думаю, что есть лучший способ, поскольку в документации говорится, что они намеренно оставили ссылки вне библиотеки: developer.android.com/topic/libraries/architecture/ - person tknell; 21.06.2017
comment
Я создал эту проблему Issueetracker.google.com/issues/62848977 в средстве отслеживания проблем. Надеюсь, они что-то с этим сделают. - person Akshay Chordiya; 21.06.2017
comment
Не совсем собственное решение, но вам не нужно использовать интерфейсы для DAO, вы также можете использовать абстрактные классы ... это означает, что удобные методы могут находиться внутри самого DAO, если вы не хотите иметь класс-оболочку. См. Мой ответ для получения дополнительной информации ... - person Kieran Macdonald-Hall; 31.10.2017

В настоящее время нет собственного решения этой проблемы. Я создал этот 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);
}
person Akshay Chordiya    schedule 04.09.2017
comment
Глядя на другие решения, я вижу способ сделать это - использовать абстрактный класс вместо интерфейса при создании dao и добавлении аналогичных конкретных методов в эти абстрактные классы. Но я все еще не могу понять, как получить дочерний экземпляр dao внутри этих методов? например. как вы можете получить экземпляр petDao внутри userDao? - person Purush Pawar; 08.11.2020

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

Эти операции сопоставления отношений выполняются из коробки Requery https://github.com/requery/requery. Кроме того, у него нет проблем со вставкой перечислений и есть некоторые преобразователи для других сложных типов, таких как URI.

person MainActivity    schedule 01.08.2019

Мне удалось правильно вставить его с помощью относительно простого обходного пути. Вот мои сущности:

   @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". Вставка объекта рецепта сначала исправила это для меня.

person stefan.georgiev.uzunov    schedule 24.04.2020