안드로이드 시스템·Compose 용어 사전
데이터@TypeConverter · @Relation · @Embedded

TypeConverter

Room이 모르는 타입을 저장 가능한 기본 타입으로 변환하는 함수 쌍.

Room이 모르는 타입을 저장 가능한 기본 타입으로 변환하는 함수 쌍.

class Converters {
    @TypeConverter fun fromInstant(v: Instant?): Long? = v?.toEpochMilli()
    @TypeConverter fun toInstant(v: Long?): Instant? = v?.let(Instant::ofEpochMilli)

    @TypeConverter fun fromTags(v: List<String>): String = v.joinToString(",")
    @TypeConverter fun toTags(v: String): List<String> =
        if (v.isBlank()) emptyList() else v.split(",")
}

@Database(entities = [...], version = 1)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase()

SQLite 가 아는 타입은 다섯 개뿐이다

  • NULL · INTEGER · REAL · TEXT · BLOB

그 외(Instant · Uri · enum · List) 는 전부 변환이 필요하다

리스트를 문자열로 넣는 것의 대가

"tag1,tag2,tag3" 로 넣으면

  • 태그로 검색·필터링·조인을 할 수 없다 (LIKE '%tag2%' 는 오탐이 난다)
  • 인덱스를 걸 수 없다
  • 구분자가 값에 들어가면 깨진다

정규화가 정답이다

  • users · tags · user_tags(교차 테이블)

"조회 조건이 되지 않는 부수 데이터" 일 때만 직렬화가 정당하다

@Embedded — 한 테이블에 펼친다

data class Address(val city: String, val street: String)

@Entity
data class UserEntity(
    @PrimaryKey val id: Long,
    val name: String,
    @Embedded val address: Address        // city · street 컬럼으로 펼쳐진다
)

@Relation — 여러 테이블을 묶어 읽는다

data class UserWithPosts(
    @Embedded val user: UserEntity,
    @Relation(parentColumn = "id", entityColumn = "userId")
    val posts: List<PostEntity>
)

@Transaction                              // ★ 필수 — 내부적으로 쿼리가 둘로 쪼개진다
@Query("SELECT * FROM users")
fun observeUsersWithPosts(): Flow<List<UserWithPosts>>

@Transaction 을 빠뜨리면

  • users 조회와 posts 조회 사이에 쓰기가 끼어들어 불일치한 결과가 나온다
  • Room 이 경고를 내지만 빌드는 통과한다 — 놓치기 쉽다

외래키

@Entity(foreignKeys = [ForeignKey(
    entity = UserEntity::class, parentColumns = ["id"],
    childColumns = ["userId"], onDelete = ForeignKey.CASCADE
)], indices = [Index("userId")])          // ★ 인덱스를 안 걸면 삭제가 느려진다
data class PostEntity(...)

면접 함정

  • "TypeConverter로 객체를 JSON으로 넣으면 편하다" → 그 필드로 쿼리할 수 없게 된다. 스키마 변경도 어려워진다.
  • "@Relation이 JOIN을 만든다" → 별도 쿼리를 실행해 메모리에서 합친다. 그래서 @Transaction이 필요하다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — Room — 로컬 DB·DAO·관계·마이그레이션