https://goni95.tistory.com/173
1️⃣Room
Room은 내부적으로 *SQLite를 사용하여 SQLite의 모든 기능을 제공하고 DB 접근의 편의성을 높여주는 *ORM(Object Relational Mapping) 라이브러리로 DB 데이터를 Java 또는 Kotlin 객체로 *mapping할 수 있습니다.
LiveData, Rx, Coroutine Flow로 데이터를 관찰할 수 있도록 구축되어 있고, 컴파일 시 코드들을 자동으로 생성해주는 다양한 Annotation을 지원합니다.
Room은 64kb인 realm에 비해 적은 용량이고 메서드의 수가 적어 dex 크기 제한에도 고민하지 않아도 되지만 쿼리문을 작성할 줄 알아야 합니다.
사용법
아래 예제는 Koin + Room + Repository Pattern으로 구현한 예제입니다. Koin과 Repository을 설정하는 부분과 관련된 코드는 포함하고 있지않기 때문에 둘 다 모른다면 예제를 실행해보기가 어렵습니다. Koin에 대해선 알지만 Repositorty Pattern을 모르신다면 FoodNtrIrdntRepositoryImpl 대신 ViewModel에서 서 주입받도록 변경하시면 됩니다.
1. module 수준의 build.gradle 파일에 Room 라이브러리 중속성 추가
Room 2.1 버전 부터는 @Insert, @Delete, @Update annotation이 달린 DAO 메서드와 반환 유형이 Rx Completable, Single<T>, Maybe<T> 그리고 Coroutine Flow를 지원합니다.
def room_version = "2.3.0"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version" // optional - Kotlin Extensions and Coroutines support for Room
2. DB 내의 Table 역할을 하는 class 생성
import androidx.room.*
@androidx.room.Entity(tableName = "foodNtrIrdntTable")
data class FoodNtrIrdntEntity (
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id")
override val id: Long?,
@ColumnInfo(name = "company")
val company: String,
@ColumnInfo(name = "begin_year")
val beginYear: String,
@ColumnInfo(name = "description_kor")
val descriptionKOR: String,
@ColumnInfo(name = "calorie")
val calorie: Double,
@ColumnInfo(name = "carbohydrate")
val carbohydrate: Double,
@ColumnInfo(name = "protein")
val protein: Double,
@ColumnInfo(name = "fat")
val fat: Double,
@ColumnInfo(name = "sugar")
val sugar: Double,
@ColumnInfo(name = "salt")
val salt: Double,
@ColumnInfo(name = "cholesterol")
val cholesterol: Double,
@ColumnInfo(name = "saturated_fatty_acid")
val saturatedFattyAcid: Double,
@ColumnInfo(name = "trans_fat")
val transFat: Double,
@ColumnInfo(name = "serving_weight")
val servingWeight: Int,
@ColumnInfo(name = "serving_count")
val servingCount: Int
) : Entity
interface Entity {
val id: Long?
}
3. 데이터베이스의 접근을 위해서 추상 인터페이스를 제공하는 객체인 DAO class 생성
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import sang.gondroid.calingredientfood.data.dto.entity.FoodNtrIrdntEntity
@Dao
interface FoodNtrIrdntDao {
@Query("SELECT * FROM foodNtrIrdntTable WHERE description_kor LIKE :value")
suspend fun getSearchList(value: String): List<FoodNtrIrdntEntity>
@Insert
suspend fun insert(foodNtrIrdntEntity: FoodNtrIrdntEntity)
}
4. DB의 전체적인 소유자 역할을 하며, DB 생성 및 버전 관리를 위한 class 생성
import androidx.room.Database
import androidx.room.RoomDatabase
import sang.gondroid.calingredientfood.data.dto.entity.FoodNtrIrdntEntity
/**
* [22.03.25] : RoomDatabase를 생성하고 관리하는 DB 객체
* entities : 현재 DB 관련 Entity를 정의
* version : DB 버전 정의 / Schema 변경 시 version도 바뀌어야함
* exportSchema : Room에 Schema 구조를 폴더로 Export(내보내다) 할 것인지 설정
*/
@Database(
entities = [FoodNtrIrdntEntity::class],
version = 1,
exportSchema = false
)
abstract class ApplicationDatabase : RoomDatabase() {
abstract fun foodNtrIrdntDao() : FoodNtrIrdntDao
}
5. Koin을 통한 ApplicationDatabase와 FoodNtrIrdntDao 의존성 주입
internal val appModule = module {
/**
* Room
*/
single { provideDB(androidApplication()) }
single { provideFoodNtrIrdntDao(get()) }
/**
* Coroutine Dispatchers
* Gon [22.01.12] : Coroutine을 Dispatcher에 전달하면 dispatcher가 자신이 관리하는 Thread Pool 내의 Thread에 분배
*/
single { Dispatchers.IO }
single { Dispatchers.Main }
//주입받는 객체
single<FoodNtrIrdntRepository> { FoodNtrIrdntRepositoryImpl(get()) }
}
private fun provideDB(context : Context) : ApplicationDatabase =
Room.databaseBuilder(context, ApplicationDatabase::class.java, API.DB_NAME).build()
private fun provideFoodNtrIrdntDao(database: ApplicationDatabase) = database.foodNtrIrdntDao()
6. 주입받는 DAO 객체를 통한 DAO 메서드 호출
/**
* Gon [22.01.12] : Domain Layer의 FoodNtrIrdntRepository 구현체
*/
class FoodNtrIrdntRepositoryImpl(
private val ioDispatcher: CoroutineDispatcher,
private val foodNtrIrdntDao: FoodNtrIrdntDao
) : FoodNtrIrdntRepository {
override suspend fun getCustomFoodNtrIrdntList(value: String): List<FoodNtrIrdntEntity> =
withContext(ioDispatcher) {
foodNtrIrdntDao.getSearchList(value)
}
override suspend fun insertCustomFoodNtrIrdnt(foodNtrIrdntEntity: FoodNtrIrdntEntity) =
withContext(ioDispatcher) {
foodNtrIrdntDao.insert(foodNtrIrdntEntity)
}
}
*mapping : 두 개의 서로 다른 데이터간의 관계를 정의해 하나의 데이터가 다른 데이터를 가르키도록 하는 것 입니다.
*ORM : 서로 다른 객체와 테이블에 대한 불일치를 해결해주는 별도의 쿼리문 없이도 객체를 통해 간접적으로 데이터베이스의 데이터를 다룰 수 있도록 객체와 관계형 데이터베이스를 연결해주는(*mapping) 프레임워크입니다.
*Annotation : 안드로이드 개발 시 Annotation은 특정 클래스, 변수, 메소드 등에 붙여 해당 타겟의 기능을 좀 더 명확하게 해주는 역할을 합니다. Kotlin 또는 Android에 내장된 built in annotation과 Annotation에 대한 정보를 나타내기 위한 Annotation인 meta annotation, 개발자가 직접 만드는 custom annotation이 있습니다.
*SQLite : Android 플랫폼에 사용되는 오픈 소스, 서버리스 데이터베이스로 데이터베이스 파일의 저장 루트가 애플리케이션의 내부에 있어 어플리케이션을 삭제하면 해당 파일도 함께 삭제됩니다.
( tip. 저장 루트가 data/data/패키지명/databases/db명.db가 아닌 패키지 외부에 있는 경우나 매니페스트에서 allowBackup 태그를 true인 경우 삭제되지 않습니다. )
'Android > Android의 모든 것' 카테고리의 다른 글
👋Android의 모든 것 : 1️⃣MVVM을 위한 Databinding, LiveData 개념과 예제 (0) | 2022.04.26 |
---|---|
👋Android의 모든 것 : 1️⃣ AAC ViewModel과 MVVM ViewModel에 대하여 (0) | 2022.04.18 |
👋Android의 모든 것 : 1️⃣Lottie에 대해서 (0) | 2022.03.25 |
👋Android의 모든 것 : 1️⃣SharedPreference, 2️⃣DataStore (0) | 2022.03.21 |
👋Android의 모든 것 : 1️⃣안드로이드의 애니메이션의 모든 것 (0) | 2022.03.17 |