RoomDatabase 사용하기!

2024. 3. 9. 01:45수업 복습!

오늘은 기존의 SQLiteDatabase가 아닌 RoomDatabase에 대해 배우게 되었습니다!

 

RoomDatabase란?
SQLiteDatabase 사용 시 프로그래밍을 보다 간단하게 할 수 있도록 제공되는 라이브러리로써
구글에서 정한 규격대로 프로그래밍을 하게 되면 SQLiteDatabase를 사용하는 코드를
자동으로 만들어준다! 


1단계 : ViewBinding 세팅부터 해줍니다!

 kotlinOptions {
        jvmTarget = "1.8"
    }
    buildFeatures {
        viewBinding = true
    }
}

 

class MainActivity : AppCompatActivity() {

lateinit var activityMainBinding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(activityMainBinding.root)
    }
}

 

 

2단계 : build.gradle.kts에 라이브러리를 설정 해줍니다

--> 상단 plugins에는 Kotlin("kapt")를 설정해주고

plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
kotlin("kapt")
}

--> 하단 plugins에는 implelmentation과 annotationProcessor, kapt를 설정 해줍니다

implementation("androidx.room:room-runtime:2.6.1")
annotationProcessor("androidx.room:room-compiler:2.6.1")
kapt("androidx.room:room-compiler:2.6.1")

 

 

3단계 : Entity를 작성해줍니다

Entity는 데이터 베이스에 저장된 데이터를 담거나 저장할 데이터를 담을 모델에 해당합니다

RoomDatabase는 Entity에 작성한 내용을 기반으로 테이블을 생성해줍니다.

//tableName : 생성될 테이블의 이름
@Entity(tableName = "TestTable")

위의 코드처럼 @Entity를 활용하여 테이블을 생성해줍니다!

 

이후 컬럼으로 설정할 프러퍼티들을 주 생성자에 정의 합니다

//주 생성자에 정의한 프러퍼티들이 컬럼으로 만들어진다
data class TestModel (
    //idx 컬럼
    //autoGenerate에 true를 넣어주면 데이터를 저장할 때 마다 1씩 증가 되는
    //값으로 채워준다
    @PrimaryKey(autoGenerate = true)
    var testIdx: Int = 0,
    var testData1: String = "",
    var testData2: Double = 0.0
)

 

 

4단계 : Dao를 작성해준다.

Dao는 DataBase Access Object의 약자로서

데이터 베이스에 접속해서 데이터를 읽고 쓰는 작업을 수행합니다!

--> TestDao라고 이름을 만들었습니다!

 

우선 TestDao를 인터페이스로 만들어 줍니다

@Dao
interface TestDao {
}

 

데이터 저장

//데이터 저장
//매개 변수로 들어오는 모델 중에
//primary key로 지정된 프러퍼티는 1부터 1씩 증가되는 값으로 저장되고
//그 외에는 프러퍼티에 들어있는 값이 저장된다
// ) insert into TestTable (testData1, testData2) values (?, ?)
@Insert
fun insertData(testModel: TestModel)

 

데이터 수정

//데이터 수정
//매개 변수로 들어오는 모델 중에
//primary key로 지정된 프로퍼티를 조건절로 하고
//그 외에는 프러퍼티에 들어있는 값으로 수정된다
// ) update TestTable testData1 = ?, testData2 = ? where testIdx = ?
@Update
fun updateData(testModel: TestModel)

 

데이터 삭제

//데이터 삭제
//매개 변수로 들어오는 모델의 프로퍼티 중에 primary key로 지정된 프로퍼티를 조건절로 하는
//쿼리문이 만들어진다
//) delete from TestTable testIdx = ?
@Delete
fun deleteData(testModel: TestModel)

 

이 때 만약 자동으로 만들어지는 쿼리문이 아닌 다른 쿼리문을 쓰고자 한다면

    Query라는 이노테이션을 이용합니다

    데이터를 가져오는 것도 Query라는 이노테이션을 사용해야 합니다!

 

