Room: 핵심 개념, Entity, DAO 및 데이터베이스 작업

저자: IT Sectr 게시일: 2026-05-04 읽는 시간: 8 분

Room은 Android에서 SQLite와 함께 작동하는 라이브러리로, Jetpack의 일부입니다. 원시 SQLite 위에 추상화 계층을 제공하여 테이블 생성, 쿼리 실행 및 Kotlin 및 Java 객체로의 데이터 변환을 자동화합니다. Android Developers에 따르면 Room은 빌드 시 SQL 쿼리를 컴파일하여 구문의 정확성과 Entity 및 테이블 간의 관계를 확인합니다.

핵심 사항

  • Room은 Android 앱에서 SQLite와 함께 작동하는 Jetpack ORM 라이브러리입니다.
  • Entity는 @Entity로 주석이 달린 클래스이며, 각 인스턴스는 테이블의 행에 해당합니다.
  • DAO는 SQL 쿼리를 위한 주석이 달린 메서드가 있는 데이터 액세스 객체입니다.
  • Database는 RoomDatabase를 확장하는 추상 클래스로, Entity와 DAO를 연결합니다.
  • 마이그레이션은 사용자 데이터 손실 없이 데이터베이스 스키마를 안전하게 변경하는 메커니즘입니다.

Room이란 무엇이며 왜 필요한가

Room은 Android Jetpack의 영속성 라이브러리로, SQLite에 대한 객체-관계 매핑을 제공합니다. Room은 원시 SQLite의 세 가지 주요 문제를 해결합니다: 테이블 생성을 위한 많은 양의 보일러플레이트 코드 작성, 컴파일 시 SQL 쿼리 검증 부족, Cursor를 객체로 수동 변환하는 문제입니다.

이 라이브러리는 주석 프로세서(kapt 또는 KSP)를 사용하여 빌드 시 추상 RoomDatabase 및 DAO 클래스의 구현을 생성합니다. 이는 SQL의 구문 오류와 유형 불일치가 애플리케이션 실행 전, 즉 Google Play에 게시한 후 런타임이 아니라 개발 중에 발견되도록 보장합니다.

Google I/O 2023에 따르면 Room은 로컬 데이터로 작업하는 Android 애플리케이션의 68%에서 사용됩니다. 이는 구식 SQLiteOpenHelper 및 ContentProvider 대신 Google이 모든 새 프로젝트에 권장하는 기기 내 데이터 저장소의 표준입니다.

서버의 로컬 데이터 캐싱, 오프라인 모드 또는 복잡한 SQL 쿼리를 실행할 수 있는 구조화된 사용자 데이터 저장이 필요한 프로젝트에 Room을 도입하세요.

Room은 Android Jetpack의 일부이며 로컬 데이터로 작업하는 모든 새 프로젝트에 Google이 공식적으로 권장합니다. Realm이나 ObjectBox와 달리 Room은 네이티브 SQLite를 사용하여 DB Browser에서 DataGrip까지 모든 타사 데이터베이스 도구와의 호환성을 보장합니다. 개발자는 애플리케이션의 .db 파일을 열고 직접 SQL 쿼리를 실행할 수 있어 개발 중 디버깅 및 데이터 분석이 간소화됩니다.

Room의 Entity 및 주석

Entity는 @Entity로 주석이 달린 데이터 클래스로, Room이 데이터베이스 테이블로 변환합니다. 클래스의 각 필드는 테이블의 열이 되고, 각 인스턴스는 행이 됩니다. Room은 리플렉션을 사용하여 필드에 액세스하므로 필수 식별자에는 @PrimaryKey 주석이 필요합니다.

주요 주석

@Entity 주석은 클래스가 테이블임을 Room에 알립니다. tableName 매개변수는 클래스 이름과 다른 경우 테이블 이름을 설정합니다. @PrimaryKey는 autoGenerate = true를 통한 자동 생성을 지원하여 기본 키를 정의합니다.

kotlin
@Entity(tableName = "users")
data class User(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,
    @ColumnInfo(name = "full_name")
    val name: String,
    @Ignore
    val tempData: String?
)

@ColumnInfo는 Kotlin 필드 이름과 다른 경우 테이블의 열 이름을 지정합니다. @Ignore는 필드를 테이블에서 제외합니다 — 데이터베이스에 저장되지 않습니다. @ForeignKey는 삭제 또는 업데이트 시 계단식 작업과 함께 테이블 간 관계의 외래 키를 설명합니다.

Room은 @Embedded 주석을 통해 중첩된 객체를 지원합니다. 중첩된 클래스의 필드는 이름 충돌을 피하기 위해 접두사와 함께 부모 테이블의 열로 확장됩니다. 예를 들어 User에 포함된 city 및 street 필드가 있는 Address 클래스는 users 테이블에 address_city 및 address_street 열을 생성하여 단순 값 객체에 대한 별도 테이블이 필요하지 않습니다.

유형 변환기

