나만 보는 일기장

[Room] Room의 구조와 사용법 본문

개발/Android

[Room] Room의 구조와 사용법

Patrick0422 2022. 4. 4. 15:58

Room 지속성 라이브러리는 SQLite에 추상화 계층을 제공하여 SQLite를 완벽히 활용하면서 더 견고한 데이터베이스 액세스를 가능하게 합니다.

오늘은 Hilt와 ViewModel 등의 여러 라이브러리를 통해 Room을 사용하는 법을 알아보도록 하겠습니다.

Room의 구조

Room을 사용할 때 만들어 주어야 하는 것은 크게

1. DB에 들어가는 테이블의 구조인 Entity,

2. DB의 데이터에 접근할 수 있는 함수를 정의해 놓은 DAO,

3. DB 그 자체인 Database

3가지 입니다.

1. Entity

@Entity(tableName = Constants.TABLE_NAME)
class TeamBookmarkEntity(
    var favoriteTeam: TeamData
) {
    @PrimaryKey(autoGenerate = true)
    var id: Int = 0
}

먼저 Entity는 @Entity 어노테이션으로 표시해 줍니다. 기본은 클래스 이름대로 테이블이 생성되지만, @Entity(tableName = 테이블 이름) 형식으로 테이블 이름을 따로 지정해 줄 수도 있습니다. 

그다음 @PrimaryKey 어노테이션으로 기본키를 설정해 줍니다. 기본키는 직접 설정할 수도 있고, @PrimaryKey(autoGenerate = true)와 같이 자동으로 설정되도록 쓸 수도 있습니다. 

2. DAO

@Dao
interface TeamBookmarkDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertTeamBookmark(teamBookmark: TeamBookmarkEntity)

    @Query("SELECT * FROM team_bookmark_table ORDER BY id ASC;")
    fun readTeamBookmark(): Flow<List<TeamBookmarkEntity>>

    @Delete
    suspend fun deleteTeamBookmark(teamBookmarkEntity: TeamBookmarkEntity)
}

DAO는 @Dao 어노테이션으로 표시해주고, 내부에 사용할 기능들을 만들어 줍니다.

단순 삽입, 삭제, 수정은 @Insert, @Delete, @Update 어노테이션을 사용하면 Room이 알아서 처리해주고,

세부적인 쿼리를 작성하기 위해서는 @Query 어노테이션을 달고 내부에 SQL문을 작성해 주면 됩니다.

3. Database

@Database(
    entities = [TeamBookmarkEntity::class],
    version = 1,
    exportSchema = false
)
@TypeConverters(TeamBookmarkTypeConverter::class)
abstract class TeamBookmarkDatabase: RoomDatabase() {
    abstract fun teamBookmarkDao(): TeamBookmarkDao
}

Database 역시 @Database 어노테이션으로 표시해 준 후, DB에서 사용할 엔티티들을 넣어줍니다. version은 테이블의 구조가 달라질 경우 올려주어야 한다고 하는데, 저는 자세히 모르겠습니다.

 

위에 나온 @TypeConverters 어노테이션의 경우, LocalDateTime 같은 Room에 그대로 집어넣을 수 없는 자료형의 경우에 String 같은 자료형으로 변환해주는 TypeConverter를 만들어 지정해준 것 입니다. TypeConverter는 @TypeConverter 어노테이션을 붙여 구현하면 됩니다.

@TypeConverter
fun localDateTimeToString(localDateTime: LocalDateTime): String =
    localDateTime.format(DateTimeFormatter.ofPattern(localDateTimePattern))

@TypeConverter
fun stringToLocalDateTime(string: String): LocalDateTime =
    LocalDateTime.parse(string, DateTimeFormatter.ofPattern(localDateTimePattern))

 

이제 Room의 구성요소는 만들었고, DI를 통해 DAO를 주입 받기 위해서 Module을 만들어줍니다.

@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {

    @Provides
    @Singleton
    fun provideWakeUpTimeDataBase(@ApplicationContext context: Context): WakeUpTimeDatabase =
        Room.databaseBuilder(context, WakeUpTimeDatabase::class.java, DATABASE_NAME).build()

    @Provides
    @Singleton
    fun provideWakeUpTimeDao(database: WakeUpTimeDatabase) = database.wakeUpTimeDao()
}

위와 같이 만들어 주었습니다.

class Repository @Inject constructor(
    private val wakeUpTimeDao: WakeUpTimeDao
) {
    fun getWakeUpTimes(): Flow<List<WakeUpTimeEntity>> = 
        wakeUpTimeDao.getWakeUpTimes()

    suspend fun insertWakeUpTime(wakeUpTimeEntity: WakeUpTimeEntity) = 
        wakeUpTimeDao.insertWakeUpTime(wakeUpTimeEntity)

    suspend fun deleteWakeUpTime(wakeUpTimeEntity: WakeUpTimeEntity) = 
        wakeUpTimeDao.deleteWakeUpTime(wakeUpTimeEntity)
}

그 이후 Repository에 DAO를 주입해주었습니다. 데이터를 가져오는 함수는 Flow 형태로 가져오기 때문에 suspend 함수로 만들지 않았고 나머지는 suspend 함수로 만들었습니다.

@HiltViewModel
class MainViewModel @Inject constructor(
    private val repository: Repository
) : ViewModel() {
    fun getWakeUpTimes(): Flow<List<WakeUpTimeEntity>> = repository.getWakeUpTimes()

    fun insertWakeUpTime(wakeUpTimeEntity: WakeUpTimeEntity) = viewModelScope.launch {
        repository.insertWakeUpTime(wakeUpTimeEntity)
    }

    fun deleteWakeUpTime(wakeUpTimeEntity: WakeUpTimeEntity) = viewModelScope.launch {
        repository.deleteWakeUpTime(wakeUpTimeEntity)
    }
}

이후에 뷰모델에서 Repository를 가져와주고,

private fun checkIsTodaysWakeUpTimeSaved() = mainViewModel.getWakeUpTimes().asLiveData().observe(viewLifecycleOwner) { result ->
    mainViewModel.isTodaysWakeUpTimeSaved = result.map { it.wakeUpTime.dayOfMonth }.contains(LocalDate.now().dayOfMonth)
    applyLottieStyle()
}

그 이후 위와 같이 Flow를 LiveData로 변환해 값을 가져왔습니다.

Comments