728x90
오늘은 Room에 대해서 공부를 해보자.
Room이란
Android에서 Room은 SQLite 데이터베이스를 쉽게 다룰 수 있도록 도와주는 지원 라이브러리이다.
- 안드로이드에서 앱의 데이터를 저장하는 방법은 크게 파일, 데이터베이스, 프리퍼런스로 나뉜다.
그 중 데이터 베이스 프로그래밍을 이용하여 안드로이드 폰에서 DB를 관리하는 오픈소스 SQLite 가 있다. - Room은 SQLite를 완벽히 활용하면서 원활한 데이터베이스 액세스가 가능하도록 SQLite에 추상화 계층을 제공한다.
- Realm에 비해서 낮은 용량(약 64KB)을 가지면서 꽤 괜찮은 성능과 쉽게 사용할 수 있다는 장점이 있다.
Room의 구성요소
- Entity(엔터티): 엔터티는 데이터베이스의 테이블을 나타내는 클래스이다. 각 엔터티는 데이터베이스 테이블의 열에 해당하는 필드를 가지고 있다.
- DAO(Data Access Object): DAO는 데이터베이스에 접근하여 데이터를 조작하는 메서드를 제공하는 인터페이스이다. 각 메서드는 SQL 쿼리 또는 Room에서 제공하는 편리한 어노테이션을 사용하여 정의된다.
- Database(데이터베이스): 데이터베이스는 앱에서 사용할 데이터베이스를 정의하고, DAO 인터페이스를 포함하는 클래스이다. RoomDatabase를 확장하며, 싱글톤으로 사용하는 것이 일반적이다.
Room 종속항목 추가
//Room
implementation("androidx.room:room-runtime:${rootProject.extra["room_version"]}")
ksp("androidx.room:room-compiler:${rootProject.extra["room_version"]}")
implementation("androidx.room:room-ktx:${rootProject.extra["room_version"]}")
Room의 실제 사용기
Data Module
@Module
@InstallIn(SingletonComponent::class)
object DBModule {
@Provides
@Singleton
fun provideCompanyDB(@ApplicationContext context: Context): CompanyDatabase =
Room.databaseBuilder(context, CompanyDatabase::class.java, "company_table")
.fallbackToDestructiveMigration()
.allowMainThreadQueries()
.build()
@Provides
fun provideCompanyDao(companyDatabase: CompanyDatabase): CompanyDao {
return companyDatabase.getDao()
}
}
- @Module: Dagger Hilt 모듈을 정의한다. 이 모듈은 의존성을 제공하는 데 사용된다.
- @InstallIn(SingletonComponent::class): 모듈이 어떤 Dagger 컴포넌트에 설치되어야 하는지를 지정하는데, 여기서는 SingletonComponent에 설치되었으므로 이 모듈에서 정의한 의존성은 앱의 라이프사이클 동안 단일 인스턴스로 존재하게 된다.
- @Provides: 해당 메서드가 어떤 타입의 객체를 제공하는지를 Dagger에 알려준다.
- @Singleton: 해당 의존성이 앱 전체에서 단일 인스턴스로 유지되어야 함을 나타낸다.
- @ApplicationContext: Dagger Hilt에서 제공하는 어노테이션으로, Context 객체를 주입받을 때 사용된다.
- provideCompanyDB: 데이터베이스를 생성하는 메서드이다. Room.databaseBuilder를 사용하여 CompanyDatabase 클래스의 인스턴스를 생성합니다. 또한 .fallbackToDestructiveMigration()은 데이터베이스 스키마 변경 시 데이터를 보존하지 않고 새로 생성하는 옵션을 설정하고, .allowMainThreadQueries()는 주 스레드에서 쿼리를 실행할 수 있게 해주는 옵션이다. 이 메서드는 Dagger에 의해 주입될 CompanyDatabase 객체를 생성한다.
- provideCompanyDao: CompanyDatabase로부터 CompanyDao를 얻는 메서드이다. 이 메서드는 Dagger에 의해 주입될 CompanyDao 객체를 생성한다.
CompanyDatabase
@Database(entities = [CompanyEntity::class], version = 2)
abstract class CompanyDatabase : RoomDatabase() {
abstract fun getDao(): CompanyDao
companion object {
@Volatile
private var INSTANCE: CompanyDatabase? = null
fun getDatabase(context: Context): CompanyDatabase {
if (INSTANCE == null) {
synchronized(CompanyDatabase::class) {
INSTANCE = Room.databaseBuilder(
context.applicationContext,
CompanyDatabase::class.java,
"company_table"
)
.fallbackToDestructiveMigration()
.build()
}
}
return INSTANCE as CompanyDatabase
}
}
}
- @Database(entities = [CompanyEntity::class], version = 2) 어노테이션은 데이터베이스의 구성을 나타낸다. 여기서 CompanyEntity::class는 이 데이터베이스에서 다룰 엔터티를 나타낸다. version은 데이터베이스 스키마의 버전을 나타내며, 스키마를 변경할 때마다 이 값을 증가시켜 업데이트한다.
- abstract class CompanyDatabase : RoomDatabase()은 Room 라이브러리의 RoomDatabase 클래스를 상속받는 추상 클래스이다.
- abstract fun getDao(): CompanyDao는 데이터베이스와 상호작용할 수 있는 DAO 객체를 얻기 위한 추상 메서드이다. 이 메서드는 실제 구현은 Room 라이브러리가 제공한다.
- companion object 블록 내부에 있는 코드는 싱글톤 패턴을 구현한다. 여러 스레드에서 동시에 데이터베이스 인스턴스를 생성하는 것을 방지하기 위해 @Volatile 키워드를 사용하고, INSTANCE 변수는 언제나 최신의 값을 유지한다.
- getDatabase(context: Context): CompanyDatabase 메서드는 데이터베이스 인스턴스를 가져오거나 생성한다. 만약 인스턴스가 이미 존재한다면 그것을 반환하고, 없다면 synchronized 블록을 통해 안전하게 인스턴스를 생성한다. fallbackToDestructiveMigration()은 데이터베이스 마이그레이션 중에 오류가 발생하면 기존 데이터베이스를 삭제하고 새로운 스키마로 대체하는 옵션이다.
이러한 구성으로 인해 CompanyDatabase는 앱의 생명주기 동안 단일 인스턴스만 유지하면서 안전하게 데이터베이스에 접근할 수 있도록 한다.
CompanyDao
@Dao
interface CompanyDao {
@Query("SELECT * FROM company_table")
fun getAll(): List<CompanyEntity>
@Insert(onConflict = OnConflictStrategy.REPLACE) // 같은 것이 충돌시에 새로운 것으로 대체한다.
suspend fun insertAll(companys: List<CompanyEntity>)
@Query("delete from company_table")
suspend fun deleteAll()
@Query("SELECT * FROM company_table WHERE searchQuery LIKE :query")
suspend fun searchQuery(query: String): List<CompanyEntity>
@Query("SELECT * FROM company_table WHERE companyID LIKE :query")
suspend fun searchValidCode(query: String): CompanyEntity
}
- @Dao 어노테이션은 인터페이스가 Room의 DAO로 사용됨을 나타낸다.
- getAll() 메서드는 company_table 테이블에서 모든 데이터를 가져오는데 사용된다.
- insertAll() 메서드는 리스트 형태의 CompanyEntity를 인자로 받아, 데이터베이스에 삽입하는데 사용된다. onConflict = OnConflictStrategy.REPLACE는 동일한 기본 키(primary key)가 이미 존재하는 경우 새로운 데이터로 대체하라는 옵션이다.
- deleteAll() 메서드는 company_table 테이블에서 모든 데이터를 삭제하는데 사용된다.
- searchQuery(query: String) 메서드는 searchQuery에 해당하는 검색어를 포함하는 데이터를 찾아오는데 사용된다.
- searchValidCode(query: String) 메서드는 주어진 companyID에 해당하는 데이터를 찾아오는데 사용된다. 반환값은 단일 객체인 CompanyEntity입니다.
CompanyEntity
@Entity(tableName = "company_table")
data class CompanyEntity(
val address: String,
@PrimaryKey val code: String,
val companyID: Int,
val searchQuery: String,
val divisionCode: String,
val divisionName: String,
val email: String,
val fax: String,
val gugun: String,
val latitude: String,
val longitude: String,
val modifyDate: String,
val name: String,
val registrationDate: String,
val securityKey: String,
val sido: String,
val tel: String,
val type: String,
val use: String,
val distance: String,
val duration: String
) : java.io.Serializable
Repository
class CompanyRepositoryImpl @Inject constructor(
private val companyRemoteDataSource: CompanyRemoteDataSource,
private val directions5RemoteDataSource: Directions5RemoteDataSource,
private val companyDao: CompanyDao
) : CompanyRepository {
override suspend fun getAllCompanyEntityList(): Result<Unit> {
return runCatching {
val response = companyRemoteDataSource.getCompanyList()
val companyList = response.body()!!.results.map { it.toEntity() }
companyDao.insertAll(companyList)
}
}
// (생략)
}
ViewModel
@HiltViewModel
class LoginViewModel @Inject constructor(
private val authRepository: AuthRepository,
private val companyRepository: CompanyRepository
) : ViewModel() {
// (생략)
fun bind() {
viewModelScope.launch(Dispatchers.Main) {
val result = companyRepository.getAllCompanyEntityList()
_uiState.update {
it.copy(
userMessage = if (result.isSuccess) {
R.string.success_data
} else {
R.string.failed_data
}
)
}
}
}
// (생략)
}
위 코드는 실제 프로젝트에서 사용된 코드로 앱을 실행시켜, 로그인하게 되면 회사들의 목록을 서버로 부터 받아와 RoomDB에 저장하는 코드이다. 이를 Repository와 ViewModel를 통해서 구현하였다. Bind는 해당 과정의 함수 혹은 View를 실행하면 자동으로 앱 라이프사이클에 의해서 자동으로 실행된다.
Database Inspector를 사용하여 데이터베이스 콘텐츠 보기(CodeLab 참고)
- API 수준 26 이상을 실행하는 에뮬레이터 또는 연결된 기기에서 앱을 실행한다.(아직 실행하지 않은 경우).
- Android 스튜디오의 메뉴 바에서 View > Tool Windows > App Inspection을 선택
- Database Inspector 탭을 선택
- Database Inspector 창의 드롭다운 메뉴에서 com.example.inventory를 선택(아직 선택하지 않은 경우). Inventory 앱의 item_database가 Databases 창에 표시된다.
- Databases 창에서 item_database 노드를 펼치고 검사할 Item을 선택 Databases 창이 비어 있으면 에뮬레이터에서 Add Item 화면을 사용하여 항목을 데이터베이스에 추가한다.
- Database Inspector에서 Live updates 체크박스를 선택하여 에뮬레이터나 기기에서 실행 중인 앱과 상호작용할 때 표시되는 데이터를 자동으로 업데이트한다.
참고
https://velog.io/@soyoung-dev/AndroidKotlin-ROOM-Database-사용하기
728x90
'Android > Study' 카테고리의 다른 글
[Android] OkHttp에 대해서 공부해보자. (feat. Refrofit) (0) | 2024.04.20 |
---|---|
[Android] JUnit4으로 테스트 코드 작성하기 (0) | 2024.03.12 |
[Android] abstract class를 이용하여 ViewBinding을 쉽게 사용하기 (0) | 2024.02.29 |
[Android] ListAdapter에서 DiffCallBack 정리 (0) | 2024.02.29 |
[Android] ComposeUi 도입기 (0) | 2024.02.28 |