Room은 기본 유형과 해당 래퍼만 지원합니다. 목록, Date 또는 사용자 정의 유형을 저장하려면 @TypeConverter를 사용하세요 — 사용자 정의 유형과 SQLite 기본 유형 간의 변환을 위한 정적 메서드입니다(예: List와 JSON 문자열 간).

DAO 및 SQL 쿼리

DAO(데이터 액세스 객체)는 @Dao로 주석이 달린 인터페이스 또는 추상 클래스로, 데이터 액세스를 위한 메서드를 포함합니다. 각 메서드는 SQL 작업으로 주석이 달립니다: @Insert, @Update, @Delete 또는 명시적 SQL 쿼리가 있는 @Query.

컴파일 시간 검증이 있는 @Query

@Query 주석은 SQL 문자열을 받아 Room이 컴파일 시 구문의 정확성과 Entity 필드와의 열 이름 일치를 검증합니다. Room은 :paramName 구문을 통한 매개변수화된 쿼리를 지원합니다.

kotlin
@Dao
interface UserDao {
    @Query("SELECT * FROM users WHERE id = :userId")
    suspend fun getUserById(userId: Int): User?

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertUser(user: User)

    @Query("SELECT * FROM users ORDER BY name ASC")
    fun getAllUsers(): Flow<List<User>>
}

@Insert는 중복 레코드 삽입 시 충돌을 처리하기 위한 OnConflictStrategy 옵션을 지원합니다. 반환 유형으로서의 Flow는 테이블의 데이터 변경 시마다 반응형 UI 업데이트를 제공합니다 — 구독은 INSERT, UPDATE 또는 DELETE 시 자동으로 다시 시작됩니다.

복잡한 작업을 위한 @Transaction

@Transaction 주석은 단일 트랜잭션 블록 내에서 여러 작업의 원자적 실행을 보장합니다. Room은 실행 중 데이터베이스를 잠가 여러 스레드의 동시 액세스 시 경쟁 조건을 방지합니다.

Database 및 스키마 마이그레이션

RoomDatabase는 Entity와 DAO를 단일 데이터베이스 액세스 지점으로 결합하는 추상 클래스입니다. 스키마 버전과 Entity 클래스 목록을 사용하여 Room.databaseBuilder를 통해 생성됩니다. 데이터베이스 인스턴스는 여러 연결을 피하기 위해 lazy 위임자를 통해 싱글톤으로 생성해야 합니다.

마이그레이션

Room의 마이그레이션은 이전 스키마 버전에서 새 버전으로 전환하기 위한 SQL 스크립트를 설명하는 Migration 클래스입니다. 스키마 변경 시 마이그레이션이 제공되지 않으면 Room이 IllegalStateException을 throw합니다. 이는 애플리케이션 업데이트 시 우발적인 사용자 데이터 손실로부터 보호합니다.

kotlin
val migration_1_2 = object : Migration(1, 2) {
    override fun migrate(db: SupportSQLiteDatabase) {
        db.execSQL("ALTER TABLE users ADD COLUMN age INTEGER NOT NULL DEFAULT 0")
    }
}

val db = Room.databaseBuilder(
    getApplication(),
    AppDatabase::class.java,
    "app_database"
).addMigrations(migration_1_2)
 .build()

개발 시 fallbackToDestructiveMigration을 사용할 수 있습니다. 이는 버전 불일치 시 이전 데이터베이스를 삭제하고 새 데이터베이스를 생성합니다. 이 모드는 디버깅 전용입니다 — 프로덕션 릴리스에는 적절한 마이그레이션이 포함되어야 합니다.

데이터베이스 테스트를 위해 Room은 특별한 클래스 Room.inMemoryTestBuilder를 제공합니다. 이는 디스크에 저장하지 않고 메모리 내 데이터베이스를 생성합니다. 각 테스트 완료 후 데이터베이스가 자동으로 삭제되어 테스트 시나리오의 완전한 격리를 보장합니다. android-arch-core-testing 라이브러리와 결합하면 개발자가 수동으로 상태를 정리하지 않고도 데이터베이스 수명 주기를 관리하고 마이그레이션 정확성을 검증할 수 있습니다.

Room의 성능은 쿼리 구조와 인덱스에 직접적으로 의존합니다. 느린 쿼리 분석을 위해 Room은 enableQueryCallback 플래그를 제공하여 실행 시간과 함께 모든 SQL 쿼리를 기록합니다. 개발자는 이 로그를 사용하여 100밀리초 이상 걸리는 쿼리를 찾고 @Entity의 @Index 주석을 통해 복합 인덱스를 추가하거나 @Relation을 사용하여 하위 쿼리를 직접 JOIN 작업으로 다시 작성하여 최적화할 수 있습니다.

