-
Room - Android Local DatabaseAndroid 2023. 2. 23. 13:02
๐ ์๋ ํ์ธ์ ! Mash-Up 10๊ธฐ Android Developer ์ ํ์ฑ ์ธ์ฌ๋๋ฆฝ๋๋ค. ๐์ด๋ฒ ํฌ์คํ ์ฃผ์ ๋ก ๋ฌด์์ ํ ๊น ๊ณ ๋ฏผ์ ํ๋ค,,,, ์ต๊ทผ ์ทจ์ ์ค๋น๋ฅผ ํ๋ฉฐ ๊ทธ๋์ ๊ฐ๋ฐํ๋ ์ ํ๋ฆฌ์ผ์ด์ ๋ค์ ํ๋์ ํฌํธํด๋ฆฌ์ค๋ก ๋ณด์ฌ์ฃผ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฐ๋ฐํ ์ ์ด ์์ต๋๋ค. ๊ฐ๋ฐํ๋ฉฐ ์ฌ์ฉํ๋ ๊ธฐ์ ์คํ ์ค ํ๋์ธ Room์ ๋ํด ๋ค์ ์ ๋ฆฌํด๋ณผ ๊ฒธ ํฌ์คํ ์ฃผ์ ๋ก ์ ์ ํ์์ต๋๋ค.
๐พ Local Database
์๋๋ก์ด๋์์๋ ์ฑ์ ๋ฐ์ดํฐ๋ฅผ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ๊ธฐ ์ํ ์ ์ฅ์๋ก SQLite๋ผ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ ๊ณตํ๊ณ ์์ต๋๋ค. ์ด๋ ๋ค๋ฅธ ์ธ๋ถ Database๋ค๊ณผ ๋ฌ๋ฆฌ ์๊ท๋ชจ ๋ฐ์ดํฐ๋ฅผ ๊ด๋ฆฌํ๊ณ ์ฌ์ฉํ๋ ๋ฐ ์ ํฉํ ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋๋ค. ์๋ฅผ ๋ค์ด, ์ ํ๋ฆฌ์ผ์ด์ ์ฌ์ฉ ๊ณผ์ ์์ ๋ฐ์ํ๋ ์ฉ๋์ด ํฌ์ง ์์ ๋ฐ์ดํฐ๋ค์ ๊ตณ์ด ์๋ฒ์ ์ ์ํ๊ณ ์ ์ฅํ๋ ์๊ณ ๋ฅผ ๋ค์ด์ง ์๊ณ ๋ด๋ถ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ํตํด ์ ์ฅํ๊ณ ๊ด๋ฆฌํ ์ ์์ต๋๋ค. ์ด๋ ๊ฒ ์ ์ ๋ฐ์ดํฐ๋ฅผ ๊ด๋ฆฌํ๋๋ฐ ์ต์ ํ๋ ๋งํผ ์๋๊ฐ ๋น ๋ฅด๊ณ ๊ฐ๋ณ๋ค๋ ์ฅ์ ์ด ์กด์ฌํ์ฌ ํ์์ ์ ๋ง์ ์ ํ๋ฆฌ์ผ์ด์ ์ด ๊ณตํต์ผ๋ก ๋ด๋ถ DB๋ฅผ ํ์ฉํ๊ณ ์์ต๋๋ค.
Local Database ์ข ๋ฅ
SQLite Database Library, Room Database Library
์๋๋ก์ด๋์์ Local Database๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ผ๋ก, SQLite Database Library ์ฌ์ฉ์ ๊ถ์ฅํด์์ต๋๋ค.
ํ์ง๋ง ์ด๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ์๋นํ ์๊ฐ๊ณผ ๋ ธ๋ ฅ ๋ฑ ์ฌ์ฉ ๋ฐฉ๋ฒ์ด ๋ณต์กํ์ฌ, ๊ตฌ๊ธ์์๋ ์ด๋ฌํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ณ ์ ์๋กญ๊ฒ Room Database Library๋ฅผ ๋ฐฐํฌํ์์ต๋๋ค.
๐ Room์ ์๋กญ๊ฒ ์ถ๊ฐ๋ ๊ธฐ๋ฅ 1. ์ปดํ์ผ ์์ ์ SQL ์ฟผ๋ฆฌ์ ๋ํ ์ ํจ์ฑ ๊ฒ์ฌ ๊ธฐ๋ฅ 2. Schema๊ฐ ๋ณ๊ฒฝ๋ ์ ์๋์ผ๋ก ์ ๋ฐ์ดํธ ๊ธฐ๋ฅ 3. Java ๋ฐ์ดํฐ ๊ฐ์ฒด๋ฅผ ๋ณ๊ฒฝํ๊ธฐ ์ํด ์์ฉ๊ตฌ ์ฝ๋ ์์ด ORM ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํตํด ๋งคํ ๊ธฐ๋ฅ 4. LiveData์ Rx Java๋ฅผ ์ํ Observation ์์ฑ ๋ฐ ๋์ ๊ธฐ๋ฅ
์์ ๊ฐ์ด SQLite Database Library์์๋ ํ์ฉ๋์ง ์๋ ๊ธฐ๋ฅ๋ค์ด ์๋กญ๊ฒ ์ถ๊ฐ๋๋ฉด์ ์กฐ๊ธ ๋ ๊ฐ๋ฐ์๋ค์ด Local DB๋ฅผ ๊ฐํธํ๊ฒ ๊ตฌํํ ์ ์๋๋ก ์ค๊ณ๋ ๋ชจ์ต์ ๋ณผ ์ ์์ต๋๋ค. ๊ทธ์ ๋ฐ๋ผ SQLite Database์ ์ฌ์ฉ ๋น๋๋ ์ค์ด๋ค๊ณ , Room Database์ ์ฌ์ฉ ๋น๋๋ ์ฆ๊ฐํ๊ณ ์์ต๋๋ค.
๐ฝ Room Library
ORM์ด๋?
๋จผ์ , Room์ ์์ธํ ์์๋ณด๊ธฐ ์ ์ ORM์ ๋ํ ๊ฐ๋ ์ ๊ฐ๋จํ ์ดํด๋ณผ ํ์๊ฐ ์์ต๋๋ค.
- Object-Relational Mapping
- ORM์ด๋, Object Relational Mapping, ๊ฐ์ฒด-๊ด๊ณ ๋งคํ์ ์ค์๋ง์ ๋๋ค. ๊ฐ์ฒด ์งํฅ ํ๋ก๊ทธ๋๋ฐ(Object Oriented Programming, OOP)์ ํด๋์ค๋ฅผ ์ฌ์ฉํ๊ณ , ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค(Relational DataBase, RDB)๋ ํ ์ด๋ธ์ ์ฌ์ฉํฉ๋๋ค. ์ด๋ฌํ ๊ฐ์ฒด ๋ชจ๋ธ๊ณผ ๊ด๊ณํ ๋ชจ๋ธ ๊ฐ์ ๋ถ์ผ์น๊ฐ ์กด์ฌํ๊ฒ ๋๋๋ฐ, ORM์ ํตํด ๊ฐ์ฒด ๊ฐ์ ๊ด๊ณ๋ฅผ ๋ฐํ์ผ๋ก SQL๋ฌธ์ ์๋์ผ๋ก ์์ฑํ์ฌ ๋ถ์ผ์น๋ฅผ ํด๊ฒฐํ ์ ์์ต๋๋ค.
- ์ฅ์
- ๊ฐ์ฒด ์งํฅ์ ์ธ ์ฝ๋๋ก ์ธํด ๋ ์ง๊ด์ ์ด๊ณ , ๋น์ฆ๋์ค ๋ก์ง์ ์ง์คํ ์ ์๋ค.
- ์ฌ์ฌ์ฉ์ฑ ๋ฐ ์ ์ง๋ณด์์ ํธ๋ฆฌ์ฑ์ด ์ฆ๊ฐํ๋ค.
- ๋จ์
- ์๋ฒฝํ ORM์ผ๋ก๋ง ์๋น์ค๋ฅผ ๊ตฌํํ๊ธฐ๊ฐ ์ด๋ ต๋ค.
- ํ๋ก์์ ๊ฐ ๋ง์ ์์คํ ์์๋ ORM์ ๊ฐ์ฒด ์งํฅ์ ์ธ ์ฅ์ ์ ํ์ฉํ๊ธฐ ์ด๋ ต๋ค.
Room์ด๋?
์๋๋ก์ด๋ ์ํคํ ์ณ ์ปดํฌ๋ํธ(Android Architecture Components, AAC)์ ์ํ๋ Room์ ์ ํ๋ฆฌ์ผ์ด์ ์์ SQLite ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฝ๊ณ ํธ๋ฆฌํ๊ฒ ์ฌ์ฉํ ์ ์๋๋ก ํ๋ ORM ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค.
Room Library ๊ตฌ์ฑ์์
์ํฐํฐ(Entity), ๋ฐ์ดํฐ ์ ๊ทผ ๊ฐ์ฒด(DAO), ๋ฃธ ๋ฐ์ดํฐ๋ฒ ์ด์ค(Room Database), ์ด ์ธ ๊ฐ์ ๊ตฌ์ฑ ์์๋ฅผ ํตํด Room ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๊ตฌ์ฑ๋ฉ๋๋ค.
- Entity
๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ด์ ๋ฆด๋ ์ด์ ์ฆ, ํ ์ด๋ธ์ ๋ปํ๋ฉฐ DB์ ์ ์ฅํ ๋ฐ์ดํฐ ํ์์ ์ ์ํฉ๋๋ค.
- DAO(Data Access Object)
๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ๊ทผํ์ฌ ์ํํ ์์ ์ ๋ฉ์๋ ํํ๋ก ์ ์ํฉ๋๋ค.
- Room Database
๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฒด์ ์ธ ์์ ์ ์ญํ ์ ํ๋ฉฐ DB๋ฅผ ์๋กญ๊ฒ ์์ฑํ๊ฑฐ๋ ๋ฒ์ ์ ๊ด๋ฆฌํฉ๋๋ค.
๐ป ์์ ์ดํด๋ณด๊ธฐ
Room Database๋ฅผ ํ์ฉํ์ฌ ํฌํธํด๋ฆฌ์ค ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌํํ์์ต๋๋ค.
- Entity (๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํ ์ด๋ธ ๋ด๋น) PortfolioEntity, ProfileEntity - DAO (๊ฐ๊ฐ์ ํ ์ด๋ธ์ ์ ๊ทผํ์ฌ ์์ ์ํ์ ๋ด๋น) PortfolioDao, ProfileDao - Room Database (๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฒด์ ์ธ ์์ ์ ์ญํ ) PortfolioDatabase
๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ตฌ์ฑ์์ ์ ๋๋ค.
Build.Gradle ์ค์ ํ๊ธฐ
dependencies { //Room Database Library implementation "androidx.room:room-runtime:2.2.6" implementation "androidx.room:room-ktx:2.2.6" implementation "androidx.room:room-testing:2.2.6" kapt "androidx.room:room-compiler:2.2.6" }
PortfolioEntity
@Entity(tableName = "portfolio") data class PortfolioEntity( @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") val id: Long, @ColumnInfo(name = "thumbnail") val thumbnail: Int, @ColumnInfo(name = "image_drawable") val image: String, @ColumnInfo(name = "project") val project: String, @ColumnInfo(name = "introduction") val introduction: String, @ColumnInfo(name = "programming_language") val programming: String, @ColumnInfo(name = "architecture") val architecture: String, @ColumnInfo(name = "library") val library: String, @ColumnInfo(name = "server") val server: String, @ColumnInfo(name = "term") val term: String, @ColumnInfo(name = "link") val link: String )
@Entity ์ด๋ ธํ ์ด์ ๊ณผ ํจ๊ป ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ด์ ๋ฆด๋ ์ด์ (ํ ์ด๋ธ)์ ์์ฑํ๊ณ ์์ต๋๋ค. ํด๋์ค์ ์ด๋ฆ์ PortfolioEntity์ด์ง๋ง tableName์ portfolio๋ก ์ค์ ํ์๊ธฐ ๋๋ฌธ์ ํด๋์ค ์ด๋ฆ๊ณผ๋ ๋ณ๊ฐ๋ก ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ด์ ๋ฆด๋ ์ด์ ์ด๋ฆ์ portfolio๋ก ์ค์ ๋ฉ๋๋ค. ๋ํ, ํด๋์ค์ ์์ฑ์๋ก ๋ฆด๋ ์ด์ ๋ด์ ๊ฐ๊ฐ์ ์์ฑ๋ค์ ์ ์ํ๊ณ ์์ต๋๋ค.
PortfolioDao
@Dao interface PortfolioDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertPortfolio(portfolioEntity: PortfolioEntity) @Query("SELECT * FROM portfolio") suspend fun getPortfolio(): List<PortfolioEntity> @Query("SELECT * FROM portfolio WHERE id = :id") suspend fun getPortfolioInfo(id: Long): PortfolioEntity }
@Dao ์ด๋ ธํ ์ด์ ๊ณผ ํจ๊ป ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ๊ทผํ์ฌ ์ํํ ์์ ๋ค์ ๋ฉ์๋ ํํ๋ก ์ง์ ํฉ๋๋ค. ์ด ๋ฉ์๋๋ค์ interface ๋ด์ ํฌํจ๋๋ฏ๋ก ๋ชจ๋ ์ถ์ ๋ฉ์๋๋ค์ด๋ฉฐ ์กฐํ, ์ถ๊ฐ, ์์ , ์ญ์ ๊ธฐ๋ฅ( ์ ๋ ์ถ๊ฐ, ์กฐํ ๊ธฐ๋ฅ๋ง ๊ตฌํํ์ต๋๋ค. )์ ๊ตฌํํฉ๋๋ค.
๋ํ, insert ๋ฉ์๋ ๋ถ๋ถ์ ๋ณด๋ฉด, onConflict ์ค์ ์ ํตํด ๋ง์ฝ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ด์ ์ค๋ณต๋ ๋ฐ์ดํฐ ๊ฐ์ด ์กด์ฌํ๋ค๋ฉด, ๊ทธ ๊ฐ ์์ ๋ฎ์ด ์์ฐ๋ ์ค์ ์ ํด๋์์ต๋๋ค.
PortfolioDatabase
@Database( entities = [ ProfileEntity::class, PortfolioEntity::class ], version = VERSION, exportSchema = false ) abstract class PortfolioDatabase : RoomDatabase() { abstract fun profileDao(): ProfileDao abstract fun portfolioDao(): PortfolioDao companion object { const val VERSION = 1 private const val TABLE_NAME = "portfolio_db" @Volatile private var instance: PortfolioDatabase? = null fun getInstance(context: Context): PortfolioDatabase { return instance ?: synchronized(this) { instance ?: buildDatabase(context).also { instance = it } } } private fun buildDatabase(context: Context): PortfolioDatabase { return Room.databaseBuilder(context, PortfolioDatabase::class.java, TABLE_NAME) .build() } } }
@Database ์ด๋ ธํ ์ด์ ๊ณผ ํจ๊ป ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฒด์ ์ธ ์์ ์ ์ญํ ์ ํ๊ณ ์์ผ๋ฉฐ ์์์ ์์ฑํ Entity, DAO ํด๋์ค๋ฅผ ํตํฉ์ ์ผ๋ก ๋ฌถ์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์์ฑํ๊ฑฐ๋ ๋ฒ์ ๊ด๋ฆฌ๋ฅผ ๋ด๋นํฉ๋๋ค.
Dagger DI ( ์์กด์ฑ ์ฃผ์ ์ ๋ํ ์ค๋ช ์ ์๋ตํ๊ฒ ์ต๋๋ค :( )
DatabaseModule
@Module class DatabaseModule { @Singleton @Provides fun providesPortfolioDatabase(context: Context): PortfolioDatabase = PortfolioDatabase.getInstance(context) @Singleton @Provides fun providesPortfolioDao(db: PortfolioDatabase): PortfolioDao = db.portfolioDao() }
@Singleton ์ด๋ ธํ ์ด์ ์ ํตํด, ์์ฑ์๊ฐ ์ฌ๋ฌ ์ฐจ๋ก ํธ์ถ๋๋๋ผ๋ ์ค์ ๋ก ์์ฑ๋๋ ๊ฐ์ฒด๋ ํ๋์ด๊ณ , ์ต์ด ์์ฑ ์ดํ์ ํธ์ถ๋ ์์ฑ์๋ ์ต์ด์ ์์ฑ์๊ฐ ์์ฑํ ๊ฐ์ฒด๋ฅผ ๋ฆฌํดํ๋๋ก ํ์์ต๋๋ค.
PortfolioRepository
interface PortfolioRepository { suspend fun insertPortfolio(portfolio: Portfolio) suspend fun getPortfolio(): List<Portfolio> suspend fun getPortfolioInfo(id: Long): Portfolio }
PortfolioRepositoryImpl
class PortfolioRepositoryImpl( private val portfolioDao: PortfolioDao ) : PortfolioRepository { override suspend fun insertPortfolio(portfolio: Portfolio) = portfolioDao.insertPortfolio(portfolio.mapPortfolioEntity()) override suspend fun getPortfolio(): List<Portfolio> = portfolioDao.getPortfolio().mapPortfolioList() override suspend fun getPortfolioInfo(id: Long): Portfolio = portfolioDao.getPortfolioInfo(id).mapPortfolio() }
PortfolioMapper
fun Portfolio.mapPortfolioEntity(): PortfolioEntity = PortfolioEntity( id, thumbnail, image, project, introduction, programming, architecture, library, server, term, link ) fun PortfolioEntity.mapPortfolio(): Portfolio = Portfolio( id, thumbnail, image, project, introduction, programming, architecture, library, server, term, link ) fun List<PortfolioEntity>.mapPortfolioList(): List<Portfolio> = map { it.mapPortfolio() }
๊ฐ์ ธ์จ Data๋ฅผ Mappingํ๋ ๋งค์๋์ ๋๋ค.
SampleData
val INIT_PORTFOLIO: List<Portfolio> = listOf( Portfolio( .. ), Portfolio( .. ), .. )
HomeViewModel
class HomeViewModel @Inject constructor( private val portfolioRepository: PortfolioRepository ) : ViewModel() { private val portfolio = MediatorLiveData<List<Portfolio>>() private val _homePortfolioUiModel = MediatorLiveData<List<HomePortfolioUiModel>>() val homePortfolioUiModel: LiveData<List<HomePortfolioUiModel>> get() = _homePortfolioUiModel init { _homePortfolioUiModel.addSource(portfolio) { _homePortfolioUiModel.value = it.mapToHomePortfolioUiModel() } } private fun insertPortfolio() { viewModelScope.launch { INIT_PORTFOLIO.iterator().forEach { portfolioRepository.insertPortfolio(it) } } portfolio.value = INIT_PORTFOLIO } private fun getPortfolio() { viewModelScope.launch { portfolio.value = portfolioRepository.getPortfolio() } } }
์ด๋ฒ ํฌ์คํ ์ ํตํด SQLite Database์ ๋นํด Room Database๊ฐ ์ฑ๋ฅ๋ฉด์์๋ ๋ฐ์ด๋๊ณ ํธ๋ฆฌํ๊ณ ์ง๊ด์ ์ธ ์ฝ๋๋ฅผ ์์ฑํ ์ ์๋ค๋ ๊ฒ์ ์ฒด๊ฐํ ์ ์์์ต๋๋ค :)
๊ทธ๋ผ ๐
Reference
๐'Android' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Custom View (0) 2023.02.24 Dagger - Raw Dagger (0) 2023.02.24 Android UI Test (0) 2023.02.14 Replace LiveData, SingleLiveEvent with Coroutines! (0) 2023.02.14 View Binding: What is it? (0) 2023.02.14