본문 바로가기
Android/Kotlin

Android Room Database 사용하기

by wannagohome97 2024. 1. 8.

Room 이란?

Room Database 는 Android Jetpack Library 중 하나로 SQL 테이블 데이터에 객체를 매핑시켜주는 DB 라이브러리입니다.

SQLite  를 사용하는 경우 SQLiteOpenHelper 를 상속하는 class 를 만들고 각 column 명을 전역 변수로 지정해놓고 DB 를 구성한 다음 DB에서 데이터를 꺼내올 땐 Cursor 단위로 data 를 꺼내와서 Cursor 를 다시 모델링한 데이터로 매핑시키는..

조금 번잡한 과정을 거쳐야했는데 Room 을 사용할 경우에는 DB 에 저장하려는 데이터 모델에서 column 명을 annotation 을 사용하여 미리 정의하고 Dao(Data Access Object) class 를 사용하여 테이블 생성 작업을 간소화할 수 있습니다.

 

그럼 이제 사용법을 알아보도록 ..

 

Data Class

db에서 사용할 데이터 모델을 정의하는 클래스에서 테이블 명과 각 column 명만 작성해주면 되는데

Kotlin 의 data class 기준으로는 아래와 같습니다.

@Entity(tableName = "sample_table")
data class Person(
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "_id")
    var id: Long? = null,
    @ColumnInfo(name = "name")
    var name: String,
    @ColumnInfo(name = "age")
    var age: Int
)

 

 

여기서 id 를 nullable 객체로 두고 null 값을 설정한 이유는 이렇게 하지 않으면 autoGenerate 옵션이 안먹습니다....

물론 서버에 있는 DB 를 클라이언트에서 올려두고 쓰는 것이고 거기 PrimaryKey 가 이미 존재한다면 그걸 가져다 써도 됩니다.

 

Dao Interface

아무튼 이제 이 객체를 DB 에서 꺼내고 집어넣는 매개체인 Dao 를 작성해야합니다.

@Dao
interface PersonDao {

    @Insert
    suspend fun insert(person: Person)

    @Insert
    suspend fun insertAll(persons: List<Person>)
    
    @Query("SELECT * FROM sample_table")
    suspend fun getPersons():List<Person>

    @Update
    suspend fun update(person: Person)

    @Delete
    suspend fun delete(person: Person)
    
    @Query("DELETE FROM sample_table")
    suspend fun clear()

}

 

 

위 코드를 살펴보면 알 수 있듯이

Insert, Update Delete 정도는 어노테이션을 붙이는 것 만으로 그냥 사용할 수 있고

이 외의 작업도 Query 어노테이션 안에 SQL 구문을 넣어서 쉽게 할 수 있습니다.

그리고 suspend function 으로 작성해야 한다는 특징이 있는데 이유는 db 작업이 무거워질 경우 UI thread 가 오랜시간 block되기 때문에 이를 방지하기 위해 suspend function 을 사용하도록 되어있습니다.(dao 를 non-suspend 로 작성하면 컴파일 에러는 나지 않지만 코드를 실행하면 crash 가 발생합니다.)

 

Database 

그럼 Database 를 생성하는 코드도 작성해봅시다.

@Database(entities = [Person::class], version = 1)
abstract class PersonDatabase : RoomDatabase() {

    abstract fun personsDao(): PersonDao

    companion object {
        @Volatile
        private var INSTANCE: PersonDatabase? = null
        private val LOCK = Any()

        fun getDatabase(context: Context): PersonDatabase {
            return INSTANCE ?: synchronized(LOCK) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    PersonDatabase::class.java,
                    "persons_database"
                ).build()
                INSTANCE = instance
                instance
            }
        }
    }
}

 

 

추상 클래스로 RoomDatabase 를 상속받은 클래스를 위와 같이 작성한 후

 

Repository

class MyRepository(context: Context) {

    companion object{
        private var instance: MyRepository? = null
        fun getInstance(context: Context): MyRepository{
            if (instance == null){
                instance = MyRepository(context)
            }
            return instance!!
        }
    }
    private val personsDao: PersonDao by lazy {
        val database = PersonDatabase.getDatabase(context)
        database.personsDao()
    }

    suspend fun insert(person: Person){
        personsDao.insert(person)
    }
    suspend fun insertAll(persons:List<Person>){
        personsDao.insertAll(persons)
    }
    suspend fun getPersons():List<Person>{
        return personsDao.getPersons()
    }
    suspend fun update(person: Person){
        personsDao.update(person)
    }
    suspend fun delete(person: Person){
        personsDao.delete(person)
    }
    suspend fun clear(){
        personsDao.clear()
    }
}

 

이런 식으로 별도의 Repository 클래스를 작성해서 Database 를 불러오며 Dao Interface 도 설정해주면 됩니다.

예시에선 싱글톤 클래스로 작성하였는데 이 부분은 상황에 맞게 사용하면 됩니다.

(저 companion object 블럭의 내용은 필수적이진 않다는 이야기입니다.)

 

이제 DB 작업을 아래와 같이 할 수 있습니다.

 

class MainActivity : AppCompatActivity() {

    private val mCoroutineScope = CoroutineScope(Dispatchers.IO)
    private val mRepository: MyRepository by lazy {
        MyRepository.getInstance(this)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mCoroutineScope.launch {
            insertCharles()
        }
    }

    private suspend fun insertCharles(){
        val person = Person(name = "김찰스",
            age = 28)
        insertSomeone(person)
        println("${mRepository.getPersons()}")
    }
    
    suspend fun insertSomeone(person: Person){
        mRepository.insert(person)
    }
}

 

위 예시 코드의 insertSomeone 에 Person 객체를 넣으면 그 Person 이 db에 Insert 되게 됩니다.

즉,  Activity 가 시작되면 28세의 김찰스씨가 db 에 insert 되는 것 입니다.

I/System.out: [Person(id=1, name=김찰스, age=28)]

 

바로 이렇게 말이죠