Room은 SQLCipher를 통한 데이터베이스 암호화도 지원합니다. net.zetetic:android-database-sqlcipher 라이브러리를 추가하고 표준 대신 SupportFactory를 사용하면 DAO 쿼리나 Entity 구조를 변경하지 않고 디스크의 모든 데이터에 대한 투명한 암호화가 제공됩니다. 이는 사용자의 개인 데이터를 처리하는 애플리케이션에 필요하며 GDPR 및 개인 데이터 보호에 관한 러시아 연방법 152-FZ를 준수합니다. 암호화 비밀번호는 루팅된 장치에서 도구를 통한 추출로부터 보호하기 위해 Android Keystore에 저장할 수 있습니다.

Kotlin Coroutines 및 Flow와 함께 사용하는 Room

Room은 버전 2.1부터 Kotlin Coroutines를 기본적으로 지원합니다. DAO 메서드는 메인 스레드를 차단하지 않고 백그라운드 스레드에서 쿼리를 실행하는 suspend 함수가 될 수 있습니다. Room은 디스패처를 자동으로 관리하며 읽기 및 쓰기 쿼리에 Dispatchers.IO를 사용합니다.

반응형 쿼리를 위해 Room은 Flow를 반환합니다 — 영향을 받는 테이블의 변경 시마다 새 값을 내보내는 콜드 데이터 스트림입니다. ViewModel은 stateIn 또는 collect를 통해 Flow를 구독하여 어댑터에 수동으로 알리지 않고 자동 UI 업데이트를 제공합니다.

Room은 SQLite에서 페이지별로 데이터를 로드하는 특별한 PagingSource 구현을 통해 Paging 3도 지원합니다. 이는 수천 개의 레코드가 있는 대규모 목록에 효율적입니다: Paging 3는 화면에 보이는 행만 로드하고 데이터베이스 변경 시 자동으로 업데이트합니다.

오프라인 액세스와 무한 스크롤이 있는 뉴스 피드, 작업 로그 또는 제품 목록을 표시하려면 Room과 함께 Paging 3를 사용하세요.

자주 묻는 질문

Room과 SQLiteOpenHelper의 차이점은 무엇인가요?

Room은 테이블 생성, Cursor-객체 변환 및 컴파일 시 SQL 검증을 자동화합니다. SQLiteOpenHelper는 수동으로 스키마를 작성하고 Cursor를 처리해야 하며 애플리케이션 실행 전 쿼리 검증이 없어 오류 위험이 증가합니다.

스키마를 변경할 때마다 마이그레이션을 작성해야 하나요?

네, Entity를 변경할 때(필드 추가/제거, 유형 변경), 마이그레이션이 필요합니다. 마이그레이션이 없으면 Room이 시작 시 IllegalStateException을 throw합니다. 개발 시 fallbackToDestructiveMigration을 활성화할 수 있지만 프로덕션 릴리스에는 올바른 마이그레이션 스크립트가 필요합니다.

Room은 테이블 간의 관계를 지원하나요?

Room은 계단식 작업을 위한 @ForeignKey와 중첩 객체를 위한 @Relation을 지원합니다. 복잡한 JOIN 쿼리의 경우 @Embedded 및 @Relation을 통해 중첩 엔티티가 있는 POJO를 반환하는 @Query와 함께 @Transaction 주석을 사용하세요.

Kotlin 없이 Java에서 Room을 사용할 수 있나요?

네, Room은 Java와 완전히 호환됩니다. suspend 함수 대신 LiveData 또는 RxJava Observable을 사용하고, Flow 대신 LiveData를 사용하세요. Java와 함께 사용하는 Room은 동일한 모든 주석을 지원하지만 비동기 작업에 더 많은 보일러플레이트 코드가 필요합니다.

Room 데이터베이스 암호화는 어떻게 작동하나요?

Room은 Zetetic의 SQLCipher를 통한 암호화를 지원합니다. Room.databaseBuilder 대신 net.zetetic:android-database-sqlcipher 라이브러리의 SupportFactory를 사용하여 암호화 비밀번호를 전달하세요. 디스크의 모든 데이터는 DAO 쿼리에 대해 투명하게 암호화됩니다.

요약

  • Room은 컴파일 시 SQL 검증이 있는 SQLite용 Jetpack ORM 라이브러리입니다.
  • @Entity는 테이블을 설명하고, @PrimaryKey는 식별자, @ColumnInfo는 열 이름입니다.
  • @Dao는 데이터 액세스를 위한 @Query, @Insert, @Update 및 @Delete가 있는 메서드를 포함합니다.
  • RoomDatabase는 Entity와 DAO를 결합하며, Room.databaseBuilder를 통해 생성됩니다.
  • 마이그레이션(Migration)은 데이터 손실 없이 스키마 변경을 위한 SQL 스크립트를 설명합니다.
  • Room은 반응형 UI 업데이트를 위해 Kotlin Coroutines(suspend) 및 Flow를 기본 지원합니다.
  • 대규모 목록의 경우 페이지별 로딩을 위해 PagingSource를 통해 Room과 함께 Paging 3를 사용하세요.

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기