Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

yourginieus

Create a RoomDB 본문

Android/Android Kotlin 기초 실습 정리

Create a RoomDB

EOJIN 2022. 10. 27. 17:38
  • Room 은 Android Jetpack의 일부인 database library 임
  • Room library는 SQLite database의 상단에 있는 추상화 레이어임
  • SQLite는 SQL을 사용하여 데이터베이스 작업을 수행함
  • SQLite를 직접 사용하는 대신. Room은 데이터베이스 설정, 구성 및 상호작용 작업을 단순화함
  • 또한 일반 function call을 사용하여 앱이 데이터베이스와 상호작용하도록 함
  • Room은 data를 검색할 수 있는 query syntax도 가지고 있음

Room DB가 이 코스에서 권장하는 전체적인 구조에 어떻게 일치하는지 보여줌

  • 우리가 실습할 앱은 다음과 같은 구조를 가짐


  • Android에서 data는 data class로 표시됨
  • 이 data는 function call을 사용하여 access하고 수정할 수 있음
  • 하지만 database 환경에서는, data에 access하고 수정하기 위해서는 entity와 query가 필요함
    • entity
      • 데이터베이스에 저장할 객체 또는 개념을 속성과 함께 나타냄
      • 테이블을 정의하는 엔티티 클래스가 필요하며, 그 테이블의 행을 나타내는, 그 클래스의 인스턴스가 필요함
      • 엔티티 클래스에는 Room에게 데이터베이스의 정보를 표시하고 상호작용하는 방법을 알려주는 mapping이 있음
      • 실습 앱에서 엔티티는 하룻밤의 수면 정보를 보유함
    • query
      • 데이터베이스 테이블 또는 테이블의 조합에 대한 데이터 또는 정보를 요청하거나, 데이터에 대한 작업 수행을 요청함
      • 일반적인 쿼리는 엔티티의 생성, 읽기, 업데이트 및 삭제를 위함
      • 예를 들어, 기록되어 있는 모든 수면 시간을 읽는 쿼리를 실행하여 시작시간 순으로 정렬할 수 있음
  • 일부 데이터를 로컬에 보관하면, 오프라인 상태에서도 사용자가 앱을 즐길 수 있음
    • 앱이 서버를 사용할 경우, 오프라인일 때에도 콘텐츠를 수정하여 앱이 인터넷에 연결되면 캐싱된 변경사항을 백그라운드에서 서버와 동기화할 수 있음
  • Room은 코틀린 데이터클래스->SQLite 테이블에 저장되는 엔티티 와 함수 선언->SQL 쿼리 에 이르기까지의 모든 작업을 수행함
  • You must
    • 각 엔티티를 annotated data class로 정의하고
    • annotated interface인 DAO(data access object)로 해당 엔티티와의 상호작용을 정의해야 함
    • Room은 이러한 annotated class들을 database 내에 테이블을 생성하는 데 사용하며, database에 대해 동작하는 쿼리를 만드는 데 사용함

1. Sleep Night 엔티티 만들기

  • 여기서는 데이터베이스 엔티티를 나타내는 annotated data class로 하룻밤의 수면을 정의할 것
  • 하룻밤의 수면을 위해 시작시간, 끝 시간, 만족도를 저장해야 함
  • 그 하룻밤을 식별하기 위해 unique한 ID가 필요함

  • database 패키지에서 SleepNight.kt 파일 열기
  • SleepNight 데이터클래스 만들기
    • 파라미터로 ID, start time(in millis), end time(in millis), numerical sleep-quality rating
    • sleepQuality를 -1로 초기화 : 아직 만족도가 조사되지 않았음을 나타냄
    • start time을 알려진 유효 시간으로 초기화 : 현재 밀리초로 초기화하면 좋을 듯
    • end time도 초기화 : 종료시간이 아직 기록되지 않았음을 나타내기 위해 시작시간으로 초기화
data class SleepNight(
       var nightId: Long = 0L,
       val startTimeMilli: Long = System.currentTimeMillis(),
       var endTimeMilli: Long = startTimeMilli,
       var sleepQuality: Int = -1
)
  • 클래스 선언 전에, @Entity 주석 추가하기
    • 이 주석은 몇 가지 arguments가 가능함
      • default : no arguments to @Entity - 테이블 이름이 클래스와 같음
      • tableName 인수 : 테이블 이름을 설정할 수 있음 - 옵션이지만 사용하면 좋음!
