Android App Track⛺-038

MunkiJeon·2024년 7월 31일

TIL

목록 보기
55/59
post-thumbnail

TIL (Today I Learned)

오늘 공부한거!

Room

- SQLite를 쉽게 사용할 수 있는 데이터베이스 객체 매핑 라이브러리
- 쉽게 Query를 사용할 수 있는 API를 제공
- Query를 컴파일 시간에 검증함
- Query결과를 LiveData로하여 데이터베이스가 변경될 때 마다 쉽게 UI를 변경할 수 있음

SQLite 보다 Room을 사용할 것을 권장함

  • Room의 주요 3요소

    • @Database: 클래스를 데이터베이스로 지정하는 annotation, RoomDatabase를 상속 받은 클래스여야 함
      • Room.databaseBuilder를 이용하여 인스턴스를 생성함
    • @Entity: 클래스를 테이블 스키마로 지정하는 annotation
    • @Dao: 클래스를 DAO(Data Access Object)로 지정하는 annotation
      • 기본적인 insert, delete, update SQL은 자동으로 만들어주며, 복잡한 SQL은 직접 만들 수 있음
  • gradle 파일 설정

    • Room은 안드로이드 아키텍처에 포함되어 있음

    • 사용하기위해 build.gradle 파일의 dependencies에 아래 내용을 추가해야 함
      - Androidx 사용하는 경우를 가정함, Android Studio와 SDK는 최신 버전으로 사용
      - 'kotlin-kapt' 플러그인이 추가
      - dependencies 추가

      plugins {
      		....
        id 'kotlin-kapt'
      }
      .....
      dependencies {
      
        ......
      
        def room_version = "2.5.1"
        implementation "androidx.room:room-runtime:$room_version"
        annotationProcessor "androidx.room:room-compiler:$room_version"
        kapt "androidx.room:room-compiler:$room_version"
        // optional - Kotlin Extensions and Coroutines support for Room
        implementation "androidx.room:room-ktx:$room_version"
        // optional - Test helpers
        testImplementation "androidx.room:room-testing:$room_version"
      }
  • Entity 생성

    • Entity는 테이블 스키마 정의
    • CREATE TABLE student_table (student_id INTEGER PRIMARY KEY, name TEXT NOT NULL);
    • @Entity data class Student
    @Entity(tableName = "student_table")    // 테이블 이름을 student_table로 지정함
    data class Student (
        @PrimaryKey 
            @ColumnInfo(name = "student_id") 
        val id: Int,
        val name: String
    )
  • DAO 생성

    • DAO는 interface나 abstract class로 정의되어야 함
    • Annotation에 SQL 쿼리를 정의하고 그 쿼리를 위한 메소드를 선언
    • 가능한 annotation으로 @Insert, @Update, @Delete, @Query가 있음
      @Query("SELECT * from table") fun getAllData() : List<Data>
    • @Insert, @Update, @Delete는 SQL 쿼리를 작성하지 않아도 컴파일러가 자동으로 생성함
      • @Insert나 @Update는 key가 중복되는 경우 처리를 위해 onConflict를 지정할 수 있음
      • OnConflictStrategy.ABORT: key 충돌시 종료
      • OnConflictStrategy.IGNORE: key 충돌 무시
      • OnConflictStrategy.REPLACE: key 충돌시 새로운 데이터로 변경
  • @Update@Deleteprimary key에 해당되는 튜플을 찾아서 변경/삭제

  • @Query로 리턴되는 데이터의 타입을 LiveData<>로 하면,
    나중에 이 데이터가 업데이트될 때 Observer를 통해 업데이트 할 수 있음

  • @Query에 SQL을 정의할 때 메소드의 인자를 사용할 수 있음

    @Query("SELECT * FROM student_table WHERE name = :sname") 
    //인자 sname을 SQL에서 :sname으로 사용
    suspend fun getStudentByName(sname: String): List<Student>
  • fun 앞에 suspend는 Kotlin coroutine(객체)을 사용하는 것임,
    나중에 이 메소드를 부를 때는 runBlocking {} 내에서 호출해야 함

  • LiveData는 비동기적으로 동작하기 때문에 coroutine으로 할 필요가 없음

@Dao
interface MyDAO {
    @Insert(onConflict = OnConflictStrategy.REPLACE)  // INSERT, key 충돌이 나면 새 데이터로 교체
    suspend fun insertStudent(student: Student)

    @Query("SELECT * FROM student_table")
    fun getAllStudents(): LiveData<List<Student>>        // LiveData<> 사용

