Y_Ding

Room 본문

TodayILearned/Android&Kotlin

Room

YJ_ILY 2023. 9. 13. 20:57

Room

안드로이드에서 데이터를 저장하는 방법 3가지 중 한가지

데이터베이스는 안드로이드에서는 SQLite라는 것을 사용

SQLite를 쉽게 사용할 수 있게 만들어진 것이 Room 라이브러리

  • 쉽게 Query를 사용할 수 있는 API 제공
  • 컴파일 시간에 Query를 검증함
  • Query 결과를 LiveData로하여 데이터베이스가 변경될 때마다 쉽게 UI를 변경 가능
  • SQLite보다 Room을 사용할 것을 안드로이드에서 권장함
  • Room의 주요 3요소
    • @Database : 클래스를 데이터베이스로 지정하는 어노테이션, RoomDatabase를 상속받는 클래스여야함
      • Room.databaseBilder를 이용하여 인스턴스 생성
    • @Entity : 클래스를 테이블 스키마로 지정하는 어노테이션
    • @Dao : 클래스를 DAO(Data Access Object)로 지정하는 어노테이션
      • 기본적인 insert, delete, update SQL은 자동으로 만들어주고, 복잡한 SQL은 직접 만들 수 있음
    • Room을 쓰려면 gradle 파일 설정을 해줘야 함
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"
}

* Androidx 사용하는 경우를 가정하고, 안드로이드 스튜디오와 SDK는 최신 버전 사용

Entity 생성

  • 테이블 스키마 정의
  • 테이블들은 어떤 컬럼들을 정의를 하는 것이 스키마
Table을 만드는 쿼리문

CREATE TABLE student_table(student_id INTEGER PRIMARY KEY, name TEXT NOT NULL);

* student_table이라는 테이블을 생성
* 테이블 안에 student id와 name이 들어갈 것
* id는 INT로 들어가고, PRIMARY KEY(unique한 값, 중복되지 않음)을 가짐
* name에는 text가 들어가고 null값이 들어갈 수 없음



@Entity data class Student

@Entity(tableName = "student_talbe")
data class Student {
	@PrimaryKey
    @ColumnInfo(name = "student_id")
    val id: Int,
    val name: String
}

DAO 생성

  • interface나 abstract class로 정의되어야 함
  • 어노테이션에 SQL 쿼리를 정의하고 그 쿼리를 위한 메소드를 선언해야 함
  • 가능한 어노테이션 : @Insert, @Update, @Delete, @Query
@Query("SELECT * from table") fun getAllData() : List<Data>

@Query("SELECT * from table") fun getAllData() : LiveData<Data>
* 리턴되는 데이터의 값을 LiveData<>로 하면, 
나중에 데이터가 업데이트될 때 Observer를 통해 할 수 있음

@Query("SELECT * FROM student_table WHERE name = :sname")
suspend fun getStudentByName(sname: String): List<Student>
* student_table에서 모든 데이터(*)를 가져올건데 이름이 sname인것을 찾아줘라
* suspend는 kotlin coroutine을 사용한 것
* 나중에 이 메소드를 호출할 때는 runBlocking{} 내에서 호출해야 함
  • @Insert, @Update, @Delete는 SQL쿼리를 작성하지 않아도 컴파일러가 자동으로 생성(Room을 쓰는 이유)
  • @Insert나 @Update는 key가 중복되는 경우 처리를 위해 onConflict를 지정 가능
    • OnConflicStrategy.ABORT : key 충돌시 종료
    • OnConflicStrategy.IGNORE : key 충돌시 무시
    • OnConflicStrategy.REPLACE : key 충돌시 새로운 데이터로 변경
  • @Update나 @Delete는 primary key에 해당되는 튜플을 찾아서 변경/삭제함

Database 생성

  • RoomDatabase를 상속하여 자신의 Room 클래스를 만들어야 함
  • 포함되는 Entity들과 데이터베이스 버전을 @Database annotation에 지정함
    • 앱을 사용하다가 추가 개발이 필요해서 테이블을 추가해야 하는 경우가 생길 때 테이블이 추가된 채로 앱을 업데이트 하게 되면 데이터베이스가 업데이트가 되지 않은 상태이기 때문에 에러가 발생함
    • 그렇기 때문에 버전 관리가 필요함
    • 기존에 깔려있던 앱에 Migration을 이용해 테이블을 추가 및 변경할 수 있음
    • RoomDatabase 객체의 addMigration() 메소드를 통해 수행

데이터 베이스 만드는 방법

@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

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) {
    	// COLUMN을 하나 추가 한다
        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")
    }
}

* ALTER TABLE : 테이블을 변경하는 것

UI연결

  • 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<> 타입으로 리턴되는 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 실습하기

  • ID와 student name을 입력한 후 Add Student 버튼 클릭 시 Student List에 추가 되도록 함
  • student name 입력 후 Query Student 버튼 클릭 시 Query Student를 확인 

'TodayILearned > Android&Kotlin' 카테고리의 다른 글

사용자 위치 얻기  (0) 2023.09.14
안드로이드 앱의 기본 구조  (2) 2023.09.14
SharedPreferences  (4) 2023.09.13
팀프로젝트 TIL  (0) 2023.09.07
팀프로젝트 TIL  (0) 2023.09.06