본문 바로가기

Android

Kotlin 이슈 7 Room 사용하여 안드로이드 로컬 db 시작하기

반응형

위시리스트를 저장하고 싶어!

안드로이드 어플에 데이터를 저장하는 방법은 여러가지가 있지만, 저는 서버가 없는 관계로 로컬 db인 SQLite를 사용하기로 했습니다.

Room은 SQLite 성능을 최대화하면서도 원활한 데이터베이스 접근을 위해 추상 레이어를 제공합니다...만 물론 직접 접근하는 방법도 존재합니다. 아래 링크는 SQLite에 직접 접근하는 방법에 대해 설명하는 안드로이드 공식 문서입니다.

 

SQLite를 사용하여 데이터 저장  |  Android 개발자  |  Android Developers

데이터베이스에 데이터를 저장하는 작업은 연락처 정보와 같이 반복적이거나 구조적인 데이터에 이상적입니다. 이 페이지는 일반적으로 개발자가 SQL 데이터베이스를 잘 알고 있다고 가정하며 Android에서 SQLite 데이터베이스를 시작하는 데 유용합니다. Android에서 데이터베이스를 사용할 때 필요한 API는 android.database.sqlite 패키지로 제공됩니다. 주의: 이러한 API는 강력하기는 하지만 상당히 낮은 수준이므로 다음과 같이 사용하

developer.android.com

아래는 Room 관련 공식 문서입니다.

 

Room을 사용하여 로컬 데이터베이스에 데이터 저장  |  Android 개발자  |  Android Developers

Room 라이브러리를 사용하여 더 쉽게 데이터를 유지하는 방법 알아보기

developer.android.com

Room의 세 가지 구성 요소

Room에는 세 개의 주요 구성 요소가 있습니다.

  • Database: 데이터베이스 홀더를 포함하며, 데이터베이스 메인 진입점으로 사용됩니다. @Database라 명시된 클래스는 다음 사항을 만족해야 합니다.

    • RoomDatabase를 확장한 추상 클래스일 것

    • 데이터베이스와 연관된 엔티티 리스트를 포함할 것

    • 아무 파라미터도 받지 않고, @Dao를 반환하는 추상화 메소드를 포함할 것

      실행 시점에 Room.databaseBuilder()

      Room.inMemoryDatabaseBuilder()를 통해 Database 인스턴스를 이용할 수 있습니다.

  • Entity: 데이터베이스 속 테이블을 나타냅니다.

  • DAO: 데이터베이스에 접근하기 위한 메소드를 포함합니다.

앱은 Room 데이터베이스를 사용하여 Data Access Object, DAO를 획득합니다. 그다음 앱은 DAO를 사용해서 엔티티를 얻거나, 변경된 엔티티를 데이터베이스에 저장합니다. 마지막으로, 앱은 엔티티를 사용해서 컬럼 값을 얻거나 설정합니다.

Room 아키텍쳐 다이어그램

예시 코드를 보면 Entity는 data class, DAO는 interface, Database는 abstract class로 표현되는 걸 볼 수 있습니다. 그리고 각각에 @Entity, @Dao, @Database 어노테이션이 붙어있네요.

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

// UserDao.kt
@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)
}

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

사용은 이렇게 하면 된다고 하네요.

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

db.userDao().getAll()

Database의 중복 생성을 막기 위해서는 이렇게 싱글톤 구성하면 좋다고 합니다.

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

        companion object {
        private var INSTANCE: AppDatabase? = null

        fun getInstance(context: Context): AppDatabase? {
            if (INSTANCE == null) {
                synchronized(AppDatabase::class) {
                    INSTANCE = Room.databaseBuilder(
                        context.applicationContext,
                        AppDatabase::class.java,
                        "db.db"
                    ).build()
                }
            }
            return INSTANCE
        }
    }
}

아래처럼 바로 부를 수 있습니다.

AppDatabase.userDao.getAll()

AppDatabase_Impl does not exist??

알아서 import 해줄 줄 알고 코드만 적고 빌드를 했더니 다음과 같은 에러가 뜹니다. dependencies는 직접 추가해주어야 하나봅니다.

아래 코드를 모듈의 build.gradle dependencies에 추가해주면 됩니다.

