안드로이드

[Kotlin/Android] Room Library (1) Entitiy, DAO, Database

란서 2021. 12. 23. 14:15

Room 라이브러리 및 아키텍쳐 구성 개요

Room은 안드로이드 JetPack의 Database 라이브러리로써 SQLite 데이터베이스의 최상위 추상계층이다.

 

 SQL은 데이터베이스 운용을 수행하기 위해 특별한 언어를 사용한다. 따라서 Room은 SQL을 직접적으로 사용하기 보다는 라이브러리를 통해 SQL 언어를 재정립하여 사용한다. 이로써 우리의 APP은 함수 호출로도 Database를 사용할 수 있다. 또한 DAO를 이용하여 Query 구문을 사용할 수 있다.

 

 

기본 Room 아키텍쳐 구조 -> 오늘 배워 볼 구조

 

 왼쪽 그림은 기본 Room의 아키텍쳐 구조이다. 오른쪽 그림은 왼쪽 그림에서 Repository와 Network과정은 생략하고 Room Database만 집중적으로 배워보기 위한 아키텍쳐 구조이다.

 

App OverView

 SleepTrackerApp은 이용자의 수면 시간 및 수면 퀄리티를 체크하는 앱이다.

SleepTrackerApp

  • Start버튼을 누르면 이용자의 수면 시작 시간을 기록한다.
  • Stop버튼을 누르면 이용자의 수면 종료 시간을 기록한다.

    이 후 시작 시간, 종료 시간, 퀄리티, 수면 시간을 기록한 String을 표시한다.
  • Clear버튼을 누르면 모든 기록을 초기화 한다.

 

-> 해당 앱에서 Sleep 기록을 저장하는 객체 SleepNight를 Room Database에 저장하는 과정을 학습.

Room Database를 사용하면 사용자가 오프라인 동안 데이터를 생성해서 해당 데이터를 로컬 저장소에 저장한다. 또한 이 후 외부 서버와 연결될 경우, 로컬 저장소에 저장되있던 데이터가 서버와 연동되면서 업데이트가 가능하다.

 

Room Database 

 

안드로이드에서 Data는 곧 Data Class를 의미한다. 이러한 데이터(객체) 들은 함수 호출을 통해 접근 및 수정될 수 있다. 

 

하지만 데이터베이스 세계에서 Data는 Entity를 의미하고 Query를 통해 접근 및 수정될 수 있다.

 

 

  • Entities (Entity) : 데이터베이스 내에 저장되는 특성, 개념, 속성을 포함한 데이터 객체로서 "테이블"을 정의하는 Entity class와 해당 테이블의 속성을 가진 인스턴스 =(실제 데이터)를 의미한다.

    Room이 데이터베이스 내 저장된 정보를 표현하고 이와 상호작용할 수 있게 Mapping을 가지며 Entity는 Mapping을 통해 데이터를 Hold한다.


  • Query : 쿼리는 Entity에 담긴 데이터(인스턴스)를 호출(접근)하고 수정할 수 있는 요청 및 명령문이다.

    가장 흔한 쿼리는 데이터의 생성(=삽입), 데이터 업데이트, 데이터 삭제 등이 있다.

  • Gradle Version
   // Room and Lifecycle dependencies
    implementation "androidx.room:room-runtime:$room_version"
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    kapt "androidx.room:room-compiler:$room_version"
    implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"

 

우리가 안드로이드에서 SQLite 사용하려면 Room을 이용해야하고, 그러려면 Entity와 Quert를 정의해야 한다. 

엔티티 Entity 

  • Entity는 Data Class를 이용해서 정의할 수 있다. 해당 data class는 어노테이션 @Entity 가 붙어야만 된다.

엔티티를 정의하기 위해선 data class 를 만들어야 하고, 해당 data class에 annotation @Entity를 붙여줘야 한다.

그리고 데이터베이스 엔티티에 고유key를 넣어주어 엔티티끼리 구별이 가능할 수 있도록 만든다.

 

@Entity(tableName = "daily_sleep_quality_table")
data class SleepNight(
    @PrimaryKey(autoGenerate = true) var nightId:Long = 0L,
    @ColumnInfo(name="start_time_milli")var startTime: Long = System.currentTimeMillis(),
    @ColumnInfo(name="end_time_milli")var endTime: Long = startTime,
    @ColumnInfo(name="quality_rationg")var sleepQuality :Int= -1)

Entity의 인자 중 tableName은 table의 이름을 해당 string 으로 바꿀 수 있다. - 하면 좋다. 권장

@PrimaryKey annotation은 해당 변수를 주요키로 만든다. (autoGenerate 설정 시, 키 자동생성) - Unique보장

