Android Room: insira entidades de relação usando Room

88

Eu adicionei um relacionamento de muitos na Room usando Relation . Eu me referi a esta postagem para escrever o seguinte código para relacionamento na Room.

A postagem informa como ler os valores do banco de dados, mas armazenar as entidades no banco de dados resultou em userIdum vazio, o que significa que não há relação entre as 2 tabelas.

Eu não tenho certeza qual é o caminho ideal para insertum Usere List of Petpara o banco de dados, enquanto o userIdvalor.

1) Entidade do usuário:

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

2) Entidade Pet:

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

Agora, para buscar os registros do banco de dados, usamos o seguinte DAO:

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

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

EDITAR

Eu criei esse problema https://issuetracker.google.com/issues/62848977 no rastreador de problemas. Esperançosamente, eles farão algo a respeito.

Akshay Chordiya
fonte
Então eles dizem que "relações" é a prioridade na atualização 2.2. A versão atual é "Room 2.1.0-alpha03" de 4 de dezembro de 2018.
Lech Osiński
Sim, basta ler o comentário deles no rastreador de problemas. Vai levar algum tempo, entretanto você pode usar as soluções alternativas
Akshay Chordiya

Respostas:

43

Você pode fazer isso alterando seu Dao de uma interface para uma classe abstrata.

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

Você também pode ir mais longe fazendo com que um Userobjeto tenha um@Ignored List<Pet> pets

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

    @Ignored
    public List<Pet> pets
}

e então o Dao pode mapear UserWithPetspara o usuário:

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

Isso deixa você com o Dao completo:

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

Em vez disso, você pode querer ter os métodos insertAll(List<Pet>)e insertPetsForUser(User, List<Pet>)em um PetDAO ... como você particiona seus DAOs depende de você! :)

De qualquer forma, é apenas outra opção. Encapsular seus DAOs em objetos DataSource também funciona.

Kieran Macdonald-Hall
fonte
Ótima ideia colocar o método de conveniência em classe abstrata ao invés de interface.
Akshay Chordiya
7
se movermos os métodos relacionados ao Pet para o PetDao, como faremos referência aos métodos PetDao dentro do UserDao?
sbearben
4
obrigado pela sua resposta. Mas como o método pet.setUserId (user.getId ()) no método insertPetsForUser (User user, List <Pet> pets) define o userId? vamos supor que você está obtendo o objeto de usuário de uma API e, neste ponto, ele não tem nenhum id (primaryKey), pois não foi salvo no RoomDb, portanto, chamar user.getId () produzirá apenas um valor padrão de 0, para cada usuário, pois ainda não foi salvo. Como você lida com isso porque user.getId () sempre retorna 0 para cada usuário. Obrigado
Ikhiloya Imokhai
38

Não há solução nativa até qualquer atualização na Biblioteca de quartos, mas você pode fazer isso com um truque. Encontre abaixo mencionado.

  1. Basta criar um usuário com animais de estimação (ignorar animais de estimação). Adicione getter e setter. Observe que temos que definir nosso Id manualmente mais tarde e não podemos usar autogenerate.

    @Entity
    public class User {
          @PrimaryKey
          public int id; 
    
          @Ignore
          private List<Pet> petList;
    }
    
  2. Crie um animal de estimação.

    @Entity 
    public class Pet 
    {
        @PrimaryKey
        public int id;     
        public int userId; 
        public String name;
    }
    
  3. O UserDao deve ser uma classe abstrata em vez de uma Interface. Então, finalmente, em seu 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;
    }
    

Seu problema pode ser resolvido sem criar UserWithPets POJO.

Dipendra Sharma
fonte
2
Eu gosto deste porque evita o POJO UserWithPets. Usando métodos padrão, o DAO também pode ser uma interface. A única desvantagem que vejo é que insertUser () e insertPetList () são métodos públicos, mas não devem ser usados ​​por um cliente. Coloquei um sublinhado antes do nome desses métodos para mostrar que eles não devem ser usados, como mostrado acima.
guglhupf
1
Alguém pode mostrar como implementar isso corretamente em uma atividade? Sei que não sei gerar os Ids corretamente.
Philipp
@Philipp Estou tratando de como gerar os ids, você encontrou uma solução?
sochas
1
Obrigado pela sua resposta @Philipp, fiz da mesma forma :)
sochas
2
Obrigado @Philipp. Gosto da sua resposta pela flexibilidade! Para ids de geração automática, no meu caso eu chamo insertUser()primeiro para ser gerado automaticamente userId, depois atribuo isso userIdao campo userId na classe Pet e, em seguida, faço um loop para insertPet().
Robert
11

Como a Room não gerencia as relações das entidades, você mesmo deve definir o userIdem cada animal de estimação e salvá-los. Contanto que não haja muitos animais de estimação ao mesmo tempo, eu usaria um insertAllmétodo para mantê-lo curto.

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

Não acho que haja maneira melhor no momento.

Para tornar o manuseio mais fácil, eu usaria uma abstração na camada acima dos DAOs:

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

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

    petDao.insertAll(pets);
}
tknell
fonte
Tentei o mesmo usando Stream. Só espero que haja uma maneira melhor de fazer isso.
Akshay Chordiya
Não acho que exista uma maneira melhor, já que a documentação diz que eles deixaram intencionalmente as referências da biblioteca: developer.android.com/topic/libraries/architecture/…
tknell
Eu criei este problema issuetracker.google.com/issues/62848977 no rastreador de problemas. Esperançosamente, eles farão algo a respeito.
Akshay Chordiya
1
Não é realmente uma solução nativa, mas você não precisa usar interfaces para DAOs, você pode usar classes abstratas também ... o que significa que os métodos de conveniência podem ficar dentro do próprio DAO se você não quiser ter uma classe de wrapper. Veja minha resposta para mais informações ...
Kieran Macdonald-Hall
6

Atualmente não existe uma solução nativa para este problema. Eu criei isso https://issuetracker.google.com/issues/62848977 no rastreador de problemas do Google e a equipe de componentes de arquitetura disse que vai adicionar uma solução nativa na ou após a v1.0 da biblioteca Room.

Solução temporária:

Enquanto isso, você pode usar a solução mencionada por tknell .

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

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

    petDao.insertAll(pets);
}
Akshay Chordiya
fonte
Olhando para outras soluções, estou vendo uma maneira de fazer isso usando uma classe abstrata em vez de uma interface enquanto cria o dao e adiciona métodos concretos semelhantes, parte dessas classes abstratas. Mas, ainda não consigo entender como obter a instância dao filho dentro desses métodos? por exemplo. como você consegue obter a instância petDao dentro do userDao?
Purush Pawar
5

Agora na v2.1.0 Room parece não ser adequado para modelos com relações aninhadas. Era necessário muito código clichê para mantê-los. Ex: inserção manual de listas, criação e mapeamento de IDs locais.

Essas operações de mapeamento de relações são feitas imediatamente por Requery https://github.com/requery/requery. Além disso, ele não tem problemas com a inserção de Enums e possui alguns conversores para outros tipos complexos como URI.

Atividade principal
fonte
2

Consegui inseri-lo corretamente com uma solução alternativa relativamente simples. Aqui estão minhas entidades:

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

Estou usando autoGenerate para valor de incremento automático (long é usado com um propósito). Aqui está minha solução:

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

Tive problemas para vincular as entidades, gerar automaticamente "recipeId = 0" produzido o tempo todo. Inserir a entidade de receita primeiro consertou para mim.

stefan.georgiev.uzunov
fonte