개발 노트/Kotlin

[Android/Kotlin] Room DataBase 활용 예제

juwon2 2024. 5. 1. 00:26

안드로이드에서 앱의 데이터를 저장하는 방법은 여러가지가 있는데

그 중 데이터 베이스 프로그래밍을 이용하여 안드로이드 폰에서 DB를 관리하는 오픈소스 SQLite 가 있다 

Room DB는 이런 SQLite를 완벽히 활용하면서 원활한 데이터베이스 액세스가 가능하도록 제공한다

 

Room DB의 이점

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

 

 

 

 

# 예제앱 만들어보기

 

EditText에 학생에 대한 id와 이름을 입력하고 Add Student버튼을 누르면, Student List에 데이터베이스의 Insert문을 통해 정보가 추가되어서 보이게되고

이름에 검색할 이름을 적고 Query Student를 누르면 Result of Query Student부분에 검색한 결과가 나타나도록하는 예제이다

 

 

 

# 의존성 설정

Room을 사용하기 위해서는 먼저 의존성을 설정해줘야한다

plugins먼저 작성해주고 sync now해준뒤 dependencies에 추가해줘야 오류가 뜨지 않는다

그리고 version은 최신버전을 찾아서 사용해주도록하자.

plugins {
	...
    id("kotlin-kapt")   // Room
}
dependencies {
	...
    
    //Room
    val room_version = "2.6.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")

}

build.gradle

 

 

Room을 사용하기위해서 꼭 생성해줘야하는 3가지가 있는데

Entity, DAO, DataBase이다!! 

 

 

 

# Entity 생성

  • Entity는 테이블 스키마 정의

기본적으로는 클래스이름이 테이블명이지만 따로 지정하고싶으면 아래와같이 @Entitiy(tableName = " ")으로 해서 따로 테이블명을 지정해 줄 수 있다

// 테이블 이름은 student_table
@Entity(tableName = "student_table")

// Student클래스에 id,name값 들어감 (student_id 라는 이름으로)
data class Student (
    @PrimaryKey @ColumnInfo(name = "student_id")
    val id: Int,
    val name: String
)

MyEntitiy.kt

 

 

# DAO 생성

  • DAO는 interface나 abstract class로 정의되어야 함
  • Annotation에 SQL 쿼리를 정의하고 그 쿼리를 위한 메소드를 선언 , @Dao 어노테이션 필수
  • 가능한 annotation으로 @Insert, @Update, @Delete, @Query가 있음
// 데이터베이스에 접근할수있는 객체를 만듬(Dao) - CRUD
@Dao
interface MyDAO {

    // database table에 삽입(추가)
    @Insert(onConflict = OnConflictStrategy.REPLACE)  // INSERT, key 충돌(중복값 충돌)이 나면 새 데이터로 교체
    suspend fun insertStudent(student: Student)

    // student_table에 해당하는 전체 데이터 가져옴
    @Query("SELECT * FROM student_table")
    fun getAllStudents(): LiveData<List<Student>>        // LiveData<> 사용 -> 데이터가 업데이트될 때마다 observer를 통해 데이터변경 알수있음

    // name이 sname에 해당하는 데이터만 조회
    @Query("SELECT * FROM student_table WHERE name = :sname")   // 메소드 인자를 SQL문에서 :을 붙여 사용
    suspend fun getStudentByName(sname: String): List<Student>  // fun 앞에 suspend는 Kotlin coroutine을 사용하는 것임, 나중에 이 메소드를 부를 때는 runBlocking {} 내에서 호출해야 함(LiveData는 비동기적으로 동작하기 때문에 coroutine으로 할 필요없음) / 홍길동으로 된 이름이 여러명일수도 있기때문에 List로 지정

    // database table에 기존에 존재하는 데이터를 삭제
    @Delete
    suspend fun deleteStudent(student: Student); // primary key is used to find the student

}

MyDao.kt

 

 

# DataBase 생성

  • RoomDatabase를 상속하여 자신의 Room 클래스를 만들어야 함
