해당 포스트에서는 실제 토이 프로젝트에서 만들어본 Database와 Dao의
코드를 직접 분석해보면서 정리한 글입니다. 😌
안드로이드를 독학하고 있는 초보자이기에
틀린 점이 있다면 꼭 댓글로 말씀 부탁드립니다. 🤗
1. Room과 SQLiteOpenHelper
예~~전에는 안드로이드에서 로컬 데이터베이스를 구축할 때 Room이 아닌, SQLiteOpenHelper를 사용했었습니다.
초기 Table 생성부터 SQL 실행까지 모든 처리를 개발자가 직접 해야 했으며,
모든 SQL 쿼리가 String으로 되어있어 휴먼에러가 발생하기 쉬운 구조였다고 생각합니다. 😭
👇🏻 Account 프로젝트에서 작성한 테이블 생성 Query문
Room은 SQLiteOpenHelper를 사용할 때 개발자가 직접 작성해야 하는 이러한 코드들을 간단하게 Annotation으로
정의할 수 있도록 해주는 라이브러리입니다. 👏🏻👍🏻
👇🏻 Table의 Schema로 사용될 data class에 @Entity Annotaion을 사용
다음은 Annotation으로 정의된 코드와 실제 build된 코드를 비교하며, Room의 내부동작에 대해 알아보겠습니다. 🤗
2. Database_Impl
Room에서는 databaseBuilder를 통해 Database를 생성하게 됩니다.
이는 SQLiteOpenHelper를 이용해 Database를 생성할 때 필요한 parameter와 매우 유사한 것을 알 수 있습니다.
class AppDatabase(context: Context) :
SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { /* */ }
2.1 Table 생성
Room에서 Database를 정의할 때 아래와 같이 entity를 추가하게 됩니다.
@Database(
entities = [
ImageEntity::class,
RemoteKeys::class
],
version = RandomPhotoDatabase.DATABASE_VERSION,
exportSchema = false // auto migration deny
)
abstract class RandomPhotoDatabase : RoomDatabase()
RandomPhotoDatabase의 실제 구현체인 RandomPhotoDatabase_Impl 소스코드를 보게된다면
@Entity로 지정된 data class에 맞게 각 Table 생성 Query들이 실행되는 것을 알 수 있습니다. 👍🏻
🌹 createAllTables 메서드는 데이터베이스가 새로 생성될 때 또는 데이터베이스가 업데이트될 때 초기에 한번만 호출되어 테이블을 생성하게 됩니다.
🏠 Hilt를 사용하여 Database를 생성할 때, Singleton으로 지정해야 하는 이유
2.2 Dao
Room의 Dao는 👇🏻아래 이미지와 같이 Database의 객체 멤버 변수로서 관리되게 됩니다.
왜 멤버 변수로 관리하는 것일까요? 예상하셨듯이, Database 내에서 Singleton으로 관리하기 위해서입니다. 🤗
예를 들어 ImageDao를 받아오는 getImageDao의 구현을 보게된다면
와 같이 한번만 초기화되고, 이후에는 생성된 객체를 가져오는 Singleton 형식으로 되어 있는 것을 알 수 있습니다.
🏠 Hilt를 사용하여 Dao를 위한 provider를 만들때는 Scopping 해줄 필요가 없습니다.
그렇다면 실제 Dao 내에 작성한 Query문들은 어떻게 구현되어 있을까요?
3. Dao_Impl
위 이미지를 보면 내부적으로는 결국 SQLiteOpenHelper를 사용해 명령문을 직접 실행하는 것을 알 수 있습니다.
🌹 insertAll이 suspend function으로 정의되어 있기 때문에 Continuation을 매개변수로 받는 것도 흥미로운 점입니다. 🤔
또한 중요하게 볼 점은 하나의 메서드를 기준으로 transaction이 만들어진다는 점입니다.
이 때문에 만약 여러 데이터를 insert해야 한다면
fun insertImages(images: List<ImageEntity>) {
images.forEach { imageDao.insert(it) }
}
보다
fun insertImages(images: List<ImageEntity>) {
imageDao.insertAll(images)
}
로 구현하는 것이 좋습니다. 👍🏻
마지막으로 Select Query가 구현된 코드를 확인해보고 마치도록 하겠습니다.
👇🏻 아래 코드는 ImageEntty를 가져오는 코드의 일부입니다.

두둥! 여기서 예상치 못한 부분을 발견하게 되었습니다.
데이터를 읽어오는 부분은 기존 SQLiteOpenHelper와 동일하게 cursor를 사용하고 있지만
🌹 구현 코드 자체가 java 코드이기 때문에 Nullable과 NonNull 변수를 구분하고 있지 않고 있습니다.
실제 객체를 생성하는 코드는 아래와 같습니다.
_item = new ImageEntity(_tmpId,_tmpAuthor,_tmpWidth,_tmpHeight,_tmpImageUrl);
_result.add(_item);
하지만 정의했던 ImageEntity의 모든 프로퍼티는 Non-null 타입입니다. 😭
이 부분은 Table을 생성하는 시점에 Non-null 타입으로 지정된 프로퍼티들은 모두 NOT NULL로 지정되어 있기 때문에
null이 넘어올 경우는 없다고 생각되기는 합니다.ㅎㅎ
이전에 작성했던 Retrofit 라이브러리 분석 포스트에 이어 이번에는 Room 라이브러리의 내부(?)
더 정확히 말하자면 실제 구현체 부분의 코드를 확인해보는 시간을 가져보았습니다.
의존성 주입을 위해 Hilt를 사용해본지 오래되지 않아 Scope 지정 문제, 특히 외부 라이브러리 사용 시
Singleton Scope 지정 여부에 대해 많은 지적을 받게 되었었습니다.
때문에 라이브러리 내부동작에 대해 조금씩 조사를 해보고 있는데...
생각보다 재미있네요.ㅎㅎ (코 쓰윽)
다음에는 다른 라이브러리 분석글로 찾아오거나 기존에 작성했던 포스트를 개선하는 방향으로
찾아뵙도록 하겠습니다. 🤗
감사합니다. 😌
'Android > Android' 카테고리의 다른 글
[Compose] Custom BoxShadow를 만들어보자! (1) | 2023.04.04 |
---|---|
[Compose] Font에 적용되는 Padding 제거하기 (0) | 2023.04.04 |
[Android/Compose] 언제 Recomposition이 발생하는 것일까? (0) | 2023.03.26 |
[Android] Retrofit의 ThreadPool : 왜 Retrofit은 Dispatcher 변경을 하지 않아도 되는것일까.. (0) | 2023.03.10 |
[Android/Compose] Compose에서 Paging3 적용기 (0) | 2023.03.03 |