-
Dao 는 Room 의 메인 컴퍼넌트로, 각각의 DAO 는 app db 에 접근하는 추상적인 방법을 제공한다.
직접 query 하거나 queryBuilder 를 통해 data 에 접근하는 대신, 각각의 컴퍼넌트에 대해 분리된 접근을 할 수 있다.
게다가 DAO 는 test 를 위한 mock db access 를 제공하기가 쉬워진다.
-
DAO 는 interface 나 abstract class 가 될 수 있다.
abstract class 라면, constructor 를 통해서 RoomDatabase 를 유일한 param 으로 받을 수 있다.
Room 은 각각의 DAO 를 compile time 에 생성한다.
-
Room 은 builder 에서 allowMainThreadQueries() 를 호출하기 전까지 main thread 에서의 db 접근을 허용하지 않는다. LiveData 나 Flowable 등을 return 하는 async query 는 이 규칙에서 예외인데, 그 이유는 그들은 원하면 bg thread 로 쉽게 작동시킬 수 있기 때문이다.
Define methods for convenience
* Insert
-
DAO 에 @Insert 로 annotate 하면, Room 은 single transaction 으로 모든 param 을 insert 하는 impl 을 만들어낸다.
@Dao
interface MyDao{
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertUsers(vararg users: User)
@Insert
fun insertBothUsers(user1:User, user2:User)
@Insert
fun insertUsersAndFriends(user:User, friends:List<User>)
}
-
만약 @Insert 가 1 개의 param 만 받는다면, 새로운 rowId 인 long 을 return 한다.
만약 param 이 array 나 collection 이라면, long[] 이나 List<Long> 을 return 한다.
* Update
-
Update 는 param 으로 주어진 것을 수정한다.
match 는 각각의 primary key 를 기준으로 한다.
@Dao
interface MyDao{
@Update
fun updateUsers(vararg users:User)
}
보통 필요하지는 않지만, affected rows 에 해당 int 를 return 한다.
* Delete
-
primary key 비교를 통해 삭제한다.
@Dao
interface MyDao{
@Delete
fun deleteUsers(vararg users:User)
}
보통 필요하지는 않지만, 삭제된 row count 를 return 한다.
Query for information
-
@Query 는 DAO class 의 핵심 annotation 이다.
이를 통해 db 를 읽거나 db 에 쓰는 작업을 할 수 있다.
각각의 @Query 함수는 compile time 에 verify 된다. 만약 query 에 문제가 있다면 compile error 가 난다. (runtime 이 아니다!!)
-
Room 은 query 의 return value 도 검증한다.
그래서 만약 field name 이 일치하지 않거나 한다면, Room 은 두가지 방법으로 경고를 준다.
1. 몇개의 field name 만 match 하면 warning
2. 모든 field name 이 match 하지 않으면 error
* Simple queries
-
@Dao
interface MyDao{
@Query("SELECT * FROM user")
fun loadAllUsers(): Array<User>
}
컴파일타임에 Room 은 모든 user table 의 모든 column 을 안다.
그래서 query 가 syntax error 가 있거나, user table 이 db 에 없거나 하는 등의 error 가 있다면 app compile 타입에 에러를 보여준다.
* Passing parameters into the query
-
param 으로 filtering 관련된 정보를 제공할 수 있다. 예를 들면 특정 나이 이상의 user 만 query 하라는..
@Dao
interface MyDao{
@Query("SELECT * FROM user WHERE age > :minAge")
fun loadAllUsersOlderThan(minAge: Int): Array<User>
}
컴파일시에, Room 은 :minAge 를 찾아 param bind 를 수행한다.
-
여러개의 param 도 한번에 활용할 수 있다.
interface MyDao{
@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
fun loadAllUsersBetweenAges(minAge: Int, maxAge: Int): Array<User>
@Query("SELECT * FROM user WHERE first_name LIKE :search OR last_name LIKE :search")
fun findUserWithName(search: String): List<User>
}
* Returning subsets of columns
-
대부분의 경우 entity 의 몇개의 field 정보만 채용하고 싶을 것이다.
예를 들면 UI 가 user 의 모든 정보 대신, 성과 이름만을 표현한다던지 하는 케이스이다.
관심있는 column 만 query 함으로써 더 빠르게 결과를 얻을 수도 있고, res 도 아낄 수 있다.
-
Room 은 query result 로 column matching 만 잘 된다면, 어떤 Java-based object 든 return 할 수 있게 해준다. 예를 들어 성과 이름만 query 결과로 전달하고 싶다면 아래와 같이 하면 된다.
data class NameTuple(
@ColumnInfo(name = "first_name") val firstName: String?,
@ColumnInfo(name = "last_name") val lastName: String?
)
그리고 DAO 의 query 는 아래와 같이 하면 된다.
@Dao
interface MyDao{
@Query("SELECT first_name, last_name FROM user")
fun loadFullName(): List<NameTuple>
}
Room 은 first_name 과 last_name column 을 NameTuple 안의 field 와 mapping 해서 문제가 있다면 compile error 를 던진다.
만약 query 가 return Model 이 가진 field 보다 더 많은 column 을 return 한다면 warning 을 보여준다.
( 이 POJO 는 @Embedded annotation 을 쓸 수도 있다. )
* Passing a collection of arguments
-
특정 지역”들"에 있는 user 들을 query 하는 등의 여러 개의 param list(array) 를 동시에 처리해야 하는 경우가 있다.
@Dao
interface MyDao{
@Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
fun loadUsersFromRegions(regions: List<String>): List<NameTuple>
}
* Observable queries
-
query 를 할 때 UI 가 data 변화에 자동으로 반응하게 하고 싶을 때가 있다.
이를 위해서 LiveData(또는 Flow) 를 return 할 수 있다.
Room 은 LiveData 관련된 모든 코드를 자동으로 생성해서 제공한다.
@Dao
interface MyDao{
@Query("SELECT first_name, last_name FROM user WHERE region in (:regions)")
fun loadUsersFromRegionsSync(regions: List<String>): LiveData<List<User>>
}
버전 1.0 부터 Room 은 LiveData update 를 판단하기 위해 query 에 명시된 table list 를 이용한다.
* Reactive queries with RxJava
-
RxJava2 을 지원한다.
@Query : Room 은 Publisher, Flowable, Observable 의 return 을 지원한다.
@Insert, @Update, @Delete : Room 2.1.0 이상부터 Completable, Single<T>, Maybe<T> 의 return 을 지원한다.
-
rxjava2 관련 기능을 사용하려면 다음을 dependency 로 추가해야 한다.
dependencies{
implementation ‘androidx.room:room-rxjava2:2.1.0-beta01’ // version 은 알아서
}
-
@Dao
interface MyDao{
@Query("SELECT * FROM user WHERE id = :id LIMIT 1")
fun loadUserById(id: Int) : Flowable<User>
@Insert
fun insertLargeNumberOfUsers(users: List<User>): Maybe<Int>
@Insert // operation 이 성공적으로 끝남을 확신해야 한다.
fun insertLargeNumberOfUsers(varargs users: User): Completable
@Delete // 적어도 한명은 무조건 삭제됨을 보장해야 한다.
fun deleteAllUsers(users: List<User>):Single<Int>
}
* Direct cursor access
-
@Dao
interface MyDao{
@Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
fun loadRawUsersOlderThan(minAge: Int): Cursor
}
Cursor api 를 사용하는것은 강비추이다.
이것을 쓰는 케이스는 이미 cursor 를 요구하는 코드와의 호환성 이슈로 cursor 를 제공하는 경우를 제외하고는 쓰지 않는 것이 좋다.
* Querying multiple tables
-
몇몇 query 는 결과를 내기 위해 여러개의 table 에 접근할 수 있다.
Room 은 table join 을 허용한다.
게다가 결과값이 Flowable 이나 Livedata 같은 observable data type 일 경우, Room 은 관련된 모든 table 들을 관찰한다.
-
아래는 책을 빌리는 유저와 대여상태인 책에 대한 정보를 통합하는 것을 보여준다.
@Dao
interface MyDao{
@Query("""
SELECT * FROM book
INNER JOIN loan ON loan.book_id = book.id
INNER JOIN user ON user.id = loan.user_id
WHERE user.name LIKE :userName
""")
fun findBooksBorrowedByNameSync(userName:String) : List<Book>
}
-
@Dao
interface MyDao{
@Query(
"""SELECT user.name AS userName, pet.name as petName
FROM user, pet
WHERE user.id = pet.user_id
""")
fun loadUserAndPetNames(): LiveData<List<UserPet>>
data class UserPet(val userName:String?, val petName:String?)
}
* Write async methods with Kotlin coroutines
-
DAO method 에 suspend kotlin keyword 를 써서 kotlin coroutine 의 async 로 만들 수 있다.
이것이 main thread 에서 불릴 수 없음을 확인시켜준다.
@Dao
interface MyDao{
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUsers(vararg users:User)
@Update
suspend fun updateUsers(vararg users: User)
@Delete
suspend fun deleteUsers(vararg users: User)
@Query("SELECT * FROM user")
suspend fun loadAllUsers(): Array<User>
}
-
Room 을 kotlin coroutine 이랑 함께 사용하려면 Room 2.1 이상, Kotlin 1.3 이상, Couroutine 1.0 이상의 조건이 만족되어야 한다.
-
@Transaction 이 annotate 된 DAO 함수들에도 이것이 적용될 수 있다.
@Dao
abstract class UsersDao{
@Transaction
open suspend fun setLoggedInUser(loggedInUser: User){
deleteUser(loggedInUser)
insertUser(loggedInUser)
}
@Query("DELETE FROM users")
abstract fun deleteUser(user: User)
@Insert
abstract suspend fun insertUser(user: User)
}
-
@Transaction 을 마킹하면 앱 사이드에서 따로 하는 것을 피하는 것이 좋다.
-
참고자료
https://developer.android.com/training/data-storage/room/accessing-data
'프로그래밍 놀이터 > 안드로이드, Java' 카테고리의 다른 글
[android] MultiDex 에 대한 이야기 (0) | 2020.08.20 |
---|---|
[android] Define data using entities - Room 에 대해 알아보자 (0) | 2020.08.19 |
[android] 기초 - Room 에 대해 알아보자 (0) | 2020.08.17 |
[android] dpi 값이 바뀔 수 있구나?!! ㄷㄷㄷ (0) | 2020.08.15 |
[android] 앱 업데이트시 다운로드 사이즈 줄이기 (0) | 2020.08.14 |
댓글