Room DB 기본 설정 후, 데이터 저장 및 가져오기
Room DB 살펴보기
사내 서비스에서 기존에 사용하고 있던 Database가 SQLite, Realm DB 두 가지 종류였는데, 이번에 새로운 테이블 구성이 필요해서 Jetpack에서 제공하는 Room DB를 사용해 봤다.
https://developer.android.com/training/data-storage/room?hl=ko
Room을 사용하여 로컬 데이터베이스에 데이터 저장 | Android 개발자 | Android Developers
Room 라이브러리를 사용하여 더 쉽게 데이터를 유지하는 방법 알아보기
developer.android.com
Room DB를 그냥 사용하려는 것은 아니고, 당연히 SQLite보다 장점이 있기에 사용하게 되었다.
1. 간편한 사용 및 유지보수 - Room은 SQLite의 추상화 계층을 제공하여 좀 더 쉽게 데이터베이스를 다룰 수 있게 해 줍니다. SQL 질의문이나 테이블 생성과 같은 작업을 자동으로 처리하여 코드를 간소화하고 유지보수를 쉽게 만듭니다.
2. 컴파일 시간 검증 - Room은 컴파일 시간에 SQL 쿼리를 확인하므로 런타임에 발생할 수 있는 일부 오류를 컴파일 단계에서 미리 확인할 수 있습니다. 이는 오타나 잘못된 SQL 문장 등으로 인한 런타임 오류를 방지할 수 있도록 도와줍니다.
3. LiveData 및 RxJava 지원 - Room은 LiveData나 RxJava와 같은 안드로이드 아키텍처 컴포넌트와 통합이 쉽도록 설계되었습니다. 이를 통해 데이터베이스의 변경 사항을 관찰하고 이에 대한 적절한 대응을 할 수 있습니다.
4. 코드 생성을 통한 불필요한 코드 제거 - Room은 주요 데이터베이스 작업을 수행하기 위해 코드를 생성합니다. 이렇게 함으로써 반복적이고 오류가 발생하기 쉬운 작업을 간소화하고, 불필요한 코드를 최소화할 수 있습니다.
작업을 진행하면서 느꼈는데, SQLite 초기 구성보다 작업이 수월했다.
Room의 구성 요소는 다음과 같다.
데이터베이스 클래스: 데이터베이스를 보유하고 앱의 영구 데이터와의 기본 연결을 위한 기본 액세스 포인트 역할을 합니다.
데이터 항목: 앱 데이터베이스의 테이블을 나타냅니다.
데이터 액세스 객체(DAO): 앱이 데이터베이스의 데이터를 쿼리, 업데이트, 삽입, 삭제하는 데 사용할 수 있는 메서드를 제공합니다.