@ColumnInfo는 해당 컬럼의 이름을 인자로 넘겨주는 string으로 만든다. - 하면 좋다 권장.

DAO (Data Access Object)

  • Query는 인터페이스를 통해 정의할 수 있다. 해당 인터페이스는 어노테이션 @DAO 가 붙어야 한다

즉 DAO는 안드로이드에서 SQL 쿼리를 함수 호출로 사용할 수 있도록 만드는 어노테이션. 

suspend는 나중에 설명.

 

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

    @Update
	fun update(sleepNight: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 라이브러리는 LiveData 타입도 Keep할 수 있다.  따라서 해당 데이터를 LiveData 객체로 반환이 가능하다.

 

Room Database

  • 마지막으로 Room을 이용하려면 entity와 DAO를 사용하는 추상 Database holder class를 만들어야 한다. 어노테이션 @Database를 사용한다.

 해당 클래스는 오직 하나의 method만 가지는데 그 역할은 데이터베이스가 존재하면 해당 데이터베이스에 대한 참조를 리턴, 그렇지 않으면 데이터베이스의 새로운 객체를 생성해서 리턴한다.

 

 데이터 베이스를 만드는 과정이 꽤 복잡하기 때문에 과정을 상세하게 적어보겠다. 그리고 데이터베이스의 구조는 어디서나 같기 때문에 해당 코드를 재활용하면 된다.

 

  1. abstract class RoomDatabase 생성 : RoomDatabase를 상속한다.
  2. Annotation @Database 를 달아준다. 인자는 

    @Database(entities = [], version = 1, exportSchema) 등이 있는데,

    entities에는 들어갈 entity를,
    version은 현재 엔티티(스키마)가 수정될 때 마다 버전 업을 해주면 되는데, 처음이니 1 로 설정
    exportSchema는 schema 버전을 백업하는 것인데, 아직은 몰라도 돼

  3. class내에 Dao타입의 추상 프로퍼티를 정의한다. - database를 사용하는 외부 클래스에서 dao를 사용하여 데이터에 접근 및 수정할 수 있도록 해야함.

  4. companion object = 추상 메서드 또는 프로퍼티를 정의. 메서드에서는 데이터베이스 클래스를 반환.

     하나의 app은 오로지 하나의 database만을 가지기 때문에 싱글톤 패턴을 사용한다.
    이용자로 하여금 클래스를 객체화 시키지 않고도 데이터베이스를 얻고 생성할 수 있게 만든다.

     prviate nullable 변수 INSTANCE를 정의한다.  -> 데이터베이스에 대한 참조를 유지하는 변수로 이용자가 계속해서 데이터베이스에 연결하려는 불필요한 상황을 없앤다.

    INSTANCE가 이미 만들어진 데이터베이스를 참조한다면, 그대로 리턴. 그렇지 않다면 데이터베이스를 만들어서 리턴.

     해당 변수는 @Volitle 어노테이션이 지정되는데, @Volitle 어노테이션이 지정되는 경우 해당 변수의 데이터는 캐싱없이 메인 메모리에서 직접 읽고 쓰기가 가능해지기 때문에 어느 스레드에서 변수를 읽든 항상 최신의 상태를 유지하게 된다. https://nesoy.github.io/articles/2018-06/Java-volatile

  5. fun getInstance()  : SleepDatabase 메소드를 통해 데이터베이스를 참조하는 INSTANCE 반환하기.

    해당 함수는 synchronized() {} 내에 구현되어서 반드시 여러 쓰레드가 동시에 접근하는 것을 막은 채로 진행된다. 
  6. Room 데이터베이스를 인스턴스로 반환하기 위해서 Room.databaseBuilder를 사용한다.

    Room.databaseBuilder(context.applicationContext : 어플리케이션컨텍스트, 데이터베이스 클래스 타입, 데이터베이스 이름)

    .fallbackToDestructiveMigration() : 스키마가 변경될 때 "이주 정보를 지닌 이주 객체"를 제공하여 이전 스키마가 지닌 모든 테이블 정보를 수거하고, 새로운 스키마의 테이블에 인스턴스를 편입시켜야 하는데 해당 함수는 그런 스키마 이주정보를 제공해줄 수 없고 스키마가 변경될 때 이전 정보를 파기한다는 내용.

    .build() = 빌드


    @Database(entities = [SleepNight::class], version = 1, exportSchema = false)
    abstract class SleepDatabase : RoomDatabase() {
        abstract val sleepDatabaseDao :SleepDatabaseDao
    
        companion object {
            @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")
                            .fallbackToDestructiveMigration()
                            .build()
                        INSTANCE = instance
                    }
                    return instance
                }
            }
        }
    
    }​

 

 

 

 

Java volatile이란?

 

nesoy.github.io