Room DB 사용시 앱을 재시작할 때마다 데이터가 초기화 된다...?
Jetpack Compose 튜토리얼의 해당 과제를 수행 중에 있다.
외부 DB를 받아와 항공편을 검색해 Favorite 설정을 하면 메인화면에서 설정한 항목을 볼 수 있어야한다.
이를 위해 Room 데이터베이스를 읽고 쓸 수 있어야 했다.
저장하고 메인화면으로 나가는 과정에서 Favorite 설정한 데이터가 표시되지 않는 상황이 발생했다.
Android Studio에서 제공하는 Database Inspector를 통해 확인한 결과 데이터를 제대로 저장되는 것을 목격했고, DB쪽에 문제가 있음을 파악했다.
https://velog.io/@gogumi4502/Solutionroom-1
나와 같은 프로젝트를 진행했던 사람이 똑같은 상황을 겪었었다.
하지만 읽어봐도 뭔가 설명은 모호했다.
.createFromAsset("database/flight_search.db")
아래 있는 코드라는 이것을 삭제하자니 입력되어있는 기존 데이터를 받아와 항공편 검색에 사용할 수가 없다.
.fallbackToDestructiveMigration()
이걸 삭제하면 되는 거였다.
https://stackoverflow.com/questions/76044041/how-do-i-make-inserted-data-persist-in-room-database
https://stackoverflow.com/questions/72832362/room-database-is-recreated-at-app-restart
이 외에도 외국에서도 나와 같은 사례가 많았던 모양이다.
그래서 정확히 .fallbackToDestructiveMigration()이 뭐하는 녀석인지도 모르고 추가했다는 것인데,
https://developer.android.com/training/data-storage/room/migrating-db-versions
공식 문서를 읽어보면 Room이 기기의 기존 데이터베이스를 현재 버전으로 업그레이드하기 위한 이전 경로를 찾을 수 없으면 IllegalStateException이 발생하고, 이전 경로가 누락되었을 때 기존 데이터를 잃어도 괜찮다면 데이터베이스 생성 시 다음과 같이 fallbackToDestructiveMigration() 빌더 메서드를 호출하라고 되어 있다.
Room.databaseBuilder(applicationContext, MyDb::class.java, "database-name")
.fallbackToDestructiveMigration()
.build()
이 옵션을 설정하면 Room이 이전을 실행하려고 하고, 정의된 이전 경로가 없을 때 사용자의 데이터베이스의 테이블에서 모든 데이터를 영구적으로 삭제한다고한다.
이것 때문임이 확실하긴 하다.
하지만 이전에 Room DB를 사용한 튜토리얼 프로젝트들이나 작업했던 것들 전부 해당 옵션이 추가 되어있는데 이 프로젝트에서만 문제가 발생하는게 모호해서 좀 더 찾아봤다.
위 게시물에서도 단순히 fallbackToDestructiveMigration을 제거하면 해결되는 문제지만, 이럴 경우 데이터베이스와 데이터베이스 버전을 업데이트하면 마이그레이션이 제공되지 않기에 충돌이 발생하기 때문에 다음 릴리스부터 데이터베이스 마이그레이션을 수행할 수 없기에 addMigrations()을 추가하거나 충돌을 맞이할 수 밖에 없다는 것.
allowMainThreadQueries()
그렇기에 저 위 링크의 답변에선 애초에 마이그레이션 계획이 없다면 addMigrations() 할 수 있는 유일한 방법은 try/catch를 사용해 조건부로 fallbackToDestructiveMigration을 호출하고, 두 개의 정수(현재 데이터베이스의 버전, 새 데이터베이스의 버전)을 사용해 위 코드와 DataStore를 사용해 데이터베이스 마이그레이션이 필요한 릴리스가 있을 때마다 새로 작업을 시도하는 코드를 작성한 것 같다.
하지만 allowMainThreadQueries()는 프로덕션 앱에서는 사용해선 안되며 테스트 및 단순성 목적으로만 사용하라고 한다. 해당 메서드는 Room은 기본적으로 Main Thread에서 접근할 수 없지만, 저 메서드를 통해 만들어진 Instance에선 접근이 가능하기 때문이다. Database에 접근하는 작업은 무거운 작업이기에 사용자 활동을 장기간 멈출 수 있기에 Main Thread에서 접근하는 것이 아닌 coroutine으로 접근하기에 사용하지 않는게 좋다는 것 같다.
+++
github에 flightsearch 프로젝트를 쳐보니 나와 같은 문제에 직면했는지 database class 파일에 fallbackToDestructiveMigration이 없는 혹은, 주석처리해둔 사람들도 여럿 보였다.
그냥 쓴 사람들도 있는데 뭐가 정환히 문제인진 모르겠다.
+++
공식문서는 Room 2.2.0 이상에서 API 메서드를 사용하여 초기화 시 기기 파일 시스템의 사전 패키징된 데이터베이스 파일의 콘텐츠로 Room 데이터베이스를 미리 채울 수 있는 방법도 소개하고 있다.
https://developer.android.com/training/data-storage/room/prepopulate?hl=ko#migrations
추가 자료:
https://velog.io/@thd0427/ROOM-DB%EB%A5%BC-Migration-%ED%95%98%EC%9E%90
https://medium.com/androiddevelopers/room-auto-migrations-d5370b0ca6eb