@Entity(tableName = "daily_sleep_quality_table")
data class SleepNight(...)
  • nightId를 primary key로 식별하기 위해, nightId에 @PrimaryKey 주석 추가하기
    • 파라미터를 autoGenerate=true로 설정
    • Room이 각 엔티티를 위한 ID를 생성해 줌
    • 따라서 각 밤의 ID가 고유해짐
@PrimaryKey(autoGenerate = true)
var nightId: Long = 0L,...
  • 나머지 속성에 @ColumnInfo 주석 추가하기
    • 아래와 같이 파라미터를 사용하여 속성명 커스터마이즈하기
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey

@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
)

2. DAO 만들기

  • Data Access Object(DAO)
  • Android에서 DAO는 데이터베이스를 삽입, 삭제 및 업데이트하는 편리한 메소드를 제공함
  • RoomDB를 사용하는 경우, 코드에서 코틀린 함수를 정의하고 호출함으로써 데이터베이스를 조회할 수 있음
  • 이러한 코틀린 함수는 SQL 쿼리에 매핑됨
  • 주석을 사용하여 이러한 매핑을 정의하고, Room은 필요한 코드를 만듦
  • DAO는 데이터베이스에 액세스하기 위한 커스텀 인터페이스를 정의하는 것으로 간주하기!

  • 일반적인 데이터베이스 조작의 경우, Room library는 @Insert, @Delete, @Update 같은 편리한 메소드를 제공함
  • 다른 모든 것에 대해서는 @Query 주석을 사용함
  • SQLite에서 지원하는 모든 쿼리를 작성할 수 있음
  • Android Studio에서 쿼리를 만들면, 컴파일러는 문법 에러를 확인하기 위해 SQL 쿼리를 확인함

  • sleep nights의 sleep-tracker database를 위해서는 다음 작업이 필요함
    • Insert new nights
    • Update an existing night to update an end time and a quality rating
    • Get a specific night based on its key
    • Get all nights, so you can display them
    • Get the most recent night
    • Delete all entries in the database

STEP 1 : Create the SleepDatabase DAO

  • database 패키지에서 SleepDatabaseDao.kt 열기
  • interface SleepDatabaseDao에 이미 @Dao 주석이 붙어있음!
    • 모든 DAO는 @Dao 주석을 붙여야 함
@Dao
interface SleepDatabaseDao {}
  • 인터페이스 바디 내부에, @Insert 주석 추가하기
  • @Insert 아래에 insert() 함수 추가해서 Entity 클래스의 SleepNight을 argument로 사용함
@Insert
fun insert(night: SleepNight)
  • 하나의 SleepNight을 위한 @Updeate 주석과 update() 함수 추가하기
    • 업데이트되는 엔티티는 전달된 엔티티와 같은 key를 가진 엔티티임!
    • 엔티티의 다른 속성 중 일부 또는 모두를 업데이트할 수 있음
@Update
fun update(night: SleepNight)
  • 다른 기능에 대한 기존의 주석은 없으므로 이제는 @Query 주석 사용하고 SQLite 쿼리 사용하기
    • @Query 주석 추가하고 Long key를 argument로 받고 nullable SleepNight를 반환하는 get() 추가하기
    • 파라미터 없다고 에러 뜰 것!
@Query
fun get(key: Long): SleepNight?
  • 쿼리는 @Query 주석에 string parameter로 제공됨
  • @Query에 String 파라미터를 추가하기 : 특정 SleepNight 엔트리에서 모든 열을 검색하기 위한 SQLite 쿼리
    • daily_sleep_quality_table에서 모든 열 선택하기
    • WHERE = nightId가 key argument 와 일치하는 곳에!
    • :key 주의! 쿼리에서는 콜론 표기법을 사용하여 함수 안에서 argument를 참조하기!
("SELECT * from daily_sleep_quality_table WHERE nightId = :key")
  • 또 다른 @Query를 clear() 와 함께 추가 : SQLite 쿼리로는 DELETE everything from the daily_sleep_quality_table
  • 이 쿼리는 테이블 자체를 삭제하는 것이 아님!
  • @Delete 주석을 사용하여 하나의 아이템을 삭제할 수 있음
  • 또한 여러 아이템 목록을 제공하여 걔네를 지울 수 있음
  • 단점은, 테이블에 무엇이 있는지 가져오거나 알아야 한다는 것
  • @Delete 주석은 특정 엔트리를 삭제하는 데 유용하지만, 테이블에서 모든 엔트리를 지우는 데는 효율적이지 않음
