- SQLite를 쉽게 사용할 수 있는 데이터베이스 객체 매핑 라이브러리
- 쉽게 Query를 사용할 수 있는 API를 제공
- Query를 컴파일 시간에 검증함
- Query결과를 LiveData로하여 데이터베이스가 변경될 때 마다 쉽게 UI를 변경할 수 있음
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(tableName = "student_table") // 테이블 이름을 student_table로 지정함
data class Student (
@PrimaryKey
@ColumnInfo(name = "student_id")
val id: Int,
val name: String
)
@Query("SELECT * from table") fun getAllData() : List<Data>@Insert, @Update, @Delete는 SQL 쿼리를 작성하지 않아도 컴파일러가 자동으로 생성함OnConflictStrategy.ABORT: key 충돌시 종료OnConflictStrategy.IGNORE: key 충돌 무시OnConflictStrategy.REPLACE: key 충돌시 새로운 데이터로 변경@Update나 @Delete는 primary 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(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
}
}
}
앞에서 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")
}
}
myDao = MyDatabase.getDatabase(this).getMyDao()
runBlocking { // (주의) UI를 블록할 수 있는 DAO 메소드를 UI 스레드에서 바로 호출하면 안됨
myDao.insertStudent(Student(1, "james")) // suspend 지정되어 있음
}
val allStudents = myDao.getAllStudents() // LiveData는 Observer를 통해 비동기적으로 데이터를 가져옴LiveData는 안드로이드 아키텍처 컴포넌트의 일부로, 관찰 가능한 데이터 홀더 클래스입니다. 이를 통해 UI 컴포넌트(예: 액티비티, 프래그먼트)는 데이터의 변경 사항을 관찰하고 이에 반응할 수 있습니다. 데이터가 변경될 때마다 LiveData는 관찰자에게 알림을 보냅니다.
LiveData는 안드로이드의 수명주기를 인식합니다. 즉, 액티비티나 프래그먼트의 수명주기 상태에 따라 알림을 자동으로 관리합니다. 이로 인해 메모리 누수 및 액티비티가 종료된 상태에서 발생할 수 있는 크래시를 방지할 수 있습니다.LiveData를 사용하면 UI가 데이터와 일관되게 유지됩니다. 데이터가 변경될 때 UI가 자동으로 갱신되기 때문에, 사용자에게 최신의 정보를 제공할 수 있습니다.LiveData는 뷰모델(ViewModel)과 함께 사용되어 앱의 데이터를 중앙에서 관리할 수 있게 합니다. 이는 데이터 관리를 더욱 효율적으로 만들어 줍니다.LiveData의 관찰자는 오직 활성 수명주기 상태(active lifecycle state)의 컴포넌트에만 알림을 보냅니다. 이는 데이터가 변경될 때 활성 상태의 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
}