Room DB 기본 설정 - Gradle / Entity / DAO / DataBase
이제 Gradle 설정부터 먼저 확인해 보자
implementation "androidx.room:room-runtime:2.5.2"
kapt "androidx.room:room-compiler:2.5.2"
요즘은 KSP를 kapt 대신 사용한다고 하는데, 현재 프로젝트 환경 때문에 kapt로 진행했다. (2.5.2 버전도 프로젝트 환경에 적합하게 선택)
코드 분석이 kapt보다 최대 2배 빠르다고 하는데.. 추후에 적용해 보는 과정을 확인해 봐야겠다.
https://developer.android.com/studio/build/migrate-to-ksp?hl=ko
kapt에서 KSP로 이전 | Android 개발자 | Android Developers
주석 프로세서의 사용을 kapt에서 KSP로 이전합니다.
developer.android.com
다음으로 데이터 형태의 기본이 될 Entity를 생성해 준다.
@Entity(tableName = "timeData")
data class TimeData(
@PrimaryKey
val dateLabel: String,
val updatedTime: Long
)
tableName을 지정해 주고, @PrimaryKey를 선언해 준다. 필자의 경우, dateLabel이라는 String을 고유한 키 값으로 가질 예정이라 저렇게 구성했고, 키를 Long 타입 id로 지정하는 경우에는
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
이렇게 자동 생성을 활용하여 새로운 데이터가 추가될 때마다 id 값을 증가시킬 수 있다.
어떤 식으로 PrimaryKey를 지정할지는 중복 오류를 감안하여 자유롭게 구성 가능하다.
이번엔 쿼리문이 입력될 DAO를 구성해 보자
@Dao
interface TimeDataDao {
...
@Query("UPDATE timeData SET updatedTime = :currentTime WHERE dateLabel = :dateLabel")
fun update(dateLabel: String, currentTime: Long)
@Insert
fun insert(timeData: TimeData)
@Query("DELETE FROM timeData")
fun deleteAll()
}
인터페이스로 정의된 Dao 내부에 Insert 및 Query 어노테이션을 활용하여 목적에 맞는 쿼리문을 작성할 수 있다.
쿼리문 작성은 DB 공부 + GPT 질문을 통해 이해하고 작성해 나가자.
이제 Database를 정의한다.
@Database(entities = [TimeData::class], version = 1)
abstract class TimeDatabase : RoomDatabase() {
abstract fun timeDao(): timeDataDao
companion object {
private var INSTANCE: TimeDatabase? = null
fun getInstance(context: Context): TimeDatabase? {
if (INSTANCE == null) {
synchronized(TimeDatabase::class) {
INSTANCE = Room.databaseBuilder(
context.applicationContext,
TimeDatabase::class.java, "Timedata.db"
).build()
}
}
return INSTANCE
}
}
}
version은 Entity의 구조가 형태가 변경될 시, 업그레이드에 필요하다.
추가적으로 빌드 옵션에는 몇 가지 기능들이 포함되어 있는데,
.allowMainThreadQueries()
이와 같이 Main Thread에서 쿼리를 동작시키도록 빌드할 수도 있는데, 테스트나 초기 개발에서만 사용하고 배포 시에는 제거하는 것이 좋다. 배포 후에는 관리되는 데이터가 증가할 시, 쿼리 비용이 증가할 수 있고, 그렇다면 백그라운드 Thread에서 처리하는 것이 바람직하기 때문. 필자는 Coroutine을 활용할 예정이다.
.fallbackToDestructiveMigration()
마이그레이션 로직을 따로 추가하지 않고, Entity 변경 시, 기존 데이터를 모두 초기화시킨 다음에 새로 빌드하겠다는 함수이다.
이 또한 테스트나 초기 개발 단계에서만 사용하는 것이 좋다.
Coroutine + Repository
이제, 실제로 쿼리해온 데이터를 추출하고, 또 업데이트하는 중간 단계의 Repository를 구성해 본다.
이 부분은 DB를 어떻게 활용할지와 프로젝트 아키텍처에 따라 상이할 수 있겠다.
class TimeRepository(context: Context) {
private val timeDatabase = TimeDatabase.getInstance(context)
private val timeDataDao = timeDatabase?.timeDao()
.
.
.
private suspend fun updateTime(dateLabel: String, updatedTime: Long) {
withContext(Dispatchers.IO) { timeDataDao?.update(dateLabel, updatedTime) }
}
private suspend fun insertTime(dateLabel: String, updatedTime: Long) {
val timeData = TimeData(dateLabel, updatedTime)
withContext(Dispatchers.IO) { timeDataDao?.insert(timeData) }
}
}
context를 인자로 받아 Database 인스턴스와 Dao를 선언해 주었다.
각 DB 접근 함수에서는 Dispatchers.IO 기반으로 쿼리 동작이 처리되도록 Coroutine으로 감싸주었다.
이로서 기본적인 Room DB 처리 로직 구성은 마무리!
다음에는 Room DB에 대한 추가적인 기능들에 대해 알아보겠다.
Room DB 기본 설정 후, 데이터 저장 및 가져오기
Room DB 살펴보기
사내 서비스에서 기존에 사용하고 있던 Database가 SQLite, Realm DB 두 가지 종류였는데, 이번에 새로운 테이블 구성이 필요해서 Jetpack에서 제공하는 Room DB를 사용해 봤다.
https://developer.android.com/training/data-storage/room?hl=ko
Room을 사용하여 로컬 데이터베이스에 데이터 저장 | Android 개발자 | Android Developers
Room 라이브러리를 사용하여 더 쉽게 데이터를 유지하는 방법 알아보기
developer.android.com
Room DB를 그냥 사용하려는 것은 아니고, 당연히 SQLite보다 장점이 있기에 사용하게 되었다.
1. 간편한 사용 및 유지보수 - Room은 SQLite의 추상화 계층을 제공하여 좀 더 쉽게 데이터베이스를 다룰 수 있게 해 줍니다. SQL 질의문이나 테이블 생성과 같은 작업을 자동으로 처리하여 코드를 간소화하고 유지보수를 쉽게 만듭니다.
2. 컴파일 시간 검증 - Room은 컴파일 시간에 SQL 쿼리를 확인하므로 런타임에 발생할 수 있는 일부 오류를 컴파일 단계에서 미리 확인할 수 있습니다. 이는 오타나 잘못된 SQL 문장 등으로 인한 런타임 오류를 방지할 수 있도록 도와줍니다.
3. LiveData 및 RxJava 지원 - Room은 LiveData나 RxJava와 같은 안드로이드 아키텍처 컴포넌트와 통합이 쉽도록 설계되었습니다. 이를 통해 데이터베이스의 변경 사항을 관찰하고 이에 대한 적절한 대응을 할 수 있습니다.
4. 코드 생성을 통한 불필요한 코드 제거 - Room은 주요 데이터베이스 작업을 수행하기 위해 코드를 생성합니다. 이렇게 함으로써 반복적이고 오류가 발생하기 쉬운 작업을 간소화하고, 불필요한 코드를 최소화할 수 있습니다.
작업을 진행하면서 느꼈는데, SQLite 초기 구성보다 작업이 수월했다.
Room의 구성 요소는 다음과 같다.
데이터베이스 클래스: 데이터베이스를 보유하고 앱의 영구 데이터와의 기본 연결을 위한 기본 액세스 포인트 역할을 합니다.
데이터 항목: 앱 데이터베이스의 테이블을 나타냅니다.
데이터 액세스 객체(DAO): 앱이 데이터베이스의 데이터를 쿼리, 업데이트, 삽입, 삭제하는 데 사용할 수 있는 메서드를 제공합니다.