@Query("DELETE FROM daily_sleep_quality_table")
fun clear()
  • @Query를 getTonight()과 함께 추가 : getTonight()이 nullable SleepNight를 반환하도록 하기
    • 테이블이 비어있는 경우 이를 처리할 수 있음
    • 테이블은 처음과 데이터가 클리어된 후에 비어 있음
  • 데이터베이스에서 "tonight"을 가져오려면, nightId를 내림차순으로 정렬하여 반환한 list 중 첫 번째 요소를 반환하는 SQLite 쿼리를 작성해야 함
    • LIMIT 1 을 사용하여 하나의 요소만 반환하기!
@Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC LIMIT 1")
fun getTonight(): SleepNight?
  • @Query를 getAllNights()와 함께 추가
    • SQLite 쿼리가 daily_sleep_quality_table의 모든 열을 내림차순으로 정렬하여 반환하도록하기
    • getAllNights()는 LiveData로서의 SleepNight 엔티티들의 리스트를 반환함
    • Room은 이 LiveData를 가지고 있음 : 데이터를 명시적으로 한 번만 가져오면 된다는 뜻
@Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC")
fun getAllNights(): LiveData<List<SleepNight>>

3. Create and test a Room database

  • 이전에 만든 Entity와 DAO를 사용하는 Database를 만들거임!
  • @Database 주석을 사용해서 abstract database holder class를 생성해야 함
  • 이 클래스에는 데이터베이스가 존재하지 않는 경우 데이터베이스의 인스턴스를 만들거나, 기존 데이터베이스의 레퍼런스를 반환하는 메소드가 있음
  • 시작하기 전에 일반적으로 진행해야 하는 프로세스
    • RoomDatabase를 상속받는 public abstract 클래스 만들기 : database holder 역할
      • Room이 알아서 작성하기 때문에 클래스는 abstract
    • 클래스에 @Database 주석 달기 : 인수로는 database를 위한 엔티티 선언하고 version number 설정하기
    • companion object 안에서, SleepDatabaseDao를 반환하는 abstract method or property 정의하기
      • Room이 body를 알아서 만들어 줄 거임!
    • 전체 앱애서 Room database 인스턴스는 한 개만 있으면 됨 - RoomDatabase가 singleton!
    • database가 존재하지 않는 경우에만 Room's database builder 사용해서 데이터베이스 생성하기
    • 그렇지 않다면 기존 데이터베이스 반환하기

STEP 1 : Create the database

  • database 패키지에서 SleepDatabase.kt 열기
  • SleepDatabase라는 abstract class 만들고 RoomDatabase 상속받기
  • @Database 주석 달기
@Database()
abstract class SleepDatabase : RoomDatabase() {}
  • missing entities와 version parameters에 대한 에러 나옴! @Database 주석은 Room이 database를 구축하기 위한 몇 가지 인수를 필요로 함
    • entities 리스트의 유일한 항목으로 SleepNight 제공
    • version을 1로 설정 - 스키마를 변경할 때마다 버전 번호를 높여야 함
    • exportSchema를 false로 설정 - 스키마 버전 이력 백업을 보관하지 않도록 하기 위함
@Database(entities = [SleepNight::class], version = 1, exportSchema = false)
  • Database는 DAO에 대해 알아야 함
    • 클래스의 body 안에서, SleepDatabaseDao를 반환하는 abstract value 선언하기
    • 여러 개의 DAO 를 가질 수 있음
