Android Room: wstaw jednostki relacji za pomocą Room


88

Dodałem jedną do wielu relacji w pokoju przy użyciu relacji . Odniosłem się do tego postu, aby napisać następujący kod dla relacji w Pokoju.

Post mówi, jak odczytać wartości z bazy danych, ale przechowywanie jednostek w bazie danych spowodowało, userIdże było puste, co oznacza, że ​​nie ma związku między dwiema tabelami.

Nie jestem pewien, co jest idealnym sposobem na i do bazy danych, mając jednocześnie wartość.insertUserList of PetuserId

1) Podmiot użytkownika:

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

2) Zwierzę domowe:

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

Teraz, aby pobrać rekordy z DB, używamy DAO:

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

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

EDYTOWAĆ

Utworzyłem ten problem https://issuetracker.google.com/issues/62848977 w narzędziu do śledzenia problemów. Miejmy nadzieję, że coś z tym zrobią.


Więc mówią, że "relacje" są priorytetem w aktualizacji 2.2. Aktualna wersja to „Pokój 2.1.0-alpha03” z 4 grudnia 2018 r.
— Lech Osiński

Tak, po prostu przeczytaj ich komentarz w narzędziu do śledzenia problemów. To zajmie trochę czasu, tymczasem możesz skorzystać z obejścia
— Akshay Chordiya,

Odpowiedzi:


43

Możesz to zrobić, zmieniając swoje Dao z interfejsu na klasę abstrakcyjną.

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

Możesz także pójść dalej, mając Userobiekt z rozszerzeniem@Ignored List<Pet> pets

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

    @Ignored
    public List<Pet> pets
}

a następnie Dao może mapować UserWithPetsna użytkownika:

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

To pozostawia ci pełne Dao:

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

Zamiast tego możesz chcieć mieć metody insertAll(List<Pet>)i insertPetsForUser(User, List<Pet>)w PetDAO ... to, jak podzielisz swoje DAO, zależy od Ciebie! :)

W każdym razie to tylko inna opcja. Pakowanie DAO w obiektach DataSource również działa.


Świetny pomysł, aby umieścić wygodną metodę w klasie abstrakcyjnej zamiast w interfejsie.
— Akshay Chordiya

7
gdybyśmy przenieśli metody związane ze Zwierzakiem do metody Petetti, w jaki sposób odnosilibyśmy się do tych metod w aplikacji Użytkownik
— sbearben

4
dzięki za odpowiedź. Ale w jaki sposób pet.setUserId (user.getId ()) w metodzie insertPetsForUser (użytkownik użytkownika, List <Pet> zwierzęta domowe) ustawia userId? załóżmy, że pobierasz obiekt użytkownika z API i w tym momencie nie ma on identyfikatora (primaryKey), ponieważ nie został zapisany w RoomDb, więc wywołanie user.getId () da tylko domyślną wartość 0 dla każdego użytkownika, ponieważ nie został jeszcze zapisany. Jak sobie z tym poradzić, ponieważ user.getId () zawsze zwraca 0 dla każdego użytkownika. Dzięki
— Ikhiloya Imokhai

38

Nie ma natywnego rozwiązania aż do jakiejkolwiek aktualizacji w bibliotece pokoi, ale możesz to zrobić podstępem. Znajdź poniżej wymienione.

  1. Po prostu utwórz użytkownika ze zwierzętami (Ignoruj ​​zwierzęta). Dodaj getter i setter. Zauważ, że musimy później ręcznie ustawić nasz identyfikator i nie możemy go użyć autogenerate.

    @Entity
    public class User {
          @PrimaryKey
          public int id; 
    
          @Ignore
          private List<Pet> petList;
    }
    
  2. Utwórz zwierzaka.

    @Entity 
    public class Pet 
    {
        @PrimaryKey
        public int id;     
        public int userId; 
        public String name;
    }
    
  3. Klasa User replace powinna być klasą abstrakcyjną, a nie interfejsem. Następnie w końcu w Twoim User√.

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

Twój problem może zostać rozwiązany przez to bez tworzenia UserWithPets POJO.


2
Podoba mi się ten, ponieważ unika on POJO UserWithPets. Używając domyślnych metod, DAO może być również interfejsem. Jedynym minusem, jaki widzę, jest to, że insertUser () i insertPetList () są metodami publicznymi, ale nie powinny być używane przez klienta. Przed nazwą tych metod umieściłem podkreślenie, aby pokazać, że nie należy ich używać, jak pokazano powyżej.
— guglhupf

