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

Akshay Chordiya
źródło
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.

Kieran Macdonald-Hall
źródło
Ś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.

Dipendra Sharma
źródło
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);
}
tknell
źródło
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);
}
Akshay Chordiya
źródło
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.

Główna aktywność
źródło
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.

stefan.georgiev.uzunov
źródło