[Android] Room 2.2와 새로워진 기능들

Harry The Great
해리의 유목코딩
6 min readOct 25, 2019

본 포스팅은 Dev Submmit에 발표된 What’s New in Room 세션요약을 중점으로합니다.

Jetpack의 기능 중 가장 유용하게 쓰는 기능 중 하나가 Android Room인 것 같습니다. 이전에 Room 예시 포스팅을 너무 대충 작성하였다가 많은 분들이 혼란을 느끼셔서 이번 포스팅은 최대한 이해하기 쉽게 작성하기로 마음먹었습니다.

Room의 정식버전 이후 2.2 버전까지 오며 많은 업데이트가 있었습니다. 특히 2.1 버전 때도 많이 바뀌었다 생각했는데 이번 2.2은 특히 더 많은 기능들이 생겼습니다.

비동기 쿼리

2.2 버전부터는 코 루틴의 flow 사용이 가능해졌습니다. 코 루틴의 Flow는 map, filter, take, zip처럼 들어온 스트림을 다른 형태로 데이터를 다시 가공하여 출력하고 콜드 스트림으로 데이터를 내보냅니다. 만약 데이터를 검증하고 API 호출을 할 때 각각을 둘로 나누어 Flow를 이용해 파이프라인과 같이 처리할 수도 있습니다. 간단한 예를 들어보겠습니다.

flow는 위 함수처럼 emit키워드를 이용해서 Flow<Dog>를 발행합니다. 이때 emit은 suspend function의 형태로 리턴 값을 발행하게 됩니다. limit을 인자로 받아 딱 limit 숫자만큼 데이터를 발행하고 어떤 인자도 주지 않는다면 1개의 값만을 발행합니다.

collect 키워드는 flow를 받아 실행합니다. 인자를 주지 않았기 때문에 updateDog(dog)와 show(dog)는 각각 1번씩 호출되지만 getDogsToPet(3)으로 선언한다면 데이터를 3개 발행하여 각각 3번 호출됩니다.

room에서는 Flow를 위와 같이 사용할 수 있습니다. 기존 livedata의 Transformation과 다른 점은 코 루틴 스레드에서 작동하기 때문에 백그라운드 혹은 서브 코 루틴에서 작동시킬 수 있습니다. 예를 들어 Room DB에 있는 특정 데이터들을 변경하고 이를 서버에 전송해야 할 때 Flow를 이용하여 파이프라인을 구성할 수 있습니다.Pre-Packed Databases

만약 이전에 데이터베이스의 초기화와 함께 넣어야 할 데이터가 있다면 위 코드처럼 데이터 생성의 콜백을 사용하여 상당히 복잡한 구현 해야 했지만 2.2 버전부터는 상당히 간결해졌습니다.

URL로부터 다운로드할 때나 특정 디렉터리에서 파일을 가져와 생성하는 경우에는 createFromFile, 에셋 폴더에서 가져오는 경우 createFromAsset을 사용할 수 있습니다.
아직 위 메서드는 테스트하지 못하여 어떤 파일 포맷인지는 확인해보진 못했지만 Room이 SQLite를 기반으로 하고 있기 때문에 SQLite 기반의 JSON포맷일 것 같습니다. (혹시 다르다면 댓글로 알려주세요.)

위 방법은 Room의 Memory Database에서는 사용할 수 없습니다.

데이터베이스 관계

1:1관계에서의 쿼리

위 그림과같이 주인(Owner)과 애완견(Pet) 테이블이 각각있고 두개의 테이블이 1:1 관계로 구성되어있다고 가정해보겠습니다.SELECT * FROM Owner

SELECT * FROM PetsWHERE PetOwnerId IN (id1, id,2 id3)

기존의 Room에서는 매칭되는 주인과 1:1 관계로 연결된 애완동물의 데이터를 얻고싶으면 2개의 쿼리를 순차적으로 실행하여 결과값을 가져와야 했습니다.

2.2부터는 Relation 어노테이션을 이용하여 1:1로 매칭되는 데이터를 함께 가져올때 조금 더 간결하게 작성할 수 있습니다.

1:N 관계

주인이 하나의 애완동물만 가지는 게 아닌 여러 애완동물을 가지게 되는 경우는 Relation어노테이션을 어떻게 사용해야 할까요? 이때는 몇 가지 옵션만 더 추가해주면 됩니다.

Relation어노테이션의 entity옵션 에 대상이되는 data class를 인자로 주고 pets을 Collection 형태로 바꾸어주면됩니다.

M:N관계

당연하겠지만 다대다 관계는 주인과 애완동물의 관계를 정의한 제3의 테이블이 필요합니다. 위처럼 Pet과 Owner테이블이 각각 존재하고 둘사이의 관계를 정의해주는 PetOwner 테이블이 있다고 가정해보겠습니다.

이전 Room에서 이런 관계의 테이블을 조회하기 위해서는 JOIN과 함께 상당히 복잡한 쿼리를 작성해야 했습니다. 2.2 버전부터는 associate옵션과 Junction을 사용할 수 있습니다.

defaultValue 옵션

@ColumnInfo(defaultValue = "CURRENT_TIMESTAMP")
val timestamp: Long

defaultValue옵션을 통해 SQLite에서 사용되는 Keywords 등을 사용할 수 있습니다.

어노테이션 옵션

android {
...
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [
"room.schemaLocation":"$projectDir/schemas".toString(),
"room.incremental":"true",
"room.expandProjection":"true"]
}
}
}
}

Gradle에는 Room과 관련하여 선언할 수 있는 옵션값이 있습니다.

schemaLocation 옵션

SchemaLocation은 JSON형식의 SQLite 데이터베이스를 지정된 디렉터리로 내보낼 수 있게 해 줍니다.

incremental 옵션

Gradle 이 room의 버전값을 올리는걸 허용해줍니다.

expandProjection

이전에는 2개의 테이블을 Embeded 어노테이션을 사용하여 조인하는 경우 동일한 칼럼 이름이 있을 때 제대로 쿼리를 가져오지 못하는 에러가 있었습니다.

2.2 버전부터는 prefix 옵션을 주어 이 문제를 해결할 수 있습니다. expandProjection옵션은 아직 Experiment이붙어있기 때문에 실제 프로덕션 사용 시 조심해서 사용해야 합니다.

마치며

이번 세션을 보며 Room 라이브러리 하나만도 이렇게 많이 바뀌었구나 하고 탄식이 나오는데 다른 건 또 얼마나 많이 바뀌었을까 겁이 나네요

--

--

Harry The Great
해리의 유목코딩

Android & IOS Developer 😀 미디움 이외에 스니펫이나 디버그노트로 활용하는 https://www.harrymikoshi.com/ 블로그도 운영하고있습니다.