Room은 기본 유형과 박싱된 유형 간 변환을 위한 기능을 제공하지만 항목 간 객체 참조는 허용하지 않습니다. 본 문서에서는 유형 변환기를 사용하는 방법 및 Room이 객체 참조를 지원하지 않는 이유를 설명합니다.
유형 변환기 사용
앱에서는 맞춤 데이터 유형을 단일 데이터베이스 열에 저장해야 하는 때도 있습니다. 맞춤 유형을 Room에서 유지할 수 있는 알려진 유형으로 상호 변환하는 방법을 Room에 알려 주는 메서드인 유형 변환기를 제공하여 맞춤 유형을 지원합니다. 유형 변환기는 @TypeConverter 주석을 사용하여 식별합니다.
Room 데이터베이스에 Date 인스턴스를 유지해야 한다고 가정해 보겠습니다. Room에서는 Date 객체를 유지하는 방법을 알 수 없으므로 유형 변환기를 정의해야 합니다.
이 예에서는 두 가지 유형 변환기 메서드를 정의합니다. 하나는 Date 객체를 Long 객체로 변환하는 메서드이고 다른 하나는 Long에서 Date로 역변환을 실행하는 메서드입니다. Room은 Long 객체를 유지하는 방법을 알고 있으므로 이러한 변환기를 사용하여 Date 객체를 유지할 수 있습니다.
이러한 유형 변환기를 정의하면 프리미티브 유형을 사용하는 것처럼 항목과 DAO에 맞춤 유형을 사용할 수 있습니다.
Kotlin
@EntitydataclassUser(privatevalbirthday:Date?)@DaointerfaceUserDao{@Query("SELECT * FROM user WHERE birthday = :targetDate")funfindUsersBornOnDate(targetDate:Date):List<User>}
자바
@EntitypublicclassUser{privateDatebirthday;}@DaopublicinterfaceUserDao{@Query("SELECT * FROM user WHERE birthday = :targetDate")List<User>findUsersBornOnDate(DatetargetDate);}
이 예에서 Room은 정의된 유형 변환기를 어디서든 사용할 수 있습니다. AppDatabase에 @TypeConverters 주석을 달았기 때문입니다. 그러나 @Entity 또는 @Dao 클래스에 @TypeConverters 주석을 달아 유형 변환기의 범위를 특정 항목이나 DAO로 지정할 수도 있습니다.
유형 변환기 초기화 제어
Room은 일반적으로 유형 변환기의 인스턴스화를 자동으로 처리합니다. 그러나 때에 따라 추가 종속 항목을 유형 변환기 클래스에 전달해야 할 수도 있습니다. 즉, 앱에서 유형 변환기의 초기화를 직접 제어해야 합니다. 이 경우 변환기 클래스에 @ProvidedTypeConverter 주석을 답니다.
주요 요점: Room은 항목 클래스 간의 객체 참조를 허용하지 않습니다. 대신 앱에 필요한 데이터를 명시적으로 요청해야 합니다.
데이터베이스에서 각 객체 모델로 관계를 매핑하는 것은 일반적인 관행이며 이러한 매핑은 서버 측에서 매우 잘 작동합니다. 필드가 액세스될 때 프로그램이 필드를 로드하는 경우에도 서버는 여전히 잘 작동합니다.
그러나 클라이언트 측에서는 이 유형의 지연 로드가 일반적으로 UI 스레드에서 발생하기 때문에 실행 가능하지 않으며 UI 스레드에서 디스크에 관한 정보를 쿼리하면 상당한 성능 문제가 발생합니다. 일반적으로 UI 스레드는 활동의 업데이트된 레이아웃을 계산하고 그리는 데 약 16ms를 소요하므로 쿼리가 5ms밖에 걸리지 않은 경우에도 앱에서 프레임을 그리는 데 여전히 시간이 부족할 가능성이 크며 이에 따라 분명한 시각적 결함이 발생할 수 있습니다. 병렬로 실행 중인 별도의 트랜잭션이 있거나 기기가 다른 디스크 집약적인 작업을 실행 중이면 쿼리가 완료되는 데 훨씬 많은 시간이 걸릴 수 있습니다. 그러나 지연 로드를 사용하지 않으면 앱이 필요한 것보다 더 많은 데이터를 가져오며 이에 따라 메모리 소비 문제가 발생합니다.
객체 관계형 매핑은 일반적으로 개발자가 앱 사용 사례에 가장 적합한 모든 것을 할 수 있도록 이 결정을 개발자에게 맡깁니다. 개발자는 일반적으로 앱과 UI 간에 모델을 공유하려고 합니다. 그러나 이 방법은 확장성이 좋지 않습니다. 시간이 지남에 따라 UI가 변경되므로 공유된 모델이 개발자가 예측 및 디버그하기 어려운 문제를 일으키기 때문입니다.
예를 들어 각 도서에 Author 객체가 있는 Book 객체 목록을 로드하는 UI를 생각해 보세요. 처음에는 지연 로드를 사용하여 Book 인스턴스가 저자를 검색하도록 하는 쿼리를 디자인할 수 있습니다. author 필드의 첫 번째 검색은 데이터베이스를 쿼리합니다. 그리고 얼마 후에 앱 UI에도 저자 이름을 표시해야 한다는 사실을 알게 되었습니다. 다음 코드 스니펫에서와 같이 이 이름에 매우 쉽게 액세스할 수 있습니다.
그러나 외견상 무해한 이 변경사항으로 인해 기본 스레드에서 Author 테이블이 쿼리됩니다.
저자 정보를 미리 쿼리하면 데이터가 더 이상 필요 없는 경우에 데이터가 로드되는 방식을 변경하기가 어려워집니다. 예를 들어 앱의 UI가 더 이상 Author 정보를 표시하지 않아도 되는 경우에도 앱은 더 이상 표시하지 않는 데이터를 사실상 로드하여 소중한 메모리 공간을 낭비합니다. 앱의 효율성은 Author 클래스가 Books와 같은 다른 테이블을 참조하면 훨씬 더 저하됩니다.
Room을 사용하여 여러 항목을 동시에 참조하려면 각 항목이 포함된 POJO를 생성한 후 테이블을 조인하는 쿼리를 작성하세요. Room의 강력한 쿼리 유효성 검사 기능과 결합되어 제대로 구조화된 이 모델을 사용하면 앱이 데이터를 로드할 때 더 적은 리소스를 소비하므로 앱 성능 및 사용자 환경을 향상할 수 있습니다.
이 페이지에 나와 있는 콘텐츠와 코드 샘플에는 콘텐츠 라이선스에서 설명하는 라이선스가 적용됩니다. 자바 및 OpenJDK는 Oracle 및 Oracle 계열사의 상표 또는 등록 상표입니다.
최종 업데이트: 2025-07-27(UTC)
[[["이해하기 쉬움","easyToUnderstand","thumb-up"],["문제가 해결됨","solvedMyProblem","thumb-up"],["기타","otherUp","thumb-up"]],[["필요한 정보가 없음","missingTheInformationINeed","thumb-down"],["너무 복잡함/단계 수가 너무 많음","tooComplicatedTooManySteps","thumb-down"],["오래됨","outOfDate","thumb-down"],["번역 문제","translationIssue","thumb-down"],["샘플/코드 문제","samplesCodeIssue","thumb-down"],["기타","otherDown","thumb-down"]],["최종 업데이트: 2025-07-27(UTC)"],[],[],null,["# Referencing complex data using Room\n\nRoom provides functionality for converting between primitive and boxed types\nbut doesn't allow for object references between entities. This document\nexplains how to use type converters and why Room doesn't support object\nreferences.\n\nUse type converters\n-------------------\n\nSometimes, you need your app to store a custom data type in a single database\ncolumn. You support custom types by providing *type converters* , which are\nmethods that tell Room how to convert custom types to and from known types that\nRoom can persist. You identify type converters by using the\n[`@TypeConverter`](/reference/androidx/room/TypeConverter) annotation.\n| **Note:** Room 2.3 and higher includes a default type converter for persisting enums. Existing type converters take precedence over the default, but if you have not already defined a type converter for enums then you don't need to define one.\n\nSuppose you need to persist instances of [`Date`](/reference/java/util/Date) in\nyour Room database. Room doesn't know how to persist `Date` objects, so you need\nto define type converters: \n\n### Kotlin\n\n```kotlin\nclass Converters {\n @TypeConverter\n fun fromTimestamp(value: Long?): Date? {\n return value?.let { Date(it) }\n }\n\n @TypeConverter\n fun dateToTimestamp(date: Date?): Long? {\n return date?.time?.toLong()\n }\n}\n```\n\n### Java\n\n```java\npublic class Converters {\n @TypeConverter\n public static Date fromTimestamp(Long value) {\n return value == null ? null : new Date(value);\n }\n\n @TypeConverter\n public static Long dateToTimestamp(Date date) {\n return date == null ? null : date.getTime();\n }\n}\n```\n\nThis example defines two type converter methods: one that converts a `Date`\nobject to a `Long` object, and one that performs the inverse conversion from\n`Long` to `Date`. Because Room knows how to persist `Long` objects, it can use\nthese converters to persist `Date` objects.\n\nNext, you add the [`@TypeConverters`](/reference/androidx/room/TypeConverters)\nannotation to the `AppDatabase` class so that Room knows about the converter\nclass that you have defined: \n\n### Kotlin\n\n```kotlin\n@Database(entities = [User::class], version = 1)\n@TypeConverters(Converters::class)\nabstract class AppDatabase : RoomDatabase() {\n abstract fun userDao(): UserDao\n}\n```\n\n### Java\n\n```java\n@Database(entities = {User.class}, version = 1)\n@TypeConverters({Converters.class})\npublic abstract class AppDatabase extends RoomDatabase {\n public abstract UserDao userDao();\n}\n```\n\nWith these type converters defined, you can use your custom type in your\nentities and DAOs just as you would use primitive types: \n\n### Kotlin\n\n```kotlin\n@Entity\ndata class User(private val birthday: Date?)\n\n@Dao\ninterface UserDao {\n @Query(\"SELECT * FROM user WHERE birthday = :targetDate\")\n fun findUsersBornOnDate(targetDate: Date): List\u003cUser\u003e\n}\n```\n\n### Java\n\n```java\n@Entity\npublic class User {\n private Date birthday;\n}\n\n@Dao\npublic interface UserDao {\n @Query(\"SELECT * FROM user WHERE birthday = :targetDate\")\n List\u003cUser\u003e findUsersBornOnDate(Date targetDate);\n}\n```\n\nIn this example, Room can use the defined type converter everywhere because you\nannotated `AppDatabase` with `@TypeConverters`. However, you can also scope type\nconverters to specific entities or DAOs by annotating your `@Entity` or `@Dao`\nclasses with `@TypeConverters`.\n\n### Control type converter initialization\n\nOrdinarily, Room handles instantiation of type converters for you. However,\nsometimes you might need to pass additional dependencies to your type converter\nclasses, which means that you need your app to directly control initialization\nof your type converters. In that case, annotate your converter class with\n[`@ProvidedTypeConverter`](/reference/androidx/room/ProvidedTypeConverter): \n\n### Kotlin\n\n```kotlin\n@ProvidedTypeConverter\nclass ExampleConverter {\n @TypeConverter\n fun StringToExample(string: String?): ExampleType? {\n ...\n }\n\n @TypeConverter\n fun ExampleToString(example: ExampleType?): String? {\n ...\n }\n}\n```\n\n### Java\n\n```java\n@ProvidedTypeConverter\npublic class ExampleConverter {\n @TypeConverter\n public Example StringToExample(String string) {\n ...\n }\n\n @TypeConverter\n public String ExampleToString(Example example) {\n ...\n }\n}\n```\n\nThen, in addition to declaring your converter class in `@TypeConverters`, use\nthe\n[`RoomDatabase.Builder.addTypeConverter()`](/reference/androidx/room/RoomDatabase.Builder#addTypeConverter(java.lang.Object))\nmethod to pass an instance of your converter class to the `RoomDatabase`\nbuilder: \n\n### Kotlin\n\n```kotlin\nval db = Room.databaseBuilder(...)\n .addTypeConverter(exampleConverterInstance)\n .build()\n```\n\n### Java\n\n```java\nAppDatabase db = Room.databaseBuilder(...)\n .addTypeConverter(exampleConverterInstance)\n .build();\n```\n\nUnderstand why Room doesn't allow object references\n---------------------------------------------------\n\n**Key takeaway:**\nRoom disallows object references between entity classes. Instead, you must\nexplicitly request the data that your app needs.\n\nMapping relationships from a database to the respective object model is a common\npractice and works very well on the server side. Even when the program loads\nfields as they're accessed, the server still performs well.\n\nHowever, on the client side, this type of lazy loading isn't feasible because\nit usually occurs on the UI thread, and querying information on disk in the UI\nthread creates significant performance problems. The UI thread typically has\nabout 16 ms to calculate and draw an activity's updated layout, so even if a\nquery takes only 5 ms, it's still likely that your app will run out of time to\ndraw the frame, causing noticeable visual glitches. The query could take even\nmore time to complete if there's a separate transaction running in parallel, or\nif the device is running other disk-intensive tasks. If you don't use lazy\nloading, however, your app fetches more data than it needs, creating memory\nconsumption problems.\n\nObject-relational mappings usually leave this decision to developers so that\nthey can do whatever is best for their app's use cases. Developers usually\ndecide to share the model between their app and the UI. This solution doesn't\nscale well, however, because as the UI changes over time, the shared model\ncreates problems that are difficult for developers to anticipate and debug.\n\nFor example, consider a UI that loads a list of `Book` objects, with each book\nhaving an `Author` object. You might initially design your queries to use lazy\nloading to have instances of `Book` retrieve the author. The first retrieval of\nthe `author` field queries the database. Some time later, you realize that you\nneed to display the author name in your app's UI, as well. You can access this\nname easily enough, as shown in the following code snippet: \n\n### Kotlin\n\n```kotlin\nauthorNameTextView.text = book.author.name\n```\n\n### Java\n\n```java\nauthorNameTextView.setText(book.getAuthor().getName());\n```\n\nHowever, this seemingly innocent change causes the `Author` table to be queried\non the main thread.\n\nIf you query author information ahead of time, it becomes difficult to change\nhow data is loaded if you no longer need that data. For example, if your app's\nUI no longer needs to display `Author` information, your app effectively loads\ndata that it no longer displays, wasting valuable memory space. Your app's\nefficiency degrades even further if the `Author` class references another table,\nsuch as `Books`.\n\nTo reference multiple entities at the same time using Room, you instead create a\nPOJO that contains each entity, then write a query that joins the corresponding\ntables. This well-structured model, combined with Room's robust query\nvalidation capabilities, allows your app to consume fewer resources when loading\ndata, improving your app's performance and user experience."]]