    @Query("SELECT * FROM student_table WHERE name = :sname")   
    suspend fun getStudentByName(sname: String): List<Student>

    @Delete
    suspend fun deleteStudent(student: Student); // primary key is used to find the student

}
  • Database 생성

    • RoomDatabase를 상속하여 자신의 Room 클래스를 만들어야 함
    • 포함되는 Entity들과 데이터베이스 버전(version)을 @Database annotation에 지정함
      • version이 기존에 저장되어 있는 데이터베이스보다 높으면, 데이터베이스를 open할 때 migration을 수행하게 됨
      • Migration 수행 방법은 RoomDatabase 객체의 addMigration() 메소드를 통해 알려줌
    • DAO를 가져올 수 있는 getter 메소드를 만듬
      • 실제 메소드 정의는 자동으로 생성됨
    • Room 클래스의 인스턴스는 하나만 있으면 되므로 Singleton 패턴을 사용
    • Room 클래스의 객체 생성은 Room.databaseBuilder()를 이용함
     @Database(entities = [Student::class, ClassInfo::class, Enrollment::class, Teacher::class], version = 1)
     abstract class MyDatabase : RoomDatabase() {
         abstract fun getMyDao() : MyDAO
    
         companion object {
             private var INSTANCE: MyDatabase? = null
             private val MIGRATION_1_2 = object : Migration(1, 2) {
                 override fun migrate(database: SupportSQLiteDatabase) { 생략 }
             }
    
             private val MIGRATION_2_3 = object : Migration(2, 3) {
                 override fun migrate(database: SupportSQLiteDatabase) { 생략 }
             }
             fun getDatabase(context: Context) : MyDatabase {
                 if (INSTANCE == null) {
                     INSTANCE = Room.databaseBuilder(
                         context, MyDatabase::class.java, "school_database")
                         .addMigrations(MIGRATION_1_2, MIGRATION_2_3)
                         .build()
                 }
                 return INSTANCE as MyDatabase
             }
         }
     }
  • Migration

    • 앞에서 MyRoomDatabase객체 생성 후 addMigrations()
      메소드를 호출하여 Migration 방법을 지정했음
      - 여러개의 Migration 지정 가능

      Room.databaseBuilder(...).addMigrations(MIGRATION_1_2, MIGRATION_2_3)
      
      private val MIGRATION_1_2 = object : Migration(1, 2) {   // version 1 -> 2
          override fun migrate(database: SupportSQLiteDatabase) {
              database.execSQL("ALTER TABLE student_table ADD COLUMN last_update INTEGER")
          }
      }
      
      private val MIGRATION_2_3 = object : Migration(2, 3) {   // version 2 -> 3
          override fun migrate(database: SupportSQLiteDatabase) {
              database.execSQL("ALTER TABLE class_table ADD COLUMN last_update INTEGER")
          }
      }
  • UI와 연결

    • 안드로이드 아키텍처에 따라 Repository와 ViewModel을 사용하길 권장하지만
    • RoomDatabase객체에서 DAO 객체를 받아오고, 이 DAO객체의 메소드를 호출하여 데이터베이스를 접근함
      myDao = MyDatabase.getDatabase(this).getMyDao()
      runBlocking { // (주의) UI를 블록할 수 있는 DAO 메소드를 UI 스레드에서 바로 호출하면 안됨
       myDao.insertStudent(Student(1, "james"))  // suspend 지정되어 있음
      }
      val allStudents = myDao.getAllStudents() // LiveData는 Observer를 통해 비동기적으로 데이터를 가져옴
UI와 연결 - LiveData

LiveData?

LiveData는 안드로이드 아키텍처 컴포넌트의 일부로, 관찰 가능한 데이터 홀더 클래스입니다. 이를 통해 UI 컴포넌트(예: 액티비티, 프래그먼트)는 데이터의 변경 사항을 관찰하고 이에 반응할 수 있습니다. 데이터가 변경될 때마다 LiveData는 관찰자에게 알림을 보냅니다.

