안드로이드의 내부 DB 로 자주 사용되는 Room, Realm, SQLite 를 비교하고 Room 에 대해 좀 더 자세히 알아보겠습니다.
Room은 SQLite 에 대한 추상화 레이어를 제공하여 원활한 데이터베이스 액세스를 지원하며 ORM 방식입니다. SQLite 와 비교하여 Room 의 장점들은 다음과 같습니다.
그렇다면 현재 많이 사용되는 Room 과 Realm 을 비교해 보겠습니다. Realm 은 안정성이 좋고, 처리 속도가 빨라 대용량 데이터 처리에 좋습니다. 그러나, 내부 라이브러리가 커 용량을 많이 차지 합니다. 또한, SQL 쿼리를 사용했다면 러닝 커브가 있습니다.
큰 용량의 데이터 처리가 아닌 경우라면 Room 또한 좋습니다.
Room은 SQLite에 대한 추상화 레이어를 제공하여 원활한 데이터베이스 액세스를 지원하는 동시에 SQLite를 완벽히 활용합니다. 앱은 Database 를 사용하여 Dao 를 가져오고, 가져온 Dao 로 Entity 들을 Database 에 저장하고 변경합니다.
build.gradle 파일에 다음의 dependency 를 추가합니다.
def room_version = "2.3.0"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
@Entity
data class User (
@PrimaryKey val uId : Int,
@ColumnInfo(name = "first_name") val firstName: String?,
@ColumnInfo(name = "last_name") val lastName: String?,
@Ignore val isChecked: Boolean
)
@Entity(tableName = "테이블 이름")
테이블 이름을 지정할 수 있습니다. 기본값은 클래스명 입니다.
@PrimaryKey
PrimaryKey 를 지정합니다.
@ColumnInfo
칼럼 명을 지정합니다. 기본 값은 변수명입니다.
@Ignore
DB 에 저장하긴 싫지만 이 클래스 내에 있어야 하는 변수에 사용합니다.
@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun getAll() : List<User>
@Query("SELECT * FROM user WHERE uId IN (:userIds)")
fun getAllByIds(userIds: IntArray) : List<User>
@Insert
fun insertAll(vararg users: User)
@Delete
fun delete(user: User)
}
@Query
Query 문을 작성합니다. 쿼리에 매개변수를 전달하려면 :변수명
방식으로 사용합니다.
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
private var INSTANCE: AppDatabase? = null
fun getInstance(context: Context): AppDatabase? {
if (INSTANCE == null) {
synchronized(AppDatabase::class) {
INSTANCE = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"user.db"
).build()
}
}
return INSTANCE
}
}
}
Database 는 RoomDatabase() 를 상속받는 abstract 클래스여야 합니다. @Database
어노테이션에는 해당 database 와 관련된 Entity 의 목록을 나열해야 합니다. 또한, 클래스 내에는 arguments 가 0개인 @Dao 를 반환하는 abstract 메소드를 포함해야 합니다.
런타임 시 Room.databaseBuilder()
또는 Room.inMemoryDatabaseBuilder()
를 호출하여 Database 인스턴스를 가져올 수 있습니다. Database 객체를 Singleton 으로 생성합니다.
Room 에 Primitive type 이 아닌 값을 저장하기 위해서는 TypeConverter 를 사용하여 Room 에게 특정 타입을 데이터베이스에 저장하고, 가져오는 방법을 알려주어야 합니다.
TypeConverter 클래스를 생성하고, 생성한 클래스를 Database 클래스에 추가하여 사용합니다.
class UserTypeConverters {
@TypeConverter
fun fromDate(date: Date?): Long? {
return date?.time
}
@TypeConverter
fun toDate(millisSinceEpoch: Long?): Date? {
return millisSinceEpoch?.let {
Date(it)
}
}
@TypeConverter
fun toUUID(uuid: String?): UUID? {
return UUID.fromString(uuid)
}
@TypeConverter
fun fromUUID(uuid: UUID?): String? {
return uuid?.toString()
}
}
@Database(entities = [Crime::class], version = 1)
@TypeConverters(UserTypeConverters::class)
abstract class AppDatabase : RoomDatabase() {
}
DB 에 데이터를 처리하는 일은 시간이 많이 걸리 수 있기 때문에 메인스레드에서 처리할 수 없습니다. 그래서 백그라운드 스레드에서 처리를 해주어야 합니다. 비동기 처리의 방법에는 AsyncTask, Rxjava, coroutin 등 다양한 방법이 있습니다. 이번에는 코루틴을 사용해 보겠습니다.
val db = AppDatabase.getInstance(applicationContext)
lifecycleScope.launch(Dispatchers.IO) {
db!!.userDao().insertAll(User(0, "han", "hi", true))
}
슬기로운 개발생활[안드로이드 Room 사용법]
안드로이드 developers[Room을 사용하여 로컬 데이터베이스에 데이터 저장]