문서 이력
  • 2022.03.03. 포스팅
  • 2022.03.05. UI Thread 관련 코드 추가


본 글은 안드로이드 개발자 사이트의 Codelab 페이지를 한글로 정리한 게시글 입니다.

Android Jetpack의 데이터베이스 라이브러리인 Room에 대해 설명한다. 기술적으로, Room 라이브러리는 SQLite DB의 최상위에 있는 추상적인 레이어이다. SQLite는 데이터베이스 동작을 위해 SQL 언어를 사용하는데, Room 라이브러리는 이를 단순화하여 단순한 함수 호출만으로 데이터베이스를 쉽게 이용할 수 있도록 해준다.

Entity와 Query

Entity

  • 데이터베이스에 저장할 속성을 가지고 있는 하나의 객체 혹은 개념
  • 데이터베이스의 테이블은 entity class로 정의되며, 각 행은 해당 class의 instance를 나타냄
  • Entity class는 Room 라이브러리에게 어떻게 정보를 표시하고 데이터베이스와 상호작용 할 것인지 나타낸다

Query

  • Query는 데이터베이스 하나의 테이블 혹은 여러 테이블의 조합의 데이터 혹은 정보에 대한 요청이거나, 데이터에 대한 작업 요청입니다.

Entity 구현

각 Entity는 annotated data class로 정의되어야 합니다.

@Entity(tableName = "daily_sleep_quality_table")
data class SleepNight(
    @PrimaryKey(autoGenerate = true)
    var nightId: Long = 0L,

    @ColumnInfo(name = "start_time_milli")
    val startTimeMilli: Long = System.currentTimeMillis(),

    @ColumnInfo(name = "end_time_milli")
    var endTimeMilli: Long = startTimeMilli,

    @ColumnInfo(name = "quality_rating")
    var sleepQuality: Int = -1
)

DAO 구현

Entity에 대한 상호작용은 annotated interface로 정의되어야 하는데, 이것은 data access object (DAO) 라고 부릅니다. 데이터베이스 접근을 위한 커스텀 인터페이스라고 생각할 수 있습니다.

안드로이드에서 DAO는 데이터베이스 삽입, 삭제, 수정에 대한 기본 함수를 제공합니다. 이외의 query는 @Query annotation을 사용하여 정의할 수 있습니다.

@Dao
interface SleepDatabaseDao {
    @Insert
    fun insert(night : SleepNight)

    @Update
    fun update(night : SleepNight)

    @Query("SELECT * from daily_sleep_quality_table WHERE nightId = :key")
    fun get(key : Long) : SleepNight?

    @Query("DELETE FROM daily_sleep_quality_table")
    fun clear()

    @Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC LIMIT 1")
    fun getTonight(): SleepNight?

    @Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC")
    fun getAllNights() : LiveData<List<SleepNight>>
}

Room 데이터베이스 구현

앞에서 생성한 Entity와 DAO 인터페이스를 이용하는 Room database를 생성합니다. 데이터베이스 클래스는 추상 클래스로서, @Database annotation과 함께 정의합니다.

@Database(entities = [SleepNight::class], version = 1, exportSchema = false) // Entity 정의, Schema 변경 시 version up
abstract class SleepDatabase : RoomDatabase() {
   abstract val sleepDatabaseDao: SleepDatabaseDao // DAO 선언. 여러 개의 DAO를 가질 수 있음.
}

추가적으로, 데이터베이스의 객체를 생성하여 반환하는 하나의 함수를 companion obect 내에 구현합니다. 이미 생성된 객체가 있다면 그것을 반환합니다. (Singleton)

companion object {
    @Volatile // Multi-Thread safe 하도록 Volatile 선언
    private var INSTANCE: SleepDatabase? = null

    fun getInstance(context: Context): SleepDatabase {
        synchronized(this) {
            var instance = INSTANCE

            if (instance == null) {
                instance = Room.databaseBuilder(
                    context.applicationContext,
                    SleepDatabase::class.java,
                    "sleep_history_database" // DB 파일 이름
                )
                .fallbackToDestructiveMigration()
                .build()
                INSTANCE = instance
            }
            return instance
        }
    }
}

UI Thread에서 데이터베이스 호출 및 사용

UI 쓰레드 (메인 쓰레드) 에서 데이터베이스 사용은 권장되지 않습니다. 그럼에도 메인 쓰레드에서 DB를 연결하고 사용하기 위해서는 allowMainThreadQueries() 설정이 필요합니다. 위에서 구현한 getInstance 함수 대신에 직접 Builder를 호출하여 데이터베이스 객체를 초기화하고 연결합니다.

val db = Room.databaseBuilder(context, SleepDatabase::class.java, "sleep_history_database")
    .allowMainThreadQueries()
    .build()
val dao = db.sleepDatabaseDao

DB를 초기화 하고나면 dao 객체를 통하여 DB를 이용할 수 있습니다.

val night = SleepNight()
sleepDao.insert(night)
val tonight = sleepDao.getTonight()
...

(추가) Coroutines 에서 Room 라이브러리 이용

  • Coroutine

DAO의 methods를 suspend로 변경하여 줍니다. getAllNights는 LiveData를 반환하므로, suspend 함수가 아니어도 됩니다.

@Dao
interface SleepDatabaseDao {
    @Insert
    suspend fun insert(night : SleepNight)

    @Update
    suspend fun update(night : SleepNight)

    @Query("SELECT * from daily_sleep_quality_table WHERE nightId = :key")
    suspend fun get(key : Long) : SleepNight?

    @Query("DELETE FROM daily_sleep_quality_table")
    suspend fun clear()

    @Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC LIMIT 1")
    suspend fun getTonight(): SleepNight?

    @Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC")
    fun getAllNights() : LiveData<List<SleepNight>>
}
fun onStopTracking() {
    val database = SleepDatabase.getInstance(application).sleepDatabaseDao
    viewModelScope.launch { // ViewModel에서 실행하므로 viewModelScope
        val newSleep = SleepNight()
        insert(newSleep)
    }
}

Tags:

Categories:

Updated:

Leave a comment