Android Room : Room을 사용하여 관계 엔티티 삽입


88

Relation을 사용하여 Room에 일대 다 관계를 추가했습니다 . 이 게시물 을 참조하여 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) 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;
}

이제 DB에서 레코드를 가져 오기 위해 다음을 사용합니다 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 업데이트에서 "관계"가 우선이라고 말합니다. 현재 버전은 12 월 4 일 2018에서 "룸 2.1.0-alpha03을"입니다
레흐 Osiński

예, 이슈 트래커에서 댓글을 읽으십시오. 그것은 그 사이에 당신이 해결 사용할 수있는 시간이 걸릴거야이다
하기 Akshay Chordiya

답변:


43

Dao를 인터페이스에서 추상 클래스로 변경하여이를 수행 할 수 있습니다.

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

그런 다음 Dao는 UserWithPetsUser에 매핑 할 수 있습니다 .

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를 남깁니다.

@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 개체로 래핑하는 것도 작동합니다.


인터페이스 대신 추상 클래스에 편의 메서드를 넣는 것이 좋습니다.
Akshay Chordiya

7
Pet 관련 메서드를 PetDao로 옮긴다면 UserDao 내에서 PetDao 메서드를 어떻게 참조할까요?
sbearben

4
답변 해 주셔서 감사합니다. 그러나 insertPetsForUser (User user, List <Pet> pets) 메서드의 pet.setUserId (user.getId ())는 어떻게 userId를 설정합니까? API에서 사용자 객체를 가져오고이 시점에서 RoomDb에 저장되지 않았기 때문에 id (primaryKey)가 없다고 가정 해 보겠습니다. 따라서 user.getId ()를 호출하면 각각에 대해 기본값 0 만 생성됩니다. 아직 저장되지 않은 사용자입니다. user.getId ()는 항상 각 사용자에 대해 0을 반환하기 때문에 어떻게 처리합니까? 감사합니다
Ikhiloya Imokhai

38

Room Library에서 업데이트 할 때까지는 기본 솔루션이 없지만 트릭으로이를 수행 할 수 있습니다. 아래에서 언급 한 내용을 찾으십시오.

  1. 애완 동물이있는 사용자 만들기 (애완 동물 무시). getter 및 setter를 추가하십시오. 나중에 ID를 수동으로 설정해야하며 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;
    }
    

UserWithPets POJO를 만들지 않고도 이것으로 문제를 해결할 수 있습니다.


2
UserWithPets POJO를 피하기 때문에 나는 이것을 좋아합니다. 기본 방법을 사용하면 DAO가 인터페이스가 될 수도 있습니다. 유일한 단점은 insertUser () 및 insertPetList ()가 공용 메서드이지만 클라이언트에서 사용해서는 안된다는 것입니다. 위와 같이 사용하지 않아야 함을 나타 내기 위해 이러한 메서드의 이름 앞에 밑줄을 표시했습니다.
guglhupf

1
누군가 활동에서 이것을 올바르게 구현하는 방법을 보여줄 수 있습니까? Ids를 올바르게 생성하는 방법을 모르겠습니다.
Philipp

@Philipp ID를 생성하는 방법을 다루고 있는데 해결책을 찾았습니까?
sochas

1
답장을위한 Thaks @Philipp, 나는 같은 방식으로 그것을했다 :)
sochas dec

2
@Philipp에게 감사드립니다 유연성에 대한 귀하의 답변이 마음에 듭니다! 자동 생성 ID의 경우, 필자의 경우 insertUser()먼저 자동 생성을 호출 userId한 다음 userIdPet 클래스의 userId 필드에 할당 한 다음 insertPet().
Robert

11

룸은 엔티티의 관계를 관리하지 않기 때문에 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);
}

나는 Stream. 더 나은 방법이 있기를 바랍니다.
Akshay Chordiya

문서에서 의도적으로 라이브러리에서 참조를 제외했다고하므로 더 나은 방법이 없다고 생각합니다. developer.android.com/topic/libraries/architecture/…
tknell

이 이슈 트래커에 issuetracker.google.com/issues/62848977 을 만들었습니다 . 바라건대 그들은 그것에 대해 뭔가를 할 것입니다.
Akshay Chordiya

1
실제로 네이티브 솔루션은 아니지만 DAO에 대한 인터페이스를 사용할 필요가 없습니다. 추상 클래스도 사용할 수 있습니다. 즉, 래퍼 클래스를 원하지 않는 경우 편리한 메서드가 DAO 자체 내에있을 수 있습니다. ... 추가 정보를 원하시면 내 대답을 참조하십시오
키어런 맥도날드 - 홀을

6

현재이 문제에 대한 기본 해결책없습니다 . 이 https://issuetracker.google.com/issues/62848977 을 Google의 문제 추적기에 만들었고 아키텍처 구성 요소 팀은 Room 라이브러리 v1.0 또는 그 이후에 기본 솔루션을 추가 할 것이라고 말했습니다.

임시 해결 방법 :

한편 tknell에서 언급 한 솔루션을 사용할 수 있습니다 .

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

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

    petDao.insertAll(pets);
}

다른 솔루션을 살펴보면 인터페이스 대신 추상 클래스를 사용하는 동시에 dao를 만들고 해당 추상 클래스의 유사한 구체적인 메서드 부분을 추가하는 방법이 있습니다. 그러나 이러한 메서드 내에서 자식 dao 인스턴스를 얻는 방법을 여전히 이해할 수 없습니까? 예. userDao 내에서 petDao 인스턴스를 어떻게 얻을 수 있습니까?
Purush Pawar

5

이제 v2.1.0에서 Room은 중첩 된 관계가있는 모델에 적합하지 않은 것 같습니다. 이를 유지하려면 많은 상용구 코드가 필요했습니다. 예 : 목록의 수동 삽입, 로컬 ID 생성 및 매핑.

이 관계 매핑 작업은 Requery https://github.com/requery/requery에 의해 즉시 수행됩니다. 또한 Enum 삽입에 문제가 없으며 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은 purpoes와 함께 사용됨). 내 해결책은 다음과 같습니다.

  @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"이 항상 발생했습니다. 레시피 엔티티를 먼저 삽입하면 문제가 해결되었습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.