Room을 사용하여 로컬 데이터베이스에 데이터 저장

Room은 SQLite에 대한 추상화 레이어를 제공하여 원활한 데이터베이스 액세스를 지원하는 동시에 SQLite를 완벽히 활용합니다.

상당한 양의 구조화된 데이터를 처리하는 앱은 데이터를 로컬로 유지하여 대단한 이점을 얻을 수 있습니다. 가장 일반적인 사용 사례는 관련 데이터를 캐싱하는 것입니다. 이런 방식으로 기기가 네트워크에 액세스할 수 없을 때 오프라인 상태인 동안에도 사용자가 여전히 콘텐츠를 탐색할 수 있습니다. 나중에 기기가 다시 온라인 상태가 되면 사용자가 시작한 콘텐츠 변경사항이 서버에 동기화됩니다.

Room이 사용자를 대신하여 이러한 문제를 처리하므로 SQLite 대신 Room을 사용하는 것이 좋습니다. 그러나 SQLite API를 직접 사용하는 것을 선호하는 경우 SQLite를 사용하여 데이터 저장을 읽어보시기 바랍니다.

Room에는 다음과 같은 세 가지 주요 구성요소가 있습니다.

  • 데이터베이스: 데이터베이스 홀더를 포함하며 앱의 지속적인 관계형 데이터에 대한 기본 연결을 위한 기본 액세스 포인트 역할을 합니다.

    @Database로 주석 처리된 클래스는 다음 조건을 충족해야 합니다.

    • RoomDatabase를 확장하는 추상 클래스여야 합니다.
    • 주석 내에 데이터베이스와 연결된 항목의 목록을 포함해야 합니다.
    • 인수가 0개인 추상 메서드를 포함하고 @Dao로 주석 처리된 클래스를 반환해야 합니다.

    런타임 시 Room.databaseBuilder() 또는 Room.inMemoryDatabaseBuilder()를 호출하여 Database 인스턴스를 가져올 수 있습니다.

  • 항목: 데이터베이스 내의 테이블을 나타냅니다.

  • DAO: 데이터베이스에 액세스하는 데 사용되는 메서드가 포함되어 있습니다.

앱은 Room 데이터베이스를 사용하여 데이터베이스와 연결된 데이터 액세스 개체 또는 DAO를 가져옵니다. 그런 다음 앱은 각 DAO를 사용하여 데이터베이스에서 항목을 가져오고 항목의 변경사항을 다시 데이터베이스에 저장합니다. 마지막으로 앱은 항목을 사용하여 데이터베이스 내의 테이블 열에 해당하는 값을 가져오고 설정합니다.

Room의 다양한 구성요소 간의 이러한 관계는 그림 1에서 확인할 수 있습니다.

그림 1. Room 아키텍처 다이어그램

다음 코드 스니펫에는 하나의 항목과 하나의 DAO가 있는 데이터베이스 구성 샘플이 포함되어 있습니다.

User

Kotlin

    @Entity
    data class User(
        @PrimaryKey val uid: Int,
        @ColumnInfo(name = "first_name") val firstName: String?,
        @ColumnInfo(name = "last_name") val lastName: String?
    )
    

자바

    @Entity
    public class User {
        @PrimaryKey
        public int uid;

        @ColumnInfo(name = "first_name")
        public String firstName;

        @ColumnInfo(name = "last_name")
        public String lastName;
    }
    

UserDao

Kotlin

    @Dao
    interface UserDao {
        @Query("SELECT * FROM user")
        fun getAll(): List<User>

        @Query("SELECT * FROM user WHERE uid IN (:userIds)")
        fun loadAllByIds(userIds: IntArray): List<User>

        @Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
               "last_name LIKE :last LIMIT 1")
        fun findByName(first: String, last: String): User

        @Insert
        fun insertAll(vararg users: User)

        @Delete
        fun delete(user: User)
    }
    

자바

    @Dao
    public interface UserDao {
        @Query("SELECT * FROM user")
        List<User> getAll();

        @Query("SELECT * FROM user WHERE uid IN (:userIds)")
        List<User> loadAllByIds(int[] userIds);

        @Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
               "last_name LIKE :last LIMIT 1")
        User findByName(String first, String last);

        @Insert
        void insertAll(User... users);

        @Delete
        void delete(User user);
    }
    

AppDatabase

Kotlin

    @Database(entities = arrayOf(User::class), version = 1)
    abstract class AppDatabase : RoomDatabase() {
        abstract fun userDao(): UserDao
    }
    

자바

    @Database(entities = {User.class}, version = 1)
    public abstract class AppDatabase extends RoomDatabase {
        public abstract UserDao userDao();
    }
    

위의 파일을 생성한 후 다음 코드를 사용하여 생성한 데이터베이스 인스턴스를 가져옵니다.

Kotlin

    val db = Room.databaseBuilder(
                applicationContext,
                AppDatabase::class.java, "database-name"
            ).build()
    

자바

    AppDatabase db = Room.databaseBuilder(getApplicationContext(),
            AppDatabase.class, "database-name").build();
    

참고: 앱이 단일 프로세스에서 실행되는 경우 AppDatabase 개체를 인스턴스화할 때 싱글톤 디자인 패턴을 따라야 합니다. 각 RoomDatabase 인스턴스는 리소스를 상당히 많이 소비하며 단일 프로세스 내에서 여러 인스턴스에 액세스할 필요가 거의 없습니다.

앱이 여러 프로세스에서 실행되는 경우 데이터베이스 빌더 호출에 enableMultiInstanceInvalidation()을 포함하세요. 이런 방식으로 각 프로세스에 AppDatabase 인스턴스가 있을 때 한 프로세스에서 공유 데이터베이스 파일을 무효화할 수 있으며 이 무효화는 다른 프로세스 내의 AppDatabase 인스턴스로 자동 전파됩니다.

Room을 이용하여 실습해 보려면 뷰가 있는 Android RoomAndroid 지속성 Codelab을 활용해 보세요. Room 코드 샘플을 살펴보려면 Android 아키텍처 구성요소 샘플을 참조하세요.