SQLite는 관계형 데이터베이스이므로 항목 간 관계를 정의할 수 있습니다. 대부분의 객체 관계 매핑(ORM) 라이브러리에서는 항목 객체가 서로를 참조할 수 있지만, Room은 이러한 상호 참조를 명시적으로 금지합니다. 이 결정이 내려지게 된 기술적 추론에 관해 알아보려면 Room에서 객체 참조를 허용하지 않는 이유 이해를 참조하세요.
관계 유형
Room은 다음 관계 유형을 지원합니다.
- 일대일: 단일 항목이 다른 단일 항목과 관련된 관계를 나타냅니다.
- 일대다: 단일 항목이 다른 유형의 여러 항목과 관련될 수 있는 관계를 나타냅니다.
- 다대다: 한 유형의 여러 항목이 다른 유형의 여러 항목과 관련될 수 있는 관계를 나타냅니다. 일반적으로 연결 테이블이 필요합니다.
- 삽입된 객체를 사용하는 중첩된 관계: 항목이 다른 항목을 속성으로 포함하고
이 중첩된 항목이 다른 항목을 추가로 포함할 수 있는
관계를 나타냅니다.
@Embedded주석을 사용합니다.
두 가지 접근 방식 중에서 선택
Room에서는 2가지 방법으로 항목 간 관계를 정의하고 쿼리합니다. 다음 중 하나를 사용할 수 있습니다.
- 삽입된 객체가 있는 중간 데이터 클래스 또는
- 멀티매핑 반환 유형이 있는 관계형 쿼리 함수
중간 데이터 클래스를 사용해야 하는 것이 아니라면 멀티매핑 반환 유형 접근 방식을 사용하는 것이 좋습니다. 이 접근 방식에 관한 자세한 내용은 멀티매핑 반환을 참고하세요.
중간 데이터 클래스 접근 방식을 사용하면 복잡한 SQL 쿼리를 작성하지 않아도 되지만, 추가 데이터 클래스가 필요하므로 코드의 복잡성이 증가할 수도 있습니다. 즉, 멀티매핑 반환 유형 접근 방식에서는 SQL 쿼리로 더 많은 작업을 실행해야 하고 중간 데이터 클래스 접근 방식에서는 코드로 더 많은 작업을 해야 합니다.
중간 데이터 클래스 접근 방식 사용
중간 데이터 클래스 접근 방식에서는 Room 항목 간 관계를 모델링하는 데이터 클래스를 정의합니다. 이 데이터 클래스는 한 항목의 인스턴스와 다른 항목의 인스턴스로 구성된 쌍을 삽입된 객체로 보유합니다. 그러면 쿼리 함수가 앱에서 사용할 이 데이터 클래스의 인스턴스를 반환할 수 있습니다.
예를 들어 UserBook 데이터 클래스를 정의하여 특정 책을 대출한 도서관 이용자를 표시하고, 쿼리 함수를 정의하여 데이터베이스에서 UserBook 인스턴스 목록을 검색할 수 있습니다.
@Dao interface UserBookDao { @Query( """ SELECT user.name AS userName, book.name AS bookName FROM user JOIN book ON user.id = book.user_id """ ) fun loadUserAndBookNames(): LiveData<List<UserBook>> } data class UserBook(val userName: String, val bookName: String)
멀티매핑 반환 유형 접근 방식 사용
멀티매핑 반환 유형 접근 방식에서는 데이터 클래스를 추가로 정의하지 않아도 됩니다. 대신 원하는 매핑 구조에 기반하여 함수의 멀티매핑 반환 유형을 정의하고 SQL 쿼리에서 항목 간 관계를 직접 정의합니다.
예를 들어 다음 쿼리 함수는 User 및 Book 인스턴스의 매핑을 반환하여 특정 책을 대출한 도서관 이용자를 나타냅니다.
@Query( """ SELECT * FROM user JOIN book ON user.id = book.user_id """ ) suspend fun loadUserAndBookNames(): Map<User, List<Book>>
멀티매핑 반환 유형을 사용하면 다른 항목이 포함되지 않은 일대일 관계를 쿼리할 수도 있습니다. 다음 쿼리 함수는
User 및 대출한 책의 수의 매핑을 반환합니다.
@MapColumn 주석을 사용하여
@Query( """ SELECT user.*, COUNT(book.id) AS book_count FROM user LEFT JOIN book ON user.id = book.user_id GROUP BY user.id """ ) suspend fun loadUserAndBookCount(): Map<User, @MapColumn(columnName = "book_count") Int>
삽입된 객체 만들기
때로 개발자는 객체에 여러 속성이 포함되어 있는 경우에도 데이터베이스 로직에서 항목 또는 데이터 객체를 응집된 전체로 표현하려고 합니다. 이런
상황에서 @Embedded 주석을 사용하여 테이블 내의
하위 속성으로 객체를 분해합니다. 그러면 다른 열과 마찬가지로 삽입된 속성을 쿼리할 수 있습니다.
예를 들어 User 클래스는 street, city, state, postCode 속성으로 구성된 Address 속성을 포함할 수 있습니다. 구성된 열을 테이블에 별도로 저장하려면 User 클래스의 Address 속성에 @Embedded 주석을 추가합니다. 다음 코드 스니펫은 이 설정을 보여줍니다.
data class Address( val street: String?, val state: String?, val city: String?, @ColumnInfo(name = "post_code") val postCode: Int ) @Entity data class User( @PrimaryKey val id: Int, val firstName: String, @Embedded val address: Address? )
User 객체를 나타내는 테이블에는 id, firstName, street, state, city, post_code라는 이름의 열이 포함됩니다.
항목에 동일한 유형의 삽입된 속성이 여러 개 있는 경우
각 열을 고유하게 유지할 수 있습니다. prefix 속성을 설정하여 그러면 Room은 제공된 값을 삽입된 객체의 각 열 이름 시작 부분에 추가합니다.