모든 행의 데이터를 다 가져오기

//모든 행의 데이터를 다 가져온다
//반환 타입에 지정된 객체에 행의 데이터를 담아 전달해준다
@Query("select testIdx, testData1, testData2 from TestTable")
fun selectAllData():List<TestModel>●

 

행 하나의 데이터를 가져오기

//행 하나의 데이터를 가져온다
//쿼리문에 값에 해당하는 부분은 매개변수의 이름을 지정한다
// :매개변수 이름
@Query("select testIdx, testData1, testData2 from TestTable where testIdx = :idx")
fun selectOneData(idx:Int) : TestModel

 

 

5단계 : DataBase를 작성해준다

SQLiteOpenHelper의 역할을 해준다고 생각하면 됩니다

추상 클래스로 만들어야 하고 이 클래스를 상속받아 필요한 기능들을 구현한 클래스가

자동으로 생성됩니다.

 

entity들을 정의해줍니다! 밑의 경우에는 Table이 하나라서 한 개만 적었지만

여러 개의 Table을 생성했을 경우 모두 적어줍니다!

//entities : entity들을 지정한다. 지정한 entity 하나당 하나의 테이블이 생성된다
@Database(entities = [TestModel::class], version = 1)

 

 

 

추상 클래스를 만들어 Dao를 지정해준다

abstract class TestDatabase : RoomDatabase() {

//dao를 지정한다
abstract fun testDao() : TestDao
}

 

 

companion Object안에 데이터 베이스 객체를 담을 변수를 만들어준다

companion object{
//데이터 베이스 객체를 담을 변수
var testDataBase:TestDatabase? = null
}

 

※여기서 잠깐!

RoomDatabase는 코루틴을 이용하게 됩니다.

즉 비동기적으로 동작할 수 있도록 되어 있습니다

하지만 비동기적 작업시 데이터 베이스 접속을 여러 군데에서 하면 문제가 발생할 수 있기 때문에

데이터 베이스 접속을 동기적으로 할 수 있도록 해야합니다!

@Synchronized
fun getInstance(context: Context) : TestDatabase? {
if (testDataBase == null){
synchronized(TestDatabase::class.java){
//데이터 베이스를 생성하고 Model들의 구조와 동일한 테이블을 생성한다
// test.db : 접속할 SQLiteDatabase파일!
testDataBase = Room.databaseBuilder(
context.applicationContext, TestDatabase::class.java, "test.db"
).build()
}
}
return testDataBase
}

 

 

6단계 : MainActivity에서 구현해본다!

우선 제가 사용할 xml을 이렇게 구성했습니다

insert : 데이터를 저장한다

selectAll : 데이터를 전부 가져온다

selectOne : 지정한 데이터를 가져온다

update : 데이터를 수정한다

delete : 데이터를 삭제한다

 

# insert 