Room DB 기본 설정 - Gradle / Entity / DAO / DataBase
이제 Gradle 설정부터 먼저 확인해 보자
implementation "androidx.room:room-runtime:2.5.2"
kapt "androidx.room:room-compiler:2.5.2"
요즘은 KSP를 kapt 대신 사용한다고 하는데, 현재 프로젝트 환경 때문에 kapt로 진행했다. (2.5.2 버전도 프로젝트 환경에 적합하게 선택)
코드 분석이 kapt보다 최대 2배 빠르다고 하는데.. 추후에 적용해 보는 과정을 확인해 봐야겠다.
https://developer.android.com/studio/build/migrate-to-ksp?hl=ko
kapt에서 KSP로 이전 | Android 개발자 | Android Developers
주석 프로세서의 사용을 kapt에서 KSP로 이전합니다.
developer.android.com
다음으로 데이터 형태의 기본이 될 Entity를 생성해 준다.
@Entity(tableName = "timeData")
data class TimeData(
@PrimaryKey
val dateLabel: String,
val updatedTime: Long
)
tableName을 지정해 주고, @PrimaryKey를 선언해 준다. 필자의 경우, dateLabel이라는 String을 고유한 키 값으로 가질 예정이라 저렇게 구성했고, 키를 Long 타입 id로 지정하는 경우에는
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
이렇게 자동 생성을 활용하여 새로운 데이터가 추가될 때마다 id 값을 증가시킬 수 있다.
어떤 식으로 PrimaryKey를 지정할지는 중복 오류를 감안하여 자유롭게 구성 가능하다.
이번엔 쿼리문이 입력될 DAO를 구성해 보자
@Dao
interface TimeDataDao {
...
@Query("UPDATE timeData SET updatedTime = :currentTime WHERE dateLabel = :dateLabel")
fun update(dateLabel: String, currentTime: Long)
@Insert
fun insert(timeData: TimeData)
@Query("DELETE FROM timeData")
fun deleteAll()
}
인터페이스로 정의된 Dao 내부에 Insert 및 Query 어노테이션을 활용하여 목적에 맞는 쿼리문을 작성할 수 있다.
쿼리문 작성은 DB 공부 + GPT 질문을 통해 이해하고 작성해 나가자.
이제 Database를 정의한다.
@Database(entities = [TimeData::class], version = 1)
abstract class TimeDatabase : RoomDatabase() {
abstract fun timeDao(): timeDataDao
companion object {
private var INSTANCE: TimeDatabase? = null
fun getInstance(context: Context): TimeDatabase? {
if (INSTANCE == null) {
synchronized(TimeDatabase::class) {
INSTANCE = Room.databaseBuilder(
context.applicationContext,
TimeDatabase::class.java, "Timedata.db"
).build()
}
}
return INSTANCE
}
}
}
version은 Entity의 구조가 형태가 변경될 시, 업그레이드에 필요하다.
추가적으로 빌드 옵션에는 몇 가지 기능들이 포함되어 있는데,
.allowMainThreadQueries()
이와 같이 Main Thread에서 쿼리를 동작시키도록 빌드할 수도 있는데, 테스트나 초기 개발에서만 사용하고 배포 시에는 제거하는 것이 좋다. 배포 후에는 관리되는 데이터가 증가할 시, 쿼리 비용이 증가할 수 있고, 그렇다면 백그라운드 Thread에서 처리하는 것이 바람직하기 때문. 필자는 Coroutine을 활용할 예정이다.
.fallbackToDestructiveMigration()
마이그레이션 로직을 따로 추가하지 않고, Entity 변경 시, 기존 데이터를 모두 초기화시킨 다음에 새로 빌드하겠다는 함수이다.
이 또한 테스트나 초기 개발 단계에서만 사용하는 것이 좋다.
Coroutine + Repository
이제, 실제로 쿼리해온 데이터를 추출하고, 또 업데이트하는 중간 단계의 Repository를 구성해 본다.
이 부분은 DB를 어떻게 활용할지와 프로젝트 아키텍처에 따라 상이할 수 있겠다.
class TimeRepository(context: Context) {
private val timeDatabase = TimeDatabase.getInstance(context)
private val timeDataDao = timeDatabase?.timeDao()
.
.
.
private suspend fun updateTime(dateLabel: String, updatedTime: Long) {
withContext(Dispatchers.IO) { timeDataDao?.update(dateLabel, updatedTime) }
}
private suspend fun insertTime(dateLabel: String, updatedTime: Long) {
val timeData = TimeData(dateLabel, updatedTime)
withContext(Dispatchers.IO) { timeDataDao?.insert(timeData) }
}
}
context를 인자로 받아 Database 인스턴스와 Dao를 선언해 주었다.
각 DB 접근 함수에서는 Dispatchers.IO 기반으로 쿼리 동작이 처리되도록 Coroutine으로 감싸주었다.
이로서 기본적인 Room DB 처리 로직 구성은 마무리!
다음에는 Room DB에 대한 추가적인 기능들에 대해 알아보겠다.