LiveData의 핵심 특징

  1. 수명주기 인식:
    • LiveData는 안드로이드의 수명주기를 인식합니다. 즉, 액티비티나 프래그먼트의 수명주기 상태에 따라 알림을 자동으로 관리합니다. 이로 인해 메모리 누수 및 액티비티가 종료된 상태에서 발생할 수 있는 크래시를 방지할 수 있습니다.
  2. UI와 데이터 상태의 일관성 유지:
    • LiveData를 사용하면 UI가 데이터와 일관되게 유지됩니다. 데이터가 변경될 때 UI가 자동으로 갱신되기 때문에, 사용자에게 최신의 정보를 제공할 수 있습니다.
  3. 중앙 집중적인 데이터 관리:
    • LiveData는 뷰모델(ViewModel)과 함께 사용되어 앱의 데이터를 중앙에서 관리할 수 있게 합니다. 이는 데이터 관리를 더욱 효율적으로 만들어 줍니다.
  4. 데이터 변경에 따른 자동 업데이트:
    • LiveData의 관찰자는 오직 활성 수명주기 상태(active lifecycle state)의 컴포넌트에만 알림을 보냅니다. 이는 데이터가 변경될 때 활성 상태의 UI만 업데이트되어, 불필요한 리소스 사용을 줄여줍니다.
  • LiveData<> 타입으로 리턴되는 DAO 메소드 경우
    • observe() 메소드를 이용하여 Observer를 지정
    • 데이터가 변경될 때마다 자동으로 Observer의 onChanged()가 호출됨
  • LiveData<>를 리턴하는 DAO 메소드는 Observer를 통해 비동기적으로 데이터를 받기 때문에, UI 스레드에서 직접 호출해도 문제 없음
val allStudents = myDao.getAllStudents()
allStudents.observe(this) {   // Observer::onChanged() 는 SAM 이기 때문에 lambda로 대체
    val str = StringBuilder().apply {
            for ((id, name) in it) {
                append(id)
                append("-")
                append(name)
                append("\n")
            }
        }.toString()
    binding.textStudentList.text = str
}
  • Room Database의 주요 어노테이션(Annotations)

    • Room은 안드로이드의 오픈소스 데이터베이스 라이브러리로, SQLite 데이터베이스의 추상 레이어를 제공하여 편리한 데이터베이스 접근과 데이터 관리를 가능하게 해줍니다. Room 데이터베이스를 사용할 때 중요한 역할을 하는 어노테이션들에 대해 알아보겠습니다.
주요 어노테이션 종류
  1. @Database
  • 데이터베이스 클래스를 정의할 때 사용합니다.
  • 데이터베이스에 포함될 엔티티와 버전을 명시합니다.
  1. @Entity
  • 데이터베이스 내의 테이블을 정의할 때 사용합니다.
  • 클래스 이름이 테이블 이름으로 사용되며, 필드는 컬럼으로 매핑됩니다.
  1. @PrimaryKey
  • 엔티티의 기본 키(primary key)를 정의할 때 사용합니다.
  • 유니크한 값이어야 하며, 데이터베이스 내에서 각 엔티티를 구분하는 데 사용됩니다.
  1. @ColumnInfo
  • 테이블의 컬럼 정보를 세부적으로 정의할 때 사용합니다.
  • 컬럼의 이름, 타입, 인덱스 등을 설정할 수 있습니다.
  1. @Dao
  • 데이터 접근 객체(Data Access Object)를 정의할 때 사용합니다.
  • 데이터베이스의 CRUD(Create, Read, Update, Delete) 연산을 위한 메소드를 포함합니다.
  1. @Insert
  • 데이터를 삽입하는 메소드에 사용합니다.
  • 해당 메소드는 엔티티를 인자로 받아 데이터베이스에 추가합니다.
  1. @Query
  • 복잡한 SQL 쿼리를 실행하는 메소드에 사용합니다.
  • 메소드에 주어진 SQL 쿼리를 실행하여 결과를 반환합니다.
  1. @Update
  • 데이터를 업데이트하는 메소드에 사용합니다.
  • 인자로 받은 엔티티의 데이터로 기존 레코드를 갱신합니다.
  1. @Delete
  • 데이터를 삭제하는 메소드에 사용합니다.
  • 인자로 받은 엔티티를 데이터베이스에서 제거합니다.
  1. @Transaction
  • 메소드가 하나의 트랜잭션으로 실행되어야 함을 나타냅니다.
  • 여러 연산을 하나의 작업으로 묶어 실행할 때 사용합니다.
  1. @ForeignKey
  • 엔티티 간의 외래 키 관계를 정의할 때 사용합니다.
  • 참조 무결성을 유지하는 데 도움을 줍니다.
  1. @Index
  • 특정 컬럼에 인덱스를 생성할 때 사용합니다.
  • 쿼리 성능을 향상시키는 데 유용합니다.
profile
공장자동화와 웹 개발을 핥아 먹다 앱 개발로 전향한 개발자의 키보드의 낡은 키캡⛑️

0개의 댓글