//데이터를 저장한다
    fun insertData(){
        activityMainBinding.apply {
            button.setOnClickListener {
                //데이터 베이스에 접속한다
                val testDataBase = TestDatabase.getInstance(this@MainActivity)

                //저장할 데이터를 담는다
                //primary key로 지정된 프러퍼티는 1부터 1씩 증가되는 값이 자동으로 부여되기 때문에
                //이 프러퍼티는 제외한다
                val testModel1 = TestModel1(testData1 = "문자열 1", testData2 = 11.11)
                //저장한다
                CoroutineScope(Dispatchers.Main).launch {
                    async (Dispatchers.IO){
                        testDataBase?.testDao()?.insertData(testModel1)
                    }
                }

                val testModel2 = TestModel1(testData1 = "문자열 2", testData2 = 22.22)
                CoroutineScope(Dispatchers.Main).launch {
                    async (Dispatchers.IO){
                        testDataBase?.testDao()?.insertData(testModel2)
                    }
                }
            }
        }

 

# selectAll

//모든 데이터를 가지고 온다 
        fun selectAllData(){
            activityMainBinding.apply { 
                button2.setOnClickListener { 
                    //데이터 베이스 접속
                    val testDatabase = TestDatabase.getInstance(this@MainActivity)
                    CoroutineScope(Dispatchers.Main).launch{
                        //데이터를 받아온다
                        val job1 = async(Dispatchers.IO){
                            testDatabase?.testDao()?.selectAllData()
                        }
                        val dataList = job1.await() as List<TestModel>
                        
                        textView.text = ""
                        
                        dataList.forEach { 
                            textView.append("testIdx : ${it.testIdx}")
                            textView.append("testData1 : ${it.testData1}")
                            textView.append("testData2 : ${it.testData2}")
                            
                        }
                    }
                }
            }
        }

 

#selectOne

//한 개의 데이터를 가지고 온다
    fun selectOneData(){
        activityMainBinding.apply { 
            button3.setOnClickListener { 
                //데이터 베이스에 접속한다
                val testDatabase = TestDatabase.getInstance(this@MainActivity)
                CoroutineScope(Dispatchers.Main).launch { 
                    //데이터를 받아온다
                    val job1 = async(Dispatchers.IO){
                        //여기서 1은 idx의 값을 의미한다
                        testDatabase?.testDao()?.selectOneData(1)
                    }
                    val testModel = job1.await() as TestModel
                    
                    textView.text = ""
                    
                    textView.append("testIdx : ${testModel.testIdx}\n")
                    textView.append("testData1 : ${testModel.testData1}\n")
                    textView.append("testData2 : ${testModel.testData2}\n")
                }
            }
        }
    }

 

# update

//데이터를 수정해준다
    fun updateData(){
        activityMainBinding.apply { 
            //데이터 베이스 접속
            val testDatabase = TestDatabase.getInstance(this@MainActivity)
            //데이터를 준비한다
            //primary key인 textIdx가 조건절이 된다
            val testModel = TestModel(testIdx = 1, testData1 = "새로운 문자열", testData2 = 55.55)
            //수정한다
            CoroutineScope(Dispatchers.Main).launch { 
                async(Dispatchers.IO) { 
                    testDatabase?.testDao()?.updateData(testModel)
                }
                
                textView.text = "완료"
            }
        }
    }

 

#delete

//데이터를 삭제해준다
    fun deleteData(){
        activityMainBinding.apply { 
            button5.setOnClickListener { 
                //데이터 베이스에 접근한다
                val testDatabase = TestDatabase.getInstance(this@MainActivity)
                //데이터를 준비한다
                //primary key인 textIdx가 조건절이 된다
                val testModel = TestModel(testIdx = 1)
                //삭제한다
                CoroutineScope(Dispatchers.Main).launch { 
                    async(Dispatchers.IO){
                        testDatabase?.testDao()?.deleteData(testModel)
                    }
                    textView.text = "삭제 완료"
                }
            }
        }
    }

 

 

실행을 해보니 매우 잘 작동을 하였습니다!!

 

느낀점
지금까지 SQLiteDatabase만 사용해서 내부 저장소에 데이터를 저장했었는데
오늘 RoomDatabase를 활용하는 방법을 배우게 되어 좋았습니다
사실 멘토님과 멘토링을 하면서 
요즘에는 SQLite보단 Room을 더 자주 쓴다고 말씀해주셨는데
SQLite만 사용할 줄 알다 보니
Room이란 무엇인지 
왜 SQLite보단 Room을 더 자주 사용하는지 
알 수 없었습니다..
하지만 오늘 드디어 RoomDatabase를 배우게 됨으로써
SQLiteDatabase를 사용하는 것보다
코드가 간결해진다는 걸 느낄 수 있었고
현재 제가 만들었던 프로젝트들의 내부 저장소를
SQLite에서 Room으로 바꿔보며 더욱 손에 익을 수 있도록
노력하겠습니다!