[Android, Room] Room Database 스터디 기록 (2)

 

Room Database 스터디 기록 (1)

https://jinudmjournal.tistory.com/129

 

[Android, Room] Room Database 스터디 기록 (1)

https://developer.android.com/training/data-storage/room?hl=ko#components Room을 사용하여 로컬 데이터베이스에 데이터 저장 | Android 개발자 | Android Developers Room 라이브러리를 사용하여 더 쉽게 데이터를 유지하는

jinudmjournal.tistory.com

 

 

Type Converter

커스텀한 데이터 객체를 저장해야 할 경우, Convertes 클래스를 만들어야 하는 경우가 있다.

예를 들어서 특정 Timestamp와 Date 객체를 변환하는 방법을 사용할 수 있다.

이 방식으로 새로운 데이터에 대한 저장 시간을 기록할 수 있다.

 

이 Converter 들을 직접 필요한 Entity에 추가하여 적용하게 된다.

 

class Converters {
    @TypeConverter
    fun fromTimestamp(value : Long?) : Date? {
        return value?.let { Date(it) }
    }

    @TypeConverter
    fun dateToTimestamp(date: Date?) : Long? {
        return date?.time?.toLong()
    }
}

 

아래와 같이 채팅 메시지의 타입을 지정하는 기능에도 사용할 수 있다.

 

@Entity
data class ChatMessage(
    @TypeConverters(Converters::class)
    val messageType : MessageType,
)

enum class MessageType {
    TEXT,
    IMAGE,
    VIDEO,
    AUDIO
}

 

Database

데이터와 쿼리가 준비 되었으니, 이제 데이터베이스를 구현해야 한다.

@Database 어노테이션을 사용하며, abstract class를 사용해서 정의한다.

 

이 어노테이션을 통해서 Room Library에 SQL 코드 등의 구현을 맡길 수 있다.

개발자가 정의한 코드에 따라서 룸 라이브러리가 필요한 SQL 코드들을 생성한다.

 

@Database(entities = [Student::class], version = 1)
abstract class StudentDatabase :RoomDatabase() {
    abstract fun studentDao() : StudentDao
}

 

위의 데이터베이스를 객체로 만들어서 사용할 수 있으며, converters 클래스를 가지고 있는 상태로 

Entity에 적용하지 않았다면 아래 어노테이션을 통해서 추가할 수도 있다.

 

@TypeConverters(Converters::class)

 

Hilt를 활용한 객체 주입

 

Room DB를 적용하기 위해서는 Hilt를 사용한 의존성 주입을 적용해야 한다.

Entity, Dao, Database 컴포넌트를 만들었으니, 이제 Hilt가 의존성을 생성해주면 Room 데이터 베이스를 사용할 수 있다.

 

Module을 만들어서 Database 객체를 생성하는 방법을 Provides Annotation을 사용해서 Hilt에 알려주면 된다.

MVVM 패턴을 활용해서 Repository 인터페이스를 주입하는 방법을 활용할 수 있다.

 

interface StudentRepository {

    suspend fun loadAllByStudentIds(studentIds: IntArray) : List<Student>

    suspend fun findByStudentName(first : String, last : String)  : Student

    suspend fun deleteStudent(student: Student)

    suspend fun getFavoriteStudents() : Flow<List<Student>>
}

 

위와 같이 room에서 사용할 주요 Dao의 기능을 하는 함수를 repository로 정의했다.

이 인터페이스를 Dao 객체를 가지고 구현하는 구현클래스인 StudentRepositoryImpl를 생성해주어야 한다.

 

class StudentRepositoryImpl(
    private val dao: StudentDao
) : StudentRepository {
    override suspend fun loadAllByStudentIds(studentIds: IntArray): List<Student> {
        return dao.loadAllByIds(studentIds = studentIds)
    }

    override suspend fun findByStudentName(first: String, last: String): Student {
        return dao.findByName(first,last)
    }

    override suspend fun deleteStudent(student: Student) {
        dao.delete(student)
    }

    override suspend fun getFavoriteStudents(): Flow<List<Student>> {
        return dao.getFavoriteStudents()
    }
}

 

의존성 주입에 대한 준비를 마쳤으니, 이제 Provides 어노테이션을 활용한 Module을 생성한다.

 

@InstallIn(SingletonComponent::class)
@Module
object AppModule

 

모듈에서 의존성 주입하는 방법은 2가지가 있다.

첫번째는 Room Database 객체를 직접 생성해서 주입하는 방법이고,

두번째는 Room을 이용하는 Repository 객체를 생성하는 것이다.

 

Room Database 객체를 생성하는 의존성 주입

 

첫 번째 방법인 Room Database 객체를 생성하는 의존성 주입 방법이다.

 

이 때 fallbackToDestructiveMigration()을 사용하는데, 이는 기존 데이터베이스 스택을 지우고 새로 생성하는 방법이다.

이전 데이터베이스 스키마를 최신 스키마 버전으로 마이그레이션을 하며,

마이그레이션을 찾을 수 없는 경우는 Room에서 데이터베이스 테이블을 삭제한다.

 

다른 방법으로 addMigrations() 메소드를 사용하는 방법이 있다.

마이그레이션 처리는 매우 중요한 작업이기 때문에 실제 배포 전에는

충분한 테스트를 통해서 기존 DB가 날아가는 등의 문제를 확인해야 한다.

 

    @Singleton
    @Provides
    fun provideStudentDatabase(application: Application) : StudentDatabase {
        return Room.databaseBuilder(
            application,
            StudentDatabase::class.java,
            "student_db"
        )
            .fallbackToDestructiveMigration() // 기존 데이터베이스 스택을 지우고 새로 생성
            .build()
    }

 

Room을 이용하는 Repository 객체 생성

 

두번째 방법인 Room을 활용한 Repository 객체 생성 방식이다.

 

    @Singleton
    @Provides
    fun provideStudentRepository(studentDatabase: StudentDatabase) : StudentRepository{
        return StudentRepositoryImpl(studentDatabase.studentDao())
    }

 

ViewModel을 활용하지 않고, 직접 데이터베이스를 구현해서 사용할 수 있다.

 

ViewModel 

Room DB를 활용하기 위해서 viewModel을 사용했다.

Module에서 Provide가 어떻게 roomDB와 그것을 사용하는

Repository를 만드는지 Hilt에 알려주었으니 인자를 넣어서 해당 기능을 사용할 수 있다.

 

@HiltViewModel
class StudentViewModel
@Inject
constructor(
    private val repository: StudentRepository,
    val handler: SavedStateHandle
) : ViewModel()

 

이제 compose 함수에서 위의 뷰 모델을 사용해서 데이터베이스에 접근할 수 있다.

Room을 MVVM 패턴으로 구현하는 방법을 알게 되었다.

 

다음 포스팅에서는 RoomDB와 Pagging 기법을 활용한 데이터 요청 방법을 공부해야겠다.