dependencies {
        ...
        implementation "androidx.room:room-runtime:2.2.3"
    kapt "androidx.room:room-compiler:2.2.3"
}

 

 

방  |  Android 개발자  |  Android Developers

Room 지속성 라이브러리는 SQLite를 완벽히 활용하면서 강력한 데이터베이스 액세스를 지원하는 추상화 계층을 SQLite에 제공합니다. 최근 업데이트 현재 안정화 릴리스 다음 출시 후보 베타 릴리스 알파 릴리스2019년 11월 20일 2.2.2 - - - 종속성 선언 Room에 종속성을 추가하려면 프로젝트에 Google Maven 저장소를 추가해야 합니다. 자세한 내용은 Google Maven 저장소를 읽어보세요. Room의 종속성에는 Room 이전

developer.android.com

Cannot access database on the main thread ...

안드로이드에는 UI(Main) Thread와 Background Thread가 있습니다. 그 중 UI Thread는 긴 시간이 걸리는 작업을 중단하고 에러를 발생시킵니다. 때문에 db 처리 작업을 하려하면 아래와 같은 에러가 발생합니다.

java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
        at androidx.room.RoomDatabase.assertNotMainThread(RoomDatabase.java:267)
        at androidx.room.RoomDatabase.query(RoomDatabase.java:323)
        at androidx.room.util.DBUtil.query(DBUtil.java:83)
                ...

코틀린의 경우 Coroutine을 사용하여 이를 background Thread에서 처리합니다.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

                val appDatabase = AppDatabase.getInstance(this)
        CoroutineScope(Dispatchers.IO).launch {
            appDatabase?.wishDao()?.getAll()
        }
        }
}

AUTOINCREMENT는 어떻게 하나요?

기존 SQLite 문법을 어떻게 사용해야 할 지 고민하던차, PrimaryKey 문서에서 확인할 수 있었습니다.

 

PrimaryKey  |  Android 개발자  |  Android Developers

PrimaryKey The android.arch Architecture Components packages are no longer maintained. They have been superseded by the corresponding androidx.* packages. See androidx.room.PrimaryKey instead. public abstract @interface PrimaryKey implements Annotation and

developer.android.com

값을 추가할 때 uid 값을 자동 증가시키고 싶다면 아래처럼 작성하시면 됩니다.

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

appDatabase?.userDao()?.insert(User(0, "엥", "휴"))

Int와 Long 타입은 다음과 같은 식으로 autoGenerate 되는 값을 0으로 두면 insert 과정에서 값이 자동으로 증가합니다. Integer의 경우, null로 처리했을 때 값이 자동 증가합니다. 이때 값은 1부터 시작합니다. 이걸 몰라서 아무 Int나 넣었다가 자꾸 에러가 났습니다...

@ColumnInfo는 무엇인가요?

@ColumnInfo는 해당 변수가 데이터베이스 내의 Column임을 나타내줍니다.

 

ColumnInfo  |  Android 개발자  |  Android Developers

ColumnInfo The android.arch Architecture Components packages are no longer maintained. They have been superseded by the corresponding androidx.* packages. See androidx.room.ColumnInfo instead. public abstract @interface ColumnInfo implements Annotation and

developer.android.com

없어도 크게 상관은 없는 듯 보이네요??

Looks like you've changed schema but forgot to update the version number.

@Database(entities = arrayOf(User::class), version = 1)

저기 나와있는 저 버전을 높이라는데, 그렇게 하니 추가 에러가 발생해서 그냥 db 파일 이름을 바꾸거나 앱을 지웠다 까는 걸로 해결했습니다. 아래 글에서 Database migration 부분을 참고하셔도 좋을 것 같습니다.

 

5일차 [안드로이드] Room

Room은 SQLite의 기능을 최대한 활용하는 동시에 데이터베이스를 원활하게 접근할 수 있도록 SQLite 위에 추상화 계층을 제공한다. 적은 양의 정형화된 데이터를 처리하는 애플리케이션은 로컬에서 해당 데이터를 유지함으로써 큰 이점을 얻을 수 있다. ex) 관련 데이터를 캐시하는 것 이렇게 하면 장치게 네트워크에 액세스할 수 없는 경우에도 사용자

woovictory.github.io

 

반응형