//Room database의 기본틀
@Database(entities = [Student::class], exportSchema = false, version = 1)
abstract class MyDatabase : RoomDatabase() {    // RoomDatabase 상속
    abstract fun getMyDao() : MyDAO


    //  companion object를 만들어서 getDatabase라는 메소드만 호출하면 바로 불러올수있도록 (싱글톤 패턴으로 만듦)
    companion object {
        private var INSTANCE: MyDatabase? = null

        private val MIGRATION_1_2 = object : Migration(1, 2) {
            override fun migrate(database: SupportSQLiteDatabase) {
            }
        }

        @Synchronized
        fun getDatabase(context: Context) : MyDatabase {
            // 싱글톤 패턴으로 인스턴스 하나만 리턴하게 되어있음
            if (INSTANCE == null) {
                INSTANCE = Room.databaseBuilder(
                    context, MyDatabase::class.java, "school_database")
                    .addMigrations(MIGRATION_1_2)
                    .build()
                // for in-memory database
                /*INSTANCE = Room.inMemoryDatabaseBuilder(
                    context, MyDatabase::class.java
                ).build()*/
            }
            return INSTANCE as MyDatabase
        }
    }
}

MyDataBase.kt

 

 

 

# 사용하기 (MainActivity)

class MainActivity : AppCompatActivity() {
    private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
    lateinit var myDao: MyDAO

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        myDao = MyDatabase.getDatabase(this).getMyDao()

        // getAllStudents() -> 전체데이터를 가져옴
        val allStudents = myDao.getAllStudents()

        // allStudents.observe를 하면, allStudents가 변경이 있을때마다 아래내용을 자동호출
        allStudents.observe(this) {
            // for문 돌려서 textStudentList에 이 값 반영
            // 주어진 데이터(it)을 id와 name사이에 -를 넣고, 한줄 띄움 (id-name)
            val str = StringBuilder().apply {
                for ((id, name) in it) {
                    append(id)
                    append("-")
                    append(name)
                    append("\n")
                }
            }.toString()
            binding.textStudentList.text = str
        }



        binding.addStudent.setOnClickListener {
            val id = binding.editStudentId.text.toString().toInt()
            val name = binding.editStudentName.text.toString()

            if (id > 0 && name.isNotEmpty()) {

                // 코루틴 사용 - 비동기처리[순서상관없이 위에서부터 순차적으로 실행되는것]
                // insertStudent를 통해(Insert문을 통해) Student 정보가 삽입되서 보임
                CoroutineScope(Dispatchers.IO).launch {
                    myDao.insertStudent(Student(id, name))
                }
            }

            // insert되고나면 값이 비도록
            binding.editStudentId.text = null
            binding.editStudentName.text = null
        }



        binding.queryStudent.setOnClickListener {
            val name = binding.editStudentName.text.toString()

            // 코루틴 사용 - 비동기처리[순서상관없이 위에서부터 순차적으로 실행되는것]
            CoroutineScope(Dispatchers.IO).launch {

                // getStudentByName을 통해 student_table중 name에 해당하는 데이터를 result변수에 할당
                val results = myDao.getStudentByName(name)

                if (results.isNotEmpty()) {
                    val str = StringBuilder().apply {
                        results.forEach { student ->
                            append(student.id)
                            append("-")
                            append(student.name)
                        }
                    }

                    // 별로의 스레드에서 동작하는것이여서 ui업데이트를 못시키기 때문에 withContext(Dispatchers.Main)를 사용해야지 ui가 업데이트됨
                    withContext(Dispatchers.Main) {
                        binding.textQueryStudent.text = str
                    }
                } else {
                    withContext(Dispatchers.Main) {
                        binding.textQueryStudent.text = ""
                    }
                }
            }
        }
    }
}

MainActivity.kt

 

 

 

# 실행결과

 

 

 

 

 

 

 

 

 

 

 

 

 

# 참고 자료

https://velog.io/@soyoung-dev/AndroidKotlin-ROOM-Database-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0

 

[Android/Kotlin] Room Database 사용하기

Android/Kotlin ROOM Database 사용하기

velog.io