안녕하세요! 오늘은 안드로이드 앱 개발에 있어서 자주 사용되는 내부 저장소(DB) Room에 대해 알아보도록 하겠습니다.
이전까지는 효과적인 데이터 관리를 위해 파일 형식으로 데이터를 저장하는 관계형 DB인 SQLite를 주로 사용했었는데,
안드로이드 공식 문서에 따르면 아래와 같은 이유로 Room의 사용을 권장하고 있습니다.
- SQL 쿼리의 컴파일 시간 확인
- 반복적이고 오류가 발생하기 쉬운 상용구 코드를 최소화하는 편의 주석
- 간소화된 데이터베이스 이전 경로
Room이란?🤔
Room이란 AAC(Android Architecture Componets)로서
스마트폰 내장 DB에 데이터를 저장하기 위해 사용하는 라이브러리입니다.
Room은 ORM(Object Relational Mapping) 라이브러리로 DB 데이터를 Java나 Kotlin 객체로 매핑해줍니다.
내부적으로 SQLite를 사용하고 있지만, DB를 구조적으로 분리해 데이터 접근의 편의성을 높여주고,
유지보수에 편하다는 장점이 있습니다.
또한, 다양한 Annotation을 통해 컴파일시 코드들을 자동으로 생성해주고 LiveData, RxJava와 같은
Observation 형태를 지원하며 MVP, MVVM과 같은 아키텍쳐 패턴에도 쉽게 활용할 수 있습니다!
Room의 기본 구성요소
Room은 크게 엔티티(Entity), 데이터 접근 객체(DAO), 데이터베이스(DB) 3가지로 구성되어 있습니다.
1. Entity
DB내 table, 즉 DB에 저장할 데이터 형식으로 클래스 변수들이 칼럼(column)이 되어 entity를 구성합니다.
@Entity
data class User(
@PrimaryKey val uid: Int,
@ForeignKey(
entity = ParentUser::class, // 외래키 연결대상 Entity 클래스
parentColmns = ["userId"], // 외래키 연결대상 Entity 필드명
childColumns = ["userForeignKey"],
onDelete = ForeignKey.CASCADE // 삭제될 경우 같이 삭제 설정
) var userForeignKey: Int = 0, // 외래키 설정 Field (childColumn)
@ColumnInfo(name = "first_name") val firstName: String?,
@ColumnInfo(name = "last_name") val lastName: String?
)
- Annotation
@Entitiy(tableName = TABLE_NAME)
: table 이름을 선언합니다.
(table 이름을 따로 지정하지 않으면 class의 이름을 table의 이름으로 인식합니다.)
@PrimaryKey
: Entity는 1개의 primary key(기본키)를 가져야 합니다.
("autoGenerate = true" 로 설정시 키가 자동으로 생성됩니다😮)
@ColumnInfo
: table 내 column을 변수와 매칭시켜줍니다.
@ForeinKey
: 외래키 설정을 통해 table간 관계 설정이 가능합니다.
* entity - 외래키가 참조할 부모 Entitiy
* parentColumns - 참조할 부모 Entity의 Key값
* childColumns - 참조한 부모의 Key값을 저장할 현재 Entity 값들(parentColumns 개수와 동일하게!)
* onDelete - 참조하는 부모의 Entity가 삭제될때 이뤄질 행위
* onUpdate - 참조하는 부모의 Entity가 업데이트될때 이뤄질 행위
2. DAO
DB에 접근하여 수행할 작업을 메소드 형태로 정의합니다.(SQL 쿼리 사용가능)
리턴값으로 RxJava에서 사용할 수 있는 Observe 형태로도 가능하며,LiveData형태로도 반환이 가능합니다. 따라서 MVVM 패턴의 ViewModel과도 유연하게 사용가능합니다🤗
@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun getAll(): List<User>
@Query("SELECT * FROM user WHERE uid IN (:userIds)")
fun loadAllByIds(userIds: IntArray): List<User>
@Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
"last_name LIKE :last LIMIT 1")
fun findByName(first: String, last: String): User
@Insert
fun insertAll(vararg users: User)
@Delete
fun delete(user: User)
}
- Annotation
@insert
: Entity set을 삽입합니다.
@Entity로 정의된 class만 인자로 받거나, 그 class의 collection 또는 array만을 인자로 받을 수 있습니다.
인자가 하나인 경우 Long 타입을 리턴(insert 된 row ID) 받을 수 있고, 여러 개인 경우 Long[] 타입, list를 리턴 받을 수 있습니다.
("onConflict = OnConflictStrategy.REPLACE" 을 사용하면 update 와 동일한 기능을 수행할 수 있습니다.)
@update
: Entity set을 업데이트합니다.
리턴값으로 업데이트된 행 수를 받습니다.
@delete
: Entity set을 삭제합니다.
리턴값으로 삭제된 행 수를 받습니다.
@query
: @Query를 사용하여 DB를 조회할 수 있습니다.
컴파일 시간에 리턴되는 객체의 필드와 SQL 결과로 나오는 column의 이름이 맞는지 확인하여
일부가 match되면 warning, match되는게 없다면 error를 보냅니다.
3. Room DB
DB의 전체적인 소유자 역할을 하며, DB 생성 및 버전을 관리합니다.
또한, DAO 에서 가져온 데이터를 CRUD(생성,읽기,갱신,삭제) 합니다.
* DAO 객체들을 반환할 수 있는 메소드를 가지는 추상클래스(abstract) 형태로 정의해야합니다.
* Room 객체를 생성할때 많은 리소스를 소모하게 때문에 Singleton 패턴으로 정의해야 효율적입니다!
/**
* AppDatabase
* DB 인스턴스 초기화
*
* interface와 abstract의 차이..?
* -> abstarct에서는 구현도 가능
*/
@Database(entities = [User::class], version = 1, exportSchema = false)
abstract class AppDatabase: RoomDatabase() {
abstract fun userDao(): UserDAO
companion object {
private var instance: AppDatabase? = null
@Synchronized
fun getInstance(context: Context): AppDatabase? {
if(instance == null) {
// 단 한번의 접근을 위한 lock(다중스레드)
synchronized(this) {
instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"db"
).build()
}
}
return instance
}
}
}
- Annotation
@Database
: 해당 클래스가 데이터베이스임을 알려줍니다.
* entities - 해당 DB에 어떤 entity(table)이 있는지 명시합니다.
* version - DB 버전으로, Scheme가 변경되면 version도 변경되어야합니다!
* exportSchema - Room의 Schema 구조를 폴더로 export할지 여부,
(DB 버전의 히스토리를 기록할 수 있는 점에서 true로 설정하는 것이 유리👍)
Gradle
// Room
implementation 'androidx.room:room-runtime:2.4.2'
implementation 'androidx.room:room-ktx:2.4.2'
kapt 'androidx.room:room-compiler:2.4.2'
사용
val db = AppDatabase.getInstance(application)
val userDao = db!!.userDao()
val users = userDao.getAll()
주의할점으로는 메인 스레드에서 DB접근이 불가능하기 때문에 Thread를 새로 생성해서 사용하거나,
코루틴을 이용하시면 됩니다.
마무리하며..✍
오늘은 간단하게 내부저장소 라이브러리인 Room에 대해 알아보았습니다.
오늘 설명한 사용법은 정말 기초적인 Room 사용법만 담고 있고,
좀 더 복잡하고 심화적인 쿼리나 Room 사용법이 많기 때문에 더 많은 공부가 필요할 것 같습니다🥲
또 Room 라이브러리와 함께 ViewModel, Coroutine, Flow 등이 사용되므로 이것들에 대한
추가적인 공부도 필요하다고 느꼈습니다.
지금까지 부족한 글 읽어주셔서 감사합니다😊
참고
https://developer.android.com/training/data-storage/room?hl=ko#kts
Room을 사용하여 로컬 데이터베이스에 데이터 저장 | Android 개발자 | Android Developers
Room 라이브러리를 사용하여 더 쉽게 데이터를 유지하는 방법 알아보기
developer.android.com
https://velog.io/@ryalya/Android-DB-Room%EC%9D%B4%EB%9E%80
[Android] DB Room이란?
21.07.15 공부한 것을 정리하는 용도의 글이므로 100% 정확하지 않을 수 있습니다. 참고용으로만 봐주시고, 내용이 부족하다고 느끼신다면 다른 글도 보시는 것이 좋습니다. + 틀린 부분, 수정해
velog.io
https://todaycode.tistory.com/39
안드로이드 Room의 사용법과 예제
1. Room 1-1. Room이란? 1-2. Room 구조 1-3. TMI 2. 사용법 2-1. gradle 2-2. Entity 2-3. DAO 2-4. Room Database 2-5. 데이터 베이스 사용 3. 예제 3-1. room + singleton + coroutine 3-..
todaycode.tistory.com
'Android' 카테고리의 다른 글
[안드로이드 / Kotlin] Status bar 투명하게 (with DrawerLayout) (0) | 2022.05.18 |
---|---|
[안드로이드 / Kotlin] Dagger Hilt (0) | 2022.05.17 |
[안드로이드 / Kotlin] ViewModel(뷰모델) (0) | 2022.04.12 |
[안드로이드 / Kotlin] 키해시(Key Hash) 얻기 (0) | 2022.04.06 |
[안드로이드 / Kotlin] 코루틴(Coroutine) (0) | 2022.03.21 |