Room

sumi Yoo·2022년 10월 28일
0

1. Room이란?

Room은 스마트폰 내장 DB에 데이터를 저장하기 위해 사용하는 라이브러리이다.

Room 구조

사용법

gradle

implementation 'androidx.room:room-runtime:2.2.6'
kapt 'androidx.room:room-compiler:2.2.6'
plugins {
    id 'kotlin-kapt'
}

Entity

Entity는 관련이 있는 속성들이 모여 하나의 정보 단위를 이룬 것이다.

Entity(개체)와 Object(객체)는 비슷해 보이지만 다른 의미를 가지고 있다.
객체는 개체를 포함한 더 큰 개념이다.
대상에 대한 정보뿐만 아니라 동작, 기능, 절차 등을 포함하는 것이 객체이다.

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

만약 테이블 이름을 정해주고 싶다면 @Entity(tableName="userProfile") 이렇게 하면 된다.

DAO

Data Access Object의 줄임말이다.

데이터에 접근할 수 있는 메서드를 정의해놓은 인터페이스이다.

@Dao
interface UserDao {
    @Insert
    fun insert(user: User)

    @Update
    fun update(user: User)

    @Delete
    fun delete(user: User)

    @Query("SELECT * FROM User") // 테이블의 모든 값을 가져와라
    fun getAll(): List<User>

    @Query("DELETE FROM User WHERE name = :name") // 'name'에 해당하는 유저를 삭제해라
    fun deleteUserByName(name: String)
}

Room Database

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

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

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

만약 하나의 데이터 베이스가 여러 개의 entity를 가져야 한다면 arrayOf() 안에 콤마로 구분해서 entity를 넣어주면 된다.

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

@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
        }
    }
}

데이터 베이스 사용

  var newUser = User("김똥깨", "20", "010-1111-5555")

        CoroutineScope(Dispatchers.IO).launch {
            val db = UserDatabase.getInstance(applicationContext)
            db?.userDao()?.insert(newUser)
            Log.d("MainActivity", "${db?.userDao()?.getAll()}")
        }

서버나 DB 작업은 ANR 발생을 일으키기 때문에 코루틴을 사용했다.

https://todaycode.tistory.com/39

Gson을 이용한 Room에 다양한 타입의 데이터 저장하기

Room에 데이터를 저장할 때는 기본적으로 primitive type과 그 wrapping 타입만 지원합니다. 그럼 그 외의 사용자가 정의한 커스텀 클래스나 List와 같은 경우는 어떻게 저장해야 할까?

@Entity(tableName = "promise")
data class Promise(
    @PrimaryKey val id: String,
    val title: String,
    val destination: String,
    val destinationX: Double,
    val destinationY: Double,
    val date: String,
    val time: String,
    val members: List<User>
)

Promise 객체를 DB에 저장하려니까 아래와 같은 오류가 발생했다.

error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.

이는 List<String> 타입을 Room에 저장하는 방법을 모르기 때문에 TypeConverter를 사용해 정의해주라는 의미입니다.

converters 사용

유형 변환기는 @TypeConverter 주석을 사용하여 식별합니다.

Room 데이터베이스에 Date 인스턴스를 유지해야 한다고 가정해 보겠습니다. Room에서는 Date 객체를 유지하는 방법을 알 수 없으므로 유형 변환기를 정의해야 합니다.

@ProvidedTypeConverter
class UserTypeConverter(private val gson: Gson) {

    @TypeConverter
    fun listToJson(value: List<User>?): String? {
        return gson.toJson(value)
    }

    @TypeConverter
    fun jsonToList(value: String): List<User>? {
        return gson.fromJson(value, Array<User>::class.java)?.toList()
    }

}

Room에 데이터를 저장할 때는 json 형태의 문자열로 직렬화하여 저장한 뒤, 꺼낼 때는 다시 역직렬화하여 원하는 형태의 클래스로 변환하면 됩니다.
converters 클래스를 만들어 user list → json String, json String → userList가 되도록 gson을 사용하여 구현

@TypeConverters(UserTypeConverter::class)

database 클래스에 TypeConverters(Converters::class) 를 추가해주면 정상적으로 Room Database를 사용할 수 있음.

@ProvidedTypeConverter

유형 변환기 초기화 제어를 한다.
Room의 annotaion으로 해당 클래스가 타입 컨버터임을 나타낸다.

Room은 일반적으로 유형 변환기의 인스턴스화를 자동으로 처리합니다. 그러나 때에 따라 추가 종속 항목을 유형 변환기 클래스에 전달해야 할 수도 있습니다. 즉, 앱에서 유형 변환기의 초기화를 직접 제어해야 합니다. 이 경우 변환기 클래스에 @ProvidedTypeConverter 주석을 답니다.

그런 다음 @TypeConverters에서 변환기 클래스를 선언하는 것 외에도 RoomDatabase.Builder.addTypeConverter() 메서드를 사용하여 변환기 클래스 인스턴스를 RoomDatabase 빌더에 전달합니다.

val db = Room.databaseBuilder(...)
  .addTypeConverter(exampleConverterInstance)
  .build()

@ProvidedTypeConverter를 사용하지 않는다면 .addTypeConverter(exampleConverterInstance) 과정은 필요없다.

ProvidedTypeConverter를 왜 쓰는가?

// 컴파일 시간 유효성 검사를 건너뛰도록 변환기에 주석 달기
@ProvidedTypeConverter
class UserTypeConverter(private val gson: Gson) {

    @TypeConverter
    fun listToJson(value: List<User>?): String? {
        return gson.toJson(value)
    }

    @TypeConverter
    fun jsonToList(value: String): List<User>? {
        return gson.fromJson(value, Array<User>::class.java)?.toList()
    }

}

UserTypeConverter() 안에 따로 정의한 생성자가 있기 때문에 room에서 자동적으로 인스턴스화 해주는 과정을 거치지 않아야 한다. UserTypeConverter()에 따로 생성자가 없다면 @ProvidedTypeConverter 주석이 필요없다.

Room에서 객체 참조를 허용하지 않는 이유 이해

Room은 항목 클래스 간의 객체 참조를 허용하지 않습니다. 대신 앱에 필요한 데이터를 명시적으로 요청해야 합니다.

클라이언트 측에서는 이 유형의 지연 로드가 일반적으로 UI 스레드에서 발생하기 때문에 실행 가능하지 않으며 UI 스레드에서 디스크에 관한 정보를 쿼리하면 상당한 성능 문제가 발생하기 때문에 객체참조를 허용하지 않음
때문에 개발자가 직접 TypeConverters를 구현해주어 Room database에 data class를 사용할 수 있게 해주어야 한다.

https://jroomstudio.tistory.com/48
https://dev.to/mzgreen/room-provided-type-converters-explained-2hfd

0개의 댓글

관련 채용 정보