๐ง Room?
๋ก์ปฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ด์ฉํ๋ ๋ฐฉ๋ฒ ์ค ํ๋๋ ์๋๋ก์ด๋์์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณตํ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ธ SQLite๊ฐ ์๋ค.
ํ์ง๋ง ๊ตฌ๊ธ์์ 2017๋ AAC(Android Architecture Components)๋ฅผ ๋ด๋์์ผ๋ก SQLite๋ฅผ ์ง์ ์ฌ์ฉํ์ฌ ๊ตฌํํ๋ ๊ฒ๋ณด๋ค Room์ ์ฌ์ฉํ ๊ฒ์ ๊ถ์ฅํ๋ค. ๊ถ์ฅ ์ฌ์ ๋ ์๋ ์๋๋ก์ด๋ SQLite ๊ณต์ ๋ฌธ์ Caution ๋ด์ฉ์ ์ฐธ๊ณ ํ ์ ์์๋ค.
- SQL Query๋ฅผ ์ง์ ์์ฑํ์ด์ผ ํ๊ณ , ์ด๋ฅผ ์ปดํ์ผ ์์ ์์ ์ ํจ์ฑ ๊ฒ์ฆ์ด ๋ถ๊ฐํ๋ค๋ ์
- Database Scheme์ด ๋ณ๊ฒฝ๋ ๋ ์ผ์ผ์ด Query๋ฅผ ์ ๋ฐ์ดํธํด์ผํ๋ค๋ ์
- ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ฒด๋ก ๋ณํํ ๋ ๋ง์ boilerplate code๋ฅผ ์์ฑํด์ผํ๋ค๋ ์
Room์ SQLite์ ์ถ์ ๋ ์ด์ด๋ฅผ ์ ๊ณตํ์ฌ ๋ชจ๋ SQLite์ ๊ธฐ๋ฅ๋ค์ ์ด์ฉํ ์ ์์ผ๋ฉด์ ๋ ํธํ๊ฒ ์ ๊ทผํ ์ ์๊ฒ ํด์ค๋ค.
๋น์ฐํ๊ฒ ์์์ ๋งํ๋ ๊ฒ์ฒ๋ผ ๊ฐ์ฒด๋ก ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์ ์ ์๋๋ก ์ค๊ณ๋์ด ์์ผ๋ฉฐ, ์ง์ Query๋ฅผ ์์ฑํ์ง ์๊ณ ๋ ๊ธฐ๋ณธ์ ์ธ CRUD ์์ ์ ์ฝ๊ฒ ํ ์ ์๋ค.
๋ํ RxJava, LiveData, Flow์ ๊ฐ์ Observable ๊ฐ์ฒด๋ฅผ ๋ฐํํ ์ ์๋๋ก ์ง์ํ๋ฉฐ ํ ์คํ ๋ํ ํธ๋ฆฌํ๊ฒ ์ง์ํด์ค๋ค.
๐จ๐ฉ๐ง๐ฆ Room Component
๐ Entity
Entity๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ์ ์ํ๋ ๊ฒ์ด๋ค.
์ฆ, ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ ์ด๋ธ Column ์ ๋ณด์ด๋ค. ์ฌ๊ธฐ์ ์ ์๋ ๋ฐ์ดํฐ๋ค์ด ๋ชจ์ฌ ํ ์ด๋ธ์ ๊ตฌ์ฑํ๊ฒ ๋๋ค.
@Entity ์ด๋ ธํ ์ด์ ์ด ์ค์ ๋ Data class๋ก ์ ์ํ๋ฉด ๋๋ค.
@Entity(tableName = "CONTENT")
data class Todo(
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "t_id") val tId: Int = 0,
@ColumnInfo(name = "content") val content: String,
@ColumnInfo(name = "is_checked") var isChecked: Boolean = false,
@ColumnInfo(name = "created_at") val createdAt: Long = System.currentTimeMills()
)
@Entity ์ด๋ ธํ ์ด์ Parameter์ ํ ์ด๋ธ์ ๋ํด ์ฌ๋ฌ ์ค์ ์ ์ค ์ ์๋ค. ํด๋น ๋ด์ฉ์ ๊ณต์ ๋ฌธ์(@Entity)๋ฅผ ์ฐธ๊ณ ํ์.
์ ์ฝ๋์์๋ tableName์ ์ ์ํ์ผ๋ SQLite์์๋ ํ ์ด๋ธ ์ด๋ฆ์ ๋์๋ฌธ์ ๊ตฌ๋ณํ์ง ์์ผ๋ฉฐ, default ๊ฐ์ ๋ฐ์ดํฐ ํด๋์ค ์ด๋ฆ์ด ๋๋ฏ๋ก ํด๋์ค ์ด๋ฆ๊ณผ ๋์ผํ๋ค๋ฉด ์์ฑํ ํ์๊ฐ ์๋ค.
Room Entity๋ ๋ฐ๋์ 1๊ฐ ์ด์์ PrimaryKey๊ฐ ํ์ํ๋ค. ํด๋น Field์ @PrimaryKey ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ๋ฉด ๋๋ค.
PrimaryKey์ ์์ฑ์๋ autoGenerate๊ฐ ์๋ค. true๋ก ํ ๊ฒฝ์ฐ ์ ๋ํฌํ ๊ฐ์ ์๋์ผ๋ก ๋ง๋ค์ด์ค๋ค.
@Entity์ ์์ฑ์ Primary Keys๋ฅผ ์ด๋ ์ด ํํ๋ก๋ ์ ์ํ ์ ์์์ผ๋ก ํ์์ ๋ฌธ์๋ฅผ ํ์ธํด๋ณด์.
@ColumInfo ์ด๋ ธํ ์ด์ ์ Field ๊ฐ์ ์์ฑ์ ๋ช ์ํ ์ ์๋ค.
Kotlin์ ๋ค์ด๋ฐ ์ปจ๋ฒค์ ์ camelCase์ด์ง๋ง, ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๋ณดํต snake_case๋ฅผ ์ฌ์ฉํ๊ธฐ์ ์ด๋ฆ์ ๋ณ๊ฒฝํด์คฌ๋ค.
Default๋ field ์ด๋ฆ์ด ๋๋ค. ์ด์ธ์ ์์ฑ์ ๊ณต์ ๋ฌธ์(@ColumInfo)๋ฅผ ์ฐธ๊ณ ํ์.
๐ DAO(Data Access Object)
Room์์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ง์ ์ ๊ทผํ์ง ์๊ณ DAO๋ผ๋ ๊ฐ์ฒด๋ฅผ ์ ์ํ์ฌ ์ ๊ทผํด์ผํ๋ค.
DAO๋ ๋ฌด์กฐ๊ฑด Interface ๋๋ Abstract class๋ก ์ ์๋์ด์ผ ํ๋ค. ์ปดํ์ผ ์์ ์์ DAO ์ด๋ ธํ ์ด์ ์ ์ฐพ์ ๊ตฌํํ๊ธฐ ๋๋ฌธ์ด๋ค.
๋ํ, ์ด๋ฅผ ํตํด ์ฟผ๋ฆฌ ์ค๋ฅ๋ฅผ ์ปดํ์ผ ์์ ์์ ๋ฐ๊ฒฌํ ์ ์๋ค.
Entity์ ๋ํด ์๋์์ฑ์ ์ง์ํ๊ธฐ์ Query ์์ฑ์์ ์คํ๋ฅผ ์ค์ฌ์ฃผ๊ธฐ๋ ํ๋ค.
@Dao
interface TodoDao {
@Insert
fun insertTodoContent(vararg todo: Todo)
@Delete
fun deleteTodoContent(todo: Todo)
@Update
fun updateTodoCheckState(todo: Todo)
@Query("SELECT * FROM CONTENT ORDER BY created_at DESC")
fun getTodoContents(): List<Todo>
}
@Dao ์ด๋ ธํ ์ด์ ์ ์ด์ฉํ์ฌ Room์๊ฒ DAO class์์ ์๋ ค์ค๋ค.
@Insert ์ด๋ ธํ ์ด์ ์ ๋ฉ์๋์ ์ง์ ํ ๊ฒฝ์ฐ ๋ชจ๋ ๋งค๊ฐ๋ณ์๋ฅผ ์ฝ์ ํ๋ ๊ตฌํ์ ์๋์ผ๋ก ๋ง๋ค์ด์ค๋ค.
์ฌ๋ฌ ๋งค๊ฐ๋ณ์ ํ์ ์ ๊ฐ์ง ์ ์๋ค๋ ํน์ฑ์ผ๋ก ๋จ์ผ ๊ฐ์ฒด ๋๋ ๋ฐฐ์ด ํํ ๋ฑ ๋ชจ๋ ๋ฐ์ดํฐ ์ ๋ ฅ์ด ๊ฐ๋ฅํ๋ค.
์ ์ฝ๋์์๋ vararg ๊ฐ๋ณ ์ธ์๋ฅผ ํตํด ์ฌ๋ฌ ๊ฐ์ฒด๋ฅผ ๋ฐ๋๋ก ํ์๋ค.
Insert ์์ฑ์ผ๋ก ์ถฉ๋์ด ๋ฐ์ํ์ ๋ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ๊ฒ์ธ์ง์ ๋ํ ์ต์ ์ ์ค์ ํ ์ ์๋ค.
Option | Description |
ABORT (default) | ํธ๋์ญ์ ์ ๋กค๋ฐฑํ๋ค. |
REPLACE | ๊ธฐ์กด rows๋ฅผ ์๋ก์ด rows๋ก ๋์ฒดํ๋ค. |
IGNORE | ๊ธฐ์กด rows๋ฅผ ์งํจ๋ค.(๋ฌด์ํ๋ค) |
@Insert(OnConflict = OnConflictStrategy.IGNORE)
fun insertTodoContent(vararg todo: Todo)
์์ ๊ฐ์ด ์ฌ์ฉํ ์ ์์ผ๋ฉฐ ๋ ์์ธํ ๋ด์ฉ์ ๊ณต์ ๋ฌธ์(@Insert)๋ฅผ ์ฐธ๊ณ ํ์.
@Delete ์ด๋ ธํ ์ด์ ์ ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํ ๋ ์ฌ์ฉํ๋ค. PrimaryKey์ ๋งค์นญ๋๋ Entity๋ฅผ ์ฐพ์ ๊ฐ์ ์ญ์ ํ๋ค.
@Update ์ด๋ ธํ ์ด์ ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐฑ์ ํ ๋ ์ฌ์ฉํ๋ค. Delete์ ๋ง์ฐฌ๊ฐ์ง๋ก PrimaryKey์ ๋งค์นญ๋๋ ๊ฐ์ ์ฐพ๋๋ค.
@Query ์ด๋ ธํ ์ด์ ์ ์ง์ Query๋ฅผ ์์ฑํ์ฌ SQL์ ์๋์ํฌ ๋ ์ฌ์ฉํ๋ค.
๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ฌ ๋ ์ฃผ๋ก ์ฌ์ฉ๋๋ฉฐ R/W ์์ ์ ๋ชจ๋ ์คํํ ์ ์๋ค.
Parameter๋ฅผ ์ด์ฉํ์ฌ ์๋์ ๊ฐ์ด Query๋ฅผ ์์ฑํ ์๋ ์๋ค. parameter๊ฐ LIKE์ ๋ค์ ์ฝ๋ก ๊ณผ ํจ๊ป ์ฌ์ฉ๋๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
@Query("SELECT * FROM CONTENT WHERE content LIKE :content ORDER BY created_at DESC")
fun getTodoMatchingContents(content: String): List<Todo>
* Flow, LiveData, Rxjava2 ๋ฑ์ Observable ๊ฐ์ฒด๋ก๋ ๋ฐํ์ด ๊ฐ๋ฅํ๋ฉฐ, PagingSource(Paging 3) ๊ฐ์ฒด๋ ์ต๊ทผ ์ง์ํ๊ธฐ ์์ํ๋ค.
๐ Database
Database๋ ์ค์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ฐ๊ฒฐ์ ์ํ ์ก์ธ์ค ํฌ์ธํธ ์ญํ ์ ํ๋ค.
@Database(entities = [Todo::class], version = 1)
abstract class AppDatabase: RoomDatabase() {
abstract fun getTodoDao(): TodoDao
companion object {
@Volatile private var instance: AppDatabase? = null
fun getInstance(context: Context): AppDatabase {
return instance ?: synchronized(this) {
instance ?: buildDatabase(context).also { instance = it }
}
}
private fun buildDatabase(context: Context): AppDatabase {
return Room.databaseBuilder(context, AppDatabase::class.java, "todo.db")
//.allowMainThreadQueries()
.build()
}
}
}
Database ํด๋์ค๋ RoomDatabase๋ฅผ ์์๋ฐ๋ Abstract class์ด์ด์ผ ํ๋ค.
ํด๋์ค์๋ @Database ์ด๋ ธํ ์ด์ ์ด ํฌํจ๋์ด์ผ ํ๋ฉฐ ์์ฑ์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ฌ์ฉ๋ Entity๋ค์ ์ด๋ ์ด ํ์์ผ๋ก ๋ด๋๋ค.
๋ํ Database version์ ๊ด๋ฆฌํ๋ค. ๋ฐ์ดํฐ Scheme์ด ๋ณ๊ฒฝ๋์์ ๋ Migration๊ณผ ๊ฐ์ ์์ ์ ์์ํ ์ํํ๋ค.
๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ฐ์ฒด๋ฅผ singleton์ผ๋ก ์ ์ธํ๋ ๊ฒ์ด ๋ฐ๋์งํ๋ค. ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ๊ทผํ ๋๋ง๋ค ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ฒ ๋๋ฉด ์ด ์์ฒด์๋ ๋ง์ ๋น์ฉ์ด ๋ค์ด๊ฐ๋ฉฐ, ๊ตฌ๊ธ ๋ฌธ์์ ๋ฐ๋ฅด๋ฉด ๋จ์ผ ํ๋ก์ธ์ค ๋ด์์ ์ฌ๋ฌ ์ธ์คํด์ค์ ์ก์ธ์คํ ํ์๊ฐ ๊ฑฐ์ ์๋ค๊ณ ํ๋ค.
๋ํ ์๋ก ๋ค๋ฅธ ์ค๋ ๋์์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ํ ๋ฒ์ ์ ๊ทผํ๋ ค๋ ๊ฒ์ ๋ง๊ธฐ ์ํด synchronized๋ฅผ ํตํ์ฌ ๋๊ธฐํ๋ฅผ ์์ผ์ค ํ์๊ฐ ์๋ค.
Room.databaseBuilder() ๋๋ Room.inMemoryDatabaseBuilder()๋ก ์ธ์คํด์ค๋ฅผ ์ป์ ์ ์๋ค. ํ์๋ ํ๋ก์ธ์ค ์ข ๋ฃ ์์ ์ฌ๋ผ์ง๋ ๋ฉ๋ชจ๋ฆฌ ํํ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ด๋ค.
๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ ์ UI performance๊ฐ ์ ํ๋๋ ๊ฒ์ ๋ง๊ธฐ์ํด Main Thread์์์ ์๋์ ํ์ฉํ์ง ์๊ณ ์๋ค.
Coroutine, RxJava, Thread ๋ฑ ์ฌ๋ฌ๊ฐ์ง ๋ฐฉ๋ฒ์ด ์๊ฒ ๋ค.
Room ์ฟผ๋ฆฌ๋ฅผ Flow<T>๋ก ๋ฐํํ๊ฒ ๋ ๊ฒฝ์ฐ ์๋์ผ๋ก Background Thread์์ ์คํ๋๋ฏ๋ก ์ด๋ฅผ ์์๋ด๋ ์ข๋ค.
allowMainThreadQueries() ๋ฉ์๋๋ฅผ ํตํด์ Main Thread ์๋์ ๊ฐ๋ฅํ๊ฒ ํ ์ ์์ผ๋ ๊ถ์ฅํ์ง ์๋๋ค.
์ฃผ์์ผ๋ก ๋จ๊ฒจ๋ ์ด์ ๋ ์ ๋ฐฉ๋ฒ๋ค์ ํฌ์คํ
์์ ์ค๋ช
ํด์ค ์ ์๊ธฐ ๋๋ฌธ์ ๋ชจ๋ฅด๋๋ฐ ๋นจ๋ฆฌ ์ฌ์ฉํด์ผํ ์ฌ๋๋ค์ ์ํด ๋จ๊ฒจ๋๋ค.
๋ค์ ํ๋ฒ ๋งํ์ง๋ง Main Thread์์์ ์๋์ ๊ถ์ฅํ์ง ์๋๋ค.
AppDatabase().getInstace(context).getTodoDao().getTodoContents() ์ด๋ ๊ฒ ์ฌ์ฉํ๋ฉด๋๋ค ๊น์ง ์๋ ค์ค์ผ ํ ๊ฒ ๊ฐ๋ค.
๐ Dependency
ํฌ์คํ ์์ฑ ๋น์ ์ต์ Stable ๋ฒ์ ์ 2.3.0์ด๋ค. Release ํ์ด์ง์์ ์ต์ ๋ฒ์ ์ ํ์ธํ์.
plugins {
id 'kotlin-kapt'
}
dependencies {
def room_version = "2.3.0"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
}
๐ Reference & ํจ๊ปํ๋ฉด ์ข์ ๊ธ
- Reference
์๋๋ก์ด๋ ๊ณต์ ๋ฌธ์ - Room
Android Codelabs - Room with a View
- ํจ๊ปํ๋ฉด ์ข์ ๊ธ
No more LiveData in Your Repository: There are better options
AAC๋ฅผ ํฌํจํ Jetpack ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ํ์ฌ ์ง์์ ์ผ๋ก ํฌ์คํ ํ ์์ ์ด๋ค.
Github Repository๋ฅผ ํตํ์ฌ ์ํ ํ๋ก์ ํธ๋ฅผ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ณ๋ก ๊ด๋ฆฌ(branch)ํ ๊ฒ์ด๋ฏ๋ก ๊ด์ฌ์ด ์์ผ๋ฉด ๋ค๋ ค๋ ์ข๋ค. ๊นํ๋ธ ๋ฐ๋ก๊ฐ๊ธฐ
๋๊ธ