티스토리 뷰

안드로이드[Kotlin]

[Database] Room이란?

JeongeunChoi 2022. 2. 16. 16:31

Room?

Room은 AAC(Android Architecture Components)로

간단히 말하면 스마트폰 내장 DB에 데이터를 저장하기 위해 사용하는 라이브러리이다.

  • ORM(Object Relational Mapping) 라이브러리로 DB 데이터를 자바 또는 코틀린 객체로 mapping 해준다.
  • SQLite를 내부적으로 사용하고 있지만, DB를 구조적으로 분리하여 데이터 접근의 편의성을 높여주고 유지보수에 편리하다.
  • 다양한 Annotation을 통해 컴파일 시 코드들을 자동으로 만들어주며 LiveData, RxJava와 같은 Observation 형태를 지원한다.
  • MVP, MVVM과 같은 아키텍쳐 패턴에 쉽게 활용할 수 있도록 되어있다.

Room 사용 시의 이점

  • SQL 쿼리의 컴파일 시간 확인
  • 반복적이고 오류가 발생하기 쉬운 상용구 코드를 최소화하는 편리한 주석
  • 간소화된 데이터베이스 이전 경로

-> 따라서 SQLite API를 직접 사용하는 대신 Room을 사용하는 것이 좋다.

 


Room 사용을 위한 Dependency 추가

apply plugin: 'kotlin-kapt'

dependencies {
    implementation "android.arch.persistence.room:runtime:1.1.1"
    kapt "android.arch.persistence.room:compiler:1.1.1"
    kaptTest "android.arch.persistence.room:testing:1.1.1"
}

 


Room의 기본 구성요소

데이터 항목(data entities)

  • 앱 데이터베이스의 테이블을 나타낸다.
  • 각 Entity는 고유 식발자인 PrimaryKey(기본키)가 반드시 필요하며, 큰 의미가 없다면 autoGenerate를 이용하여 자동으로 생성이 가능하다.
@Entity
data class MyToDoList(
    @PrimaryKey(autoGenerate = true) val id: Long = 0,

    @ColumnInfo(name="title") val title: String,
    @ColumnInfo(name="content") val content: String
)

 

데이터 액세스 객체(DAO: data access objects)

  • 앱이 데이터베이스의 데이터를 쿼리, 업데이트, 삽입, 삭제하는데 사용할 수 있는 메서드를 제공한다.
@Dao
interface ToDoDao {
    // 테이블에 데이터 삽입
    @Insert(onConflict = REPLACE) // Insert 할 때 PrimaryKey가 겹치는 것이 있으면 덮어 쓴다는 의미
    fun insert(toDo: MyToDoList)

    // 테이블의 데이터 수정
    @Update
    fun update(toDo: MyToDoList)

    // 테이블의 데이터 삭제
    @Delete
    fun delete(toDo: MyToDoList)

    // 테이블의 모든 값 가져오기
    @Query("SELECT * FROM MyToDoList")
    fun getAll(): List<MyToDoList>

    // 'title'에 해당하는 투두를 삭제
    @Query("DELETE FROM MyToDoList WHERE title = :title")
    fun deleteToDoByTitle(title: String)
}

 

데이터베이스 클래스(database class)

  • 데이터베이스를 보유하고 앱의 영구 데이터와의 기본 연결을 위한 기본 액세스 포인터 역할을 한다.
  • Entity 모델을 기반으로 하고, DAO의 메소드를 가지고 있는 데이터베이스를 생성한다.
  • 이는 RoomDatabase()를 상속한다.
@Database(entities = [MyToDoList::class], version = 4)
abstract class ToDoDatabase: RoomDatabase() {
    abstract fun getToDoDao(): ToDoDao

    // 데이터베이스 객체를 인스턴스 할 때 싱글톤으로 구현
    companion object {
        private var instance: ToDoDatabase? = null

        // 데이터베이스 객체를 반환
        @Synchronized
        fun getInstance(context: Context): ToDoDatabase? {
            if(instance == null){
                synchronized(ToDoDatabase::class){
                    instance = Room.databaseBuilder(
                        context.applicationContext,
                        ToDoDatabase::class.java,
                        "toDo-database"
                    ).fallbackToDestructiveMigration().build()
                }
            }
            return instance
        }

        // 데이터베이스 객체를 삭제
        fun destroyInstance() {
            instance = null
        }
    }
}

 