abstract val sleepDatabaseDao: SleepDatabaseDao
  • 이 abstract value 아래에, companion object 정의하기 : companion object는 클라이언트가 클래스를 인스턴스화하지 않고 database를 만들거나 가져오는 메소드에 액세스할 수 있도록 해 줌
  • 이 클래스의 목적은 데이터베이스를 제공하는 것 뿐이므로, 데이터베이스를 인스턴스화할 필요가 없음
 companion object {}
  • companion object 안에, private nullable variable인 INSTANCE 선언 : database와 그걸 null로 초기화하기 위함
  • 그 INSTANCE 변수는 만들어진 이후로 데이터베이스에 대한 참조를 가질 것
  • 이렇게 하면 계산 비용이 많이 드는 데이터베이스에 대한 연결을 반복적으로 여는 것을 방지할 수 있음
    • INSTANCE에 @Volatile 주석 달기
    • volatile 변수의 값은 캐싱되지 않으며, 메인 메모리에서 모든 쓰기 및 읽기가 수행됨
    • 이는 INSTANCE의 값이 항상 최신이며 모든 실행 스레드에서 동일하게 적용되는 데 도움을 줌
    • 즉 하나의 스레드에서 변경이 일어나도 다른 모든 스레드에 즉시 표시되며, 두 개의 스레드가 각각 캐시 내의 동일한 엔티티를 업데이트하여 문제가 발생하는 상황이 발생하지 않음
@Volatile
private var INSTANCE: SleepDatabase? = null
  • INSTANCE 아래에, companion object 안에서, getInstance() 메소드 정의하기 : database builder가 필요로 할 Context 파라미터 전달
    • SleepDatabase 타입 반환하기
  • getInstance()가 아직 아무것도 return하지 않아서 에러 발생할 것!
fun getInstance(context: Context): SleepDatabase {}
  • getInstance() 안에, synchronized{} 블록 추가하기 : this를 파라미터로 전달하여 context에 액세스할 수 있도록 하기
    • 여러 스레드가 동시에 데이터베이스 인스턴스를 요청하여 여러 개의 데이터베이스가 반환되는 문제를 해결하기 위해 감싸는 것 - synchronized 는 이 코드 블록에 접근할 수 있는 실행 스레드는 한 번에 한 개 뿐이므로 데이터베이스가 오직 한 번만 초기화되게 해 줌
synchronized(this) {}
  • synchronized block 안에서, INSTANCE의 현재 값을 local variable인 instance에 복사하기
  • 코틀린의 smart cast 기능을 활용하기 위함! - local variable에서만 사용할 수 있음
var instance = INSTANCE
  • synchronized 블록 안에서, 마지막에 return instance 하기
  • return type missmatch 에러는 무시하기 - 한 번 완료되면 null을 반환하지 않음
return instance
  • return 문 위에, if문을 추가하여 instance가 null인지 확인하기 - null이라는 뜻은 아직 database가 없다는 뜻!
if (instance == null) {}
  • instance가 null이라면, database builder를 이용하여 database 가져오기
    • if문의 body에서 Room.databaseBuilder를 호출하고 전달받은 context와 database class, name for the database 전달하기
instance = Room.databaseBuilder(
                           context.applicationContext,
                           SleepDatabase::class.java,
                           "sleep_history_database")
  • Android Studio가 Type Missmatch 에러를 발생시키므로, 이 오류를 제거하려면 migration strategy를 추가하고 다음 순서에 의해 build()해야 함
    • 필요한 migration strategy를 builder에 추가하기 - .fallbackToDestructiveMigration() 사용
  • 보통 스키마가 변경될 때 마이그레이션 전략을 마이그레이션 개체에 제공해야 함
  • 마이그레이션 개체는 - 데이터가 손실되지 않도록 이전 스키마를 사용하여 모든 행을 새 스키마 행으로 변환하는 방법을 정의하는 개체
  • Migragion은 이 코드랩의 범위를 벗어나므로, 간단한 해결책은 데이터베이스를 파괴하고 재구축하는 것 -> 데이터 손실됨
.fallbackToDestructiveMigration()
  • 마지막으로 .build()를 콜하면 오류가 제거됨
.build()
  • if 문의 마지막 단계로 INSTANCE = instance 할당하기
INSTANCE = instance
  • 최종 코드
@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
           }
       }
   }
}

 

https://developer.android.com/codelabs/kotlin-android-training-room-database?index=..%2F..android-kotlin-fundamentals&hl=ko#0 

 

'Android > Android Kotlin 기초 실습 정리' 카테고리의 다른 글

RecyclerView : GridLayout  (0) 2022.12.14
Recyclerview 기초  (0) 2022.12.13
LiveData transformations  (0) 2022.10.27
ViewModel 및 LiveData를 통한 데이터 바인딩  (0) 2022.10.27
LiveData 및 LiveData Observer  (0) 2022.10.27
Comments