1
Czy ktoś może pokazać, jak prawidłowo wdrożyć to w działaniu? Dobrze wiem, nie wiem, jak poprawnie wygenerować identyfikatory.
— Filip

@Philipp Zajmuję się generowaniem identyfikatorów, czy znalazłeś rozwiązanie?
— sochas

1
Dziękuję za odpowiedź @Philipp, zrobiłem to w ten sam sposób :)
— sochas

2
Dzięki @Philipp Podoba mi się twoja odpowiedź za jej elastyczność! W przypadku automatycznego generowania identyfikatorów w moim przypadku insertUser()najpierw dzwonię, aby uzyskać automatycznie wygenerowane userId, a następnie przypisuję to userIddo pola userId w klasie Pet, a następnie zapętlam do insertPet().
— Robert

11

Ponieważ Room nie zarządza relacjami między bytami, musisz samodzielnie ustawić userIdna każdym zwierzaku i je zapisać. O ile nie ma zbyt wielu zwierząt na raz, użyłbym insertAllmetody, aby była krótka.

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

Nie sądzę, że w tej chwili jest lepszy sposób.

Aby ułatwić obsługę, użyłbym abstrakcji w warstwie powyżej DAO:

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

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

    petDao.insertAll(pets);
}

Próbowałem tego samego, używając Stream. Mam tylko nadzieję, że jest na to lepszy sposób.
— Akshay Chordiya

Nie sądzę, że jest lepszy sposób, ponieważ dokumentacja mówi, że celowo pozostawili referencje poza biblioteką: developer.android.com/topic/libraries/architecture/ ...
— tknell

Utworzyłem ten problem issuetracker.google.com/issues/62848977 w narzędziu do śledzenia problemów. Miejmy nadzieję, że coś z tym zrobią.
— Akshay Chordiya

1
Niezupełnie natywne rozwiązanie, ale nie musisz używać interfejsów dla DAO, możesz też używać klas abstrakcyjnych ... co oznacza, że ​​wygodne metody mogą znajdować się w samym DAO, jeśli nie chcesz mieć klasy opakowującej. Zobacz moją odpowiedź, aby uzyskać więcej informacji ...
— Kieran Macdonald-Hall

6

Obecnie nie ma natywnego rozwiązania tego problemu. Utworzyłem to https://issuetracker.google.com/issues/62848977 w narzędziu Google do śledzenia problemów, a zespół Architecture Components powiedział, że doda natywne rozwiązanie w wersji 1.0 biblioteki Room lub później.

Tymczasowe obejście:

W międzyczasie możesz skorzystać z rozwiązania wspomnianego przez tknell .

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

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

    petDao.insertAll(pets);
}

Patrząc na inne rozwiązania, widzę, że sposobem na to jest użycie klasy abstrakcyjnej zamiast interfejsu podczas tworzenia dao i dodanie podobnych metod konkretnych do tych klas abstrakcyjnych. Ale nadal nie mogę zrozumieć, jak uzyskać instancję podrzędną dao w tych metodach? na przykład. w jaki sposób możesz pobrać instancję pet√ w ramach użytkownika
— Purush Pawar

5

Teraz w wersji 2.1.0 Pokój wydaje się być nieodpowiedni dla modeli z zagnieżdżonymi relacjami. Do ich utrzymania potrzebne było mnóstwo gotowego kodu. Np. Ręczne wstawianie list, tworzenie i mapowanie lokalnych identyfikatorów.

Te operacje mapowania relacji są wykonywane po wyjęciu z pudełka przez Requery https://github.com/requery/requery Dodatkowo nie ma problemów z wstawianiem wyliczeń i ma kilka konwerterów dla innych złożonych typów, takich jak URI.


2

Udało mi się go poprawnie wstawić za pomocą stosunkowo prostego obejścia. Oto moje podmioty:

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

Używam autoGenerate do automatycznego zwiększania wartości (long jest używany z celami). Oto moje rozwiązanie:

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

Miałem problem z łączeniem encji, przez cały czas automatycznie generowałem produkowany "recipeId = 0". Wstawienie encji przepisu najpierw naprawiło to za mnie.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.