[Android] Jetpack Room 라이브러리

박민균·2022년 7월 3일
0
post-thumbnail

1. Room

1-1 Room 이란?

Room은 스마트폰 내장 DB에 데이터를 저장하기 위해 사용하는 라이브러리이다.
평소에 우리는 메모를 저장하고, 일정을 저장하고, 즐겨보는 웹툰을 즐겨찾기 하고... 사용자의 데이터를 내장 DB에 저장할 일이 많다. 과거에는 SQLite라는 데이터베이스 엔진을 이용해 데이터를 저장했으나 다음과 같은 단점이 있었다

1-2 Room 구조

  • 데이터베이스 : 데이터베이스는 앱에 저장되어 있는 로컬 데이터에 대한 액세스 포인트를 제공해주는 역할
  • DAO(Data Access Object) : DAO는 앱에서 데이터베이스의 데이터를 추가, 삭제, 업데이트 작업을 할 수 있는 메소드를 제공해주는 역할, 그 외에도 다양한 쿼리 사용가능
  • Entity : 데이터베이스 내에 존재하는 테이블을 가리킵니다.

데이터베이스 클래스는 데이터베이스와 연관된 DAO 인스턴스 앱에 제공합니다. 이후 앱은 DAO를 통해서 테이블을 가리키는 Entity인스턴스를 가져올 수 있습니다. 그리고 앱에서 Entity 클래스를 이용하여 테이블 내에 데이터를 추가하거나, 데이터를 수정합니다.

2. 사용법

2-1 gradle

📌 build.gradle (Module: app)에 종속 항목 추가

plugins {
    id 'kotlin-kapt'
}
dependencies {

    def roomVersion = "2.4.2"
    implementation("androidx.room:room-runtime:$roomVersion")
    kapt ("androidx.room:room-compiler:$roomVersion")
    }

2-2 Entity

한국말로 하면 '개체'인 Entity는 관련이 있는 속성들이 모여 하나의 정보 단위를 이룬 것이다.
예를 들어 위와 같이 사람의 이름, 나이, 번호라는 속성이 모여서 하나의 정보 단위를 이루면 이것을 Entity라고 한다.

@Entity
data class User (
    var name: String,
    var age: String,
    var phone: String
){
    @PrimaryKey(autoGenerate = true) var id: Int = 0
}

data class에 @Entity 어노테이션을 붙여주고 저장하고 싶은 속성의 변수 이름과 타입을 정해준다.

primaryKey는 키 값이기 때문에 유일한(Unique) 값이어야 한다. 직접 지정해도 되지만 autoGenerate를 true로 주면 자동으로 값을 생성한다.

Tip : 테이블의 이름은 따로 정하지 않으면 클래스 이름을 사용하게 되는데
만약 테이블 이름을 정해주고 싶다면 @Entity(tableName="userProfile") 이렇게 하면 된다.

2-3 DAO

Data Access Object의 줄임말이다.
데이터에 접근할 수 있는 메서드를 정의해놓은 인터페이스이다.
❗️DAO에 대한 더 자세한 설명은 여기에서 확인할 수 있다.

@Dao
interface UserDao {
    @Insert
    fun insert(user: User)
 
    @Update
    fun update(user: User)
 
    @Delete
    fun delete(user: User)
{

Dao는 이렇게 생성하면 된다. 우선 class가 아니라 interface임에 유의하자.
맨 위에 @Dao 어노테이션을 붙이고 그 안에 메서드를 정의하게 되는데

@Insert를 붙이면 테이블에 데이터 삽입

@Update를 붙이면 테이블의 데이터 수정

@Delete를 붙이면 테이블의 데이터 삭제이다.

그 외에도 다른 기능의 메서드를 만들고 싶다면?
예를 들면 전체 데이터 불러오기 혹은 특정 데이터 불러오기를 하고 싶을 때는 아래와 같다.

@Dao
interface UserDao {
    @Query("SELECT * FROM User") // 테이블의 모든 값을 가져와라
    fun getAll(): List<User>
 
    @Query("DELETE FROM User WHERE name = :name") // 'name'에 해당하는 유저를 삭제해라
    fun deleteUserByName(name: String)
}

2-4 Room Database

@Database(entities = [User::class], version = 1)
abstract class UserDatabase: RoomDatabase() {
    abstract fun userDao(): UserDao
}

이번에는 데이터베이스를 생성하고 관리하는 데이터베이스 객체 만들기 위해서 위와 같은 추상 클래스를 만들어 줘야 한다. 우선 RoomDatabase 클래스를 상속받고, @Database 어노테이션으로 데이터베이스임을 표시한다.

어노테이션 괄호 안을 보면 entities가 있는데 여기에 [목차 2-2]에서 만든 entity를 넣어주면 된다.

version은 앱을 업데이트하다가 entity의 구조를 변경해야 하는 일이 생겼을 때 이전 구조와 현재 구조를 구분해주는 역할을 한다. 만약 구조가 바뀌었는데 버전이 같다면 에러가 뜨며 디버깅이 되지 않는다.

처음 데이터베이스를 생성하는 상황이라면 그냥 1을 넣어주면 된다.

공식문서에서는 데이터베이스 객체를 인스턴스 할 때 싱글톤으로 구현하기를 권장하고 있다.
일단 여러 인스턴스에 액세스를 꼭 해야 하는 일이 거의 없고, 객체 생성에 비용이 많이 들기 때문이다.

@Database(entities = [User::class], version = 1)
abstract class UserDatabase: RoomDatabase() {
    abstract fun userDao(): UserDao
 
    companion object {
        private var instance: UserDatabase? = null
 
        @Synchronized
        fun getInstance(context: Context): UserDatabase? {
            if (instance == null) {
                synchronized(UserDatabase::class){
                    instance = Room.databaseBuilder(
                        context.applicationContext,
                        UserDatabase::class.java,
                        "user-database"
                    ).build()
                }
            }
            return instance
        }
    }
}

그래서 위와 같이 companion object로 객체를 선언해서 사용하면 된다.
싱글톤으로 구현하지 않을 거라면 저 코드 부분을 호출할 부분에서 사용하면 된다.

객체를 생성할 때 databaseBuilder라는 static 메서드를 사용하는데
context와, database 클래스 그리고 데이터 베이스를 저장할 때 사용할 데이터베이스의 이름을 정해서 넘겨주면 된다.

2-5 데이터 베이스 사용

        var newUser = User("김똥깨", "20", "010-1111-5555")
 
        // 싱글톤 패턴을 사용하지 않은 경우
        val db = Room.databaseBuilder(
                applicationContext,
                AppDatabase::class.java,
                "user-database"
        ).build()
        db.UserDao().insert(newUser)
 
        // 싱글톤 패턴을 사용한 경우
        val db = UserDatabase.getInstance(applicationContext)
        db!!.userDao().insert(newUser)

📌 위 처럼 사용할 수 있지만 이렇게 사용하게 된다면 "Cannot access database on the main thread since it may potentially lock the UI for a long period of time" 에러가 뜬다.

결론은 Room은 데이터에 대한 접근이기 때문에 Main Thread에서 사용하기엔 적절하지 않다는 것이다.
따라서 코루틴을 생성 후 그 안에서 쓰는게 가장 이상적이라고 생각한다.

참고

0개의 댓글