Room DB
안드로이드 용 ORM
- ORM Library
- 의존성 추가
// build.gradle.kts (project)
plugins {
id("com.google.devtools.ksp") version "2.0.21-1.0.27" apply false}
// build.gradle.kts (:app)
val nav_version = "2.9.3"
dependencies {
// Room 런타임
implementation("anddroidx.room:room-runtime:${nav_version}")
// Room 컴파일러
ksp("androidx.room:room-compiler:${nav_version}")
// Kotlin 확장/코루틴 지
implementation("androidx.room:room-ktx:${nav_version}")
}
- java style
userDefiendDatabase::class.java: java style로의 변환이 필요할 수 있음
- SQLite 이용
- 인터페이스와 어노테이션 기반으로 추상화
- 직접 SQL 작성 X
- DAO(Data Access Object), Entity, Database 구성하여 사용
ORM(Object-Relational Mapping)
- 객체(Class)와 관계형 데이터베이스의 데이터(Table)를 매핑, 반환하는 기술
- 코드 → database 컨트롤

장점
- 직관적, 가독성 좋음
- SQL 오류 방지(컴파일 검증)
- jetpack compose(StateFlow/LiveData/Flow)와 연동 가능
- Migration 처리 간편
- Kotlin Coroutine과 통합 용이
- 기타
- 객체지향적 접근 → 생산성 증가
- 재사용 및 유지보수 용이성 증가
- DBMS에 대한 종속성 감소
단점
- 잘못 구현된 경우 속도 저하
- 복잡한 Query는 Query DSL 등을 이용해 처리 필요
구성 요소
| 구성 요소 | 설명 |
|---|---|
| Entity | 테이블을 정의하는 데이터 클래스(SQLite 테이블과 대응) |
| DAO(Data Access Object) | DB 접근 메서드를 정의한 인터페이스(SQL 쿼리 추상화) |
| Database | RoomDatabase를 상속받아 DB 객체 생성 및 DAO 제공 |
Entity
tableName: table 이름 지정(defaut: ClassName)
| 속성 (Annotation) | 설명 |
|---|---|
tableName |
테이블 이름을 지정(default: ClassName) |
indices |
인텍스를 설정, 성능 향상 또는 중복 방지를 위해 사용. |
ignredColums |
DB에 저장하지 않을 필드. 이름을 명시(@ignore와 유사한 역할) |
PrimaryKeys |
primary key 설정 |
foreignKeys |
foreign key 설정 |
@Entity(
tableName = "user"
indices = [Index(value = ["email"], unique = true)],
primaryKeys = ["uid"],
foreignKeys = [ForeignKey(...)],
ignoredColumns = ["tempData"]
)
data class User(
val id: Int
val fistName: String
val email
@ignore val tempData: String = ""
)
- 필드 어노테이션
- property 지정
@Entity: SQLite 테이블에 매핑되는 데이터 클래스에 붙임@ignore: DB column으로 사용 X
| 속성 (Annotation) | 설명 | 예시 |
|---|---|---|
@Entity |
해당 클래스가 데이터베이스 테이블임을 선언합니다. 기본적으로 클래스 이름을 테이블 이름으로 사용하며, tableName 속성을 통해 변경할 수 있습니다. |
@Entity(tableName = "users") |
@PrimaryKey |
테이블의 기본 키(Primary Key)를 정의합니다. 해당 열은 각 행을 고유하게 식별하며, autoGenerate = true 속성을 통해 기본 키 값을 자동으로 생성하도록 할 수 있습니다. |
@PrimaryKey(autoGenerate = true) val id: Int |
@ColumnInfo |
필드 이름을 테이블의 컬럼 이름과 다르게 지정하거나, 컬럼의 다른 속성을 정의할 때 사용합니다. | @ColumnInfo(name = "user_name") val name: String |
@Embedded |
복합 객체를 내부 컬럼으로 분해 | |
@NotNull |
not null | |
@Ignore |
해당 필드를 ROOM이 무시하도록 설정 | |
tableName |
테이블 이름을 지정(default: ClassName) | |
autoGenerate |
@PrimaryKey 속성과 함께 사용되어 기본 키 값을 자동으로 생성하도록 합니다. |
@PrimaryKey(autoGenerate = true) val id: Int |
name |
컬럼 이름 지정(default: 필드 이름) | |
typeAffinity |
컬럼의 데이터 타입을 지정 | |
| Class의 변수 type이 아닌 SQL의 type을 의미 | ||
index |
컬럼에 인덱스를 생성할지 여부를 지정 | |
collate |
컬럼의 정렬 방식을 지정 |
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
// @PrimaryKey(autoGenerate = true, val id: Int = 0)
@ColumnInfo(name = "first_name")
val firstName: String?,
@ColumnInfo(name = "last_name")
val lastName: String? = "",
)
DAO(Data Access Object)
- Database CRUID 실행
- interface지만 상속 및 구현 존재X
- debug 어려움
- compile 탐지, info 등 거의 없음
@Dao: SQL 쿼리를 추상화한 인터페이스 또는 추상 클래스의 어노테이션- 일반적으로 coroutine과의 연동을 위해
suspend속성을 사용 - SELECT 지원X
@Query: SELECT query 작성:property: 동적 쿼리 매개변수(?) 지정
// @Dao 어노테이션은 이 인터페이스가 데이터 접근 객체임을 나타냅니다.
@Dao
interface UserDao {
// @Query 어노테이션은 SQL 쿼리를 정의합니다. 모든 사용자를 가져옵니다.
@Query("SELECT * FROM user")
fun getAllUsers(): List<User> // User 객체의 리스트를 반환합니다.
// 특정 Query 작성
@Query("SELECT * FROM user WHERE id = :id") // 동적 쿼리 매개변수
fun getAllUsers(id: Int): Flow<List<User>> // User 객체의 리스트 조건절 사용.
// @Insert 어노테이션은 User 객체를 데이터베이스에 삽입합니다.
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(user: User)
// @Update 어노테이션은 기존 User 객체를 업데이트합니다.
@Update
fun updateUser(user: User)
// @Delete 어노테이션은 특정 User 객체를 삭제합니다.
@Delete
fun deleteUser(user: User)
// 특정 ID로 사용자를 찾는 쿼리입니다.
@Query("SELECT * FROM user WHERE uid = :userId")
fun getUserById(userId: Int): User? // User 객체 또는 null을 반환합니다.
}
- 주요 메서드 어노테이션
| 어노테이션 | 설명 |
|---|---|
@Insert |
삽입, 하나 혹은 여려개의 Entity 삽입 |
@Update |
|
@Delete |
|
@Query |
|
onConflict |
CID 공통 속성, 충돌 발생 시 전략 설정(REPLACE, IGNORE, ABORT 등) |
suspend
- suspend
- UI 스레드(Main Thred)에서 긴 작업을 하면 ANR 발생
- ROOM DB의 쿼리도 I/O 작업 → UI 스레드에서 작동 시 앱이 멈출 수 있음
lifecycleScope.launch {
val users = userDao.getAllUsers() // suspend 함수 호출
...
}
Database
@Database: ROOM DB 전체를 나타내는 추상 클래스에 붙음RoomDatabase를 상속
Todo::class주입 장식- 코틀린의 Kclass 타입을 의미, java의 Todo.class와 같은 개념
- Room이 컴파일 타입에 엔티티의 메타데이터을 읽어 SQLite 테이블로 매핑
- 이 데이터베이스에 포함될 엔티티 클래스를 Room에 알려주는 역할
@Database(
entities = [User::class, Book::class],
version = 1,
exporSchema = true,
autoMigrations = [AutoMigration(from =1, to 2)]
)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
- 추상 클래스로 선언하는 이유
- DAO 구현체를 Room이 컴파일 타임에 자동 생성해 연결하기 위해
- Room이 내부적으로 DAO 구현체를 생성
- Room compiler(Annotation Processor)가 생성
abstract fun daoMethod(): DaoClass: DAO 반환 메서드를 추상메서드로 선언- DB 인스턴스 관리(싱글톤, 생명주기, 스레드 안전성)를 Room이 대신하기 위해
- Room이 실제 DB 인스턴스를 생성할 때 프록시(proxy)역할
- 프록시 객체가 실제 SQLite 쿼리 실행, 커넥션 관리, 트랙션 등을 실행
TodoRoomDB.getDatabase(context).todoDao(): ViewModel에서 호출 → DAO 구현체 생성- 공통 기능(RoomDatabase가 제공하는 기능)을 상속받아 사용하기 위해
- DB 연결 관리(SQLiteOpenHelper 대체)
- clearAllTables()
- 트랙잭션 지원
- migration 처리
- 일일히 SQLiteOpenHelper를 상속 받아 구현할 필요 없음
| 속성 | 설명 |
|---|---|
| entities | DB에 포함될 @Entity 클래스 목록을 지정. (테이블 역할) |
| version | 데이터베이스 스키마 버전. 변경 시 마이그레이션 필요. |
| exportSchema | 스키마를 JSON 파일로 내보낼지 여부 (기본 true). 보통 테스트/버전 관리 용도로 사용. |
| autoMigrations | Room 2.4+ 지원. 자동 마이그레이션 규칙을 정의 (@AutoMigration). |
| views | @DatabaseView로 정의된 뷰 클래스 목록을 지정. (쿼리 결과를 읽기 전용 엔티티처럼 다룸) |
@volatile- 멀티스레드 환경에서 변수의 가시설을 보장
- 여러 스레드가 동시에 INSTANCE를 읽거나 쓸 때, 캐시를 사용하지 않고 사용하도록 보장
- 즉 최신 정보를 가져오도록 보장
@Volatile private var INSTANCE: 멀티스레드에서 최신 참조 보장.
synchronized(this)- 블록 안의 코드가 한 번에 하나의 스레드만 실행되도록 보장
- 여러 스레드가 동시에 getInstance()를 호출해도 DB
getInstance(context): 싱글턴 생성 + 재사용entities→User클래스가 테이블로 사용됨.version→ 1 → 이후 컬럼 변경/추가 시2로 올리고 마이그레이션 필요.exportSchema→true면/schemas폴더에 JSON으로 스키마 저장됨.views/autoMigrations는 예제에서 생략했지만, 필요 시 추가 가능.
import androidx.room.Database
import androidx.room.RoomDatabase
// 1. Entity 정의
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val name: String,
val age: Int
)
// 2. DAO 정의
// AppDatabase.kt
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(
entities = [User::class],
version = 1,
exportSchema = true
)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getInstance(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"app_database"
)
// 필요 시 개발 단계에서만 사용 (스키마 변경 시 데이터 파괴적 재생성)
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
instance
}
}
}
}
C
Contents