다양한 Room 구성요소 간 관계

데이터베이스 클래스는 데이터베이스와 연결된 DAO 인스턴스를 앱에 제공한다.

그러면 앱은 DAO를 사용하야 데이터베이스의 데이터를 연결된 데이터 항목 객체의 인스턴스로 검색할 수 있게 된다.

앱은 정의된 데이터 항목을 사용하여 상응하는 테이블의 행을 업데이트하거나 삽입할 새 행을 만들 수도 있다.

아래 그림은 다양한 Room 구성요소 간 관계를 보여준다.

 

출처:&amp;amp;nbsp;https://developer.android.com/training/data-storage/room#kts

 


Activity에서 Room에 접근하기

메인 쓰레드에서 Room DB에 접근하려고 하면 다음 에러가 발생한다.

Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
메인 UI 화면이 오랫동안 멈춰있을 수도 있기 때문에, 메인 쓰레드에서는 데이터베이스에 접근할 수 없습니다.

Room과 관련된 액션은 백그라운드에서 작업해야 한다. 이때, 쓰레드, 코루틴을 사용할 수 있다!

 

아래 코드는 쓰레드를 이용하였다.

Room에서 데이터를 불러와 리사이클러뷰를 이용해 UI에 나타내주는 코드이다.

 

추가로 안드로이드 OS는 UI 자원 사용은 UI Thread에서만 가능하므로 Sub Thread에서 UI 자원을 다루기 위해서는 runOnUiThread를 사용해야 한다.

 

class ToDoActivity : AppCompatActivity(), ToDoContract.View {
    // 사용자의 Input이 주어지면 뷰를 통해 Presenter로 흐름이 이어지므로
    // 뷰가 Presenter를 알고 있어야 한다.
    override lateinit var presenter: ToDoContract.Presenter
    private lateinit var myToDoSet: List<MyToDoList>
    private lateinit var recyclerView: RecyclerView
    private lateinit var toDoAdapter: ToDoAdapter
    private var db: ToDoDatabase? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityToDoBinding.inflate(layoutInflater)
        setContentView(binding.root)

        presenter = ToDoPresenter(this)
        recyclerView = binding.todoRecyclerView

        db = ToDoDatabase.getInstance(applicationContext) ?: throw IllegalAccessException()

        // 룸에 있는 데이터 불러오기
        getAllTodo()
        binding.mainAddButton.setOnClickListener {
            startAddToDoActivity()
        }

    }

    override fun setRecyclerView() {
        // 안드로이드 OS는 UI 자원 사용은 UI Thread에서만 가능하므로
        // Sub Thread에서 UI 자원을 다루기 위해 runOnUiThread를 사용
        runOnUiThread {
            toDoAdapter = ToDoAdapter(myToDoSet)
            toDoAdapter.notifyDataSetChanged()
            recyclerView.apply {
                adapter = toDoAdapter
                setHasFixedSize(true)
            }
        }
    }

    override fun getAllTodo() {
        // 메인 쓰레드에서 Room DB 접근 시 에러가 발생하므로 백그라운드에서 작업해야 한다.
        Thread{
            myToDoSet = db!!.getToDoDao().getAll()
            // 리사이클러뷰 설정
            setRecyclerView()
        }.start()
    }

    override fun onResume() {
        super.onResume()
    }

    override fun onRestart() {
        super.onRestart()
        getAllTodo()
    }

    override fun onDestroy() {
        super.onDestroy()
        ToDoDatabase.destroyInstance()
        db = null
    }

    private fun startAddToDoActivity(){
        val intent = Intent(this, AddToDoActivity::class.java)
        startActivity(intent)
    }
}

 


 

참고

https://developer.android.com/training/data-storage/room#kts

 

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

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

developer.android.com

https://blog.yena.io/studynote/2018/09/08/Android-Kotlin-Room.html

 

[Android][Kotlin] Room 으로 DB 저장하기

로컬 DB를 저장할 일이 있어서 SQLite를 살펴보다가, 작년에 발표된 Room이라는 존재를 알게 되었다. 과연 정말 SQLite보다 간단하고 좋을까? 라는 의문으로 시작해서 이번 포스트를 작성하게 되었다.

blog.yena.io