본문 바로가기
Android 개발/Room, Realm, Databases

Room DB 사용방법 총정리 # Android SQLite

by Developer88 2023. 4. 4.
반응형

오늘은 andoird의 Room DB에 대해서 정리해 보도록 하겠습니다.

개인적으로는 Realm을 선호하기는 하지만,

Android Architecture Library의 LiveData나 ViewModel과

함께 잘 사용할 수 있는 라이브러리라는 점에서는 매력적이라고 생각합니다.

 

1. Room

1-1. Room

정식명칭은 Room Persistence library이고요.

ORM(Object Relational Mapping)으로서,

SQLite 데이터베이스를 사용하기 쉽도록,

데이터베이스를 객체로 매핑해 주는 역할을 합니다.

SQLite를 Annotation을 이용해서 좀 더 사용하기 쉽게 추상화했다고 보면 될 것 같은데요.

 

1-2. Types

Room 에서 지원하는 타입은 다음과 같은 것들이 있습니다.

 

 

2. Room의 3가지 Components

Room에는 3가지 주요 Component가 있는데요.

가볍게 한번 정리하고 지나가겠습니다.

2-1. Entity

Room에서의 Entity는 관계형 데이터에서의 Table과 같은데요.

아래와 같은 형태의 데이터입니다.

데이터가 들어있는 각 한줄한줄을 Row라고 합니다.

 

Student Entity(테이블)
Name Score age
88 12
66 13

 

2-2. Database

관계형 데이터 베이스의 액세스 포인트이며, 데이터베이스 홀더인데요.

위의 Entity(관계형 데이터베이스에서의 Table)들이 묶인 것이 바로 이 Database 입니다.

 

예로 들면, 학교전산시스템의 데이터베이스에는 아래와 같은 entity들이 있을 수 있겠지요.

  • Teacher
  • Student
  • Parent
  • Admin

이러한 entity들을 통틀어서 Database라고 합니다.

Room에서는 Database클래스를 상속받는 abstract Class 이구요.

entity의 리스트들을 가지고 있습니다.

 

2-3. Dao

Database내의 Entity들의 Row들에 저장된 데이터에 접근하기 위해서,

data access objects의 줄임말인 DAO에 접근하게 됩니다.

 

DAO는 data에 액세스 하는 객체입니다.

액세스에 필요로 하는 메소드들을 가지고 있게 구성해서 사용하지요.

앱에서 데이터를 얻어오거나 쓰기 위해서,

이 클래스의 함수들을 이용하게 됩니다.

 

이제 중요 구성요소에 대해 이해했으니,

본격적으로 라이브러리를 추가하고, 직접 코드를 보면서 이해해 보도록 하겠습니다.

 

3. Room 라이브러리 Implement

Room과 관련해서는 몇 가지 라이브러리를 추가해 주어야 하는데요.

보통 DB를 위한 라이브러리보다는 조금 많습니다.

 

가장 처음이 room-runtime라이브러리이고요.

annotation을 위한 kapt 그리고 kotlin Extenstion을 위한 room-ktx라이브러리가 있습니다.

마지막 줄은 test를 위한 라이브러리입니다.

 

dependencies {
  def room_version = "2.2.5"
  implementation "androidx.room:room-runtime:$room_version"
  kapt "androidx.room:room-compiler:$room_version" 
  implementation "androidx.room:room-ktx:$room_version"

  testImplementation "androidx.room:room-testing:$room_version"
}

 

 

4. 3가지 Component의 구현

Room라이브러리를 구현한다는 것은 위에서 언급한 3개의 Component를 구현하는 것인데요.

Data class를 시작으로 하나씩 구현해 보도록 하겠습니다.

 

4-1. Data Class 작성

DB에 관련된 코드는 Data클래스부터 작성하게 됩니다.

Room에서는 Table을 data클래스로 매핑해서 작성해 놓은 것이 Entity입니다.

 

Room에서 사용하는 Data클래스는 아래와 같이 @Entity annotation을 붙여 주어야 하는데요.

data클래스를 이용해 테이블과 매핑하는 것이라고 볼 수 있습니다.

관계형 DB의 핵심인 PrimaryKey는 @PrimaryKey라고 하는 애노테이션을 붙이고요.

각 Column에 대해서는 @ComlumnInfo라는 Annotation을 이용해서 사용해 줍니다.

테이블 안에 칼럼명을 다르게 설정하기 위해서는 "name"을 다른 값으로 아래와 같이 사용할 수 있습니다.

 

참고로 PrimaryKey가 자동으로 생성되게 하기 위해서는 annotation을 아래와 같이 설정해 주면 됩니다.

 

 

위에서 사용할 수 있는 애노테이션 이외에도 사용할 수 있는 키워드가 많이 있는데요.

@Ignore를 사용하면 해당 필드는 무시할 수 있고요.

@foreignKey를 이용해, parentColums와 childColumns를 아래와 같이 사용할 수도 있습니다.

 

 

4-2. Dao

이제 DB에 액세스 하는 메소드들을 만들어 보겠습니다.

SQL쿼리를 미리 함수에 저장해 놓았다가 사용하는 것으로 생각해 볼 수 있는데요.

아래와 같이, Query 애노테이션에 괄호를 열고 SQL문을 적어 줍니다.

 

CRUD를 간단하게 아래와 같이 구현해 볼 수 있습니다.

참고로 "vararg" 키워드는 kotlin에서의 가변인자를 가리킵니다.

메소드의 인자로 받은 변수는 ":"(콜론)을 이용해서 애노테이션의 Query문에서 사용할 수 있습니다.

예를 들어, 아래의 loadAllByIds는 studentIds를 인자로 받아서 사용하고 있고요.

그 아래의 findByName메소드는 first와 last를 인자로 받아서 LIKE로 사용하고 있습니다.

 

여기서는 Insert, Update, Delete, Query 애노테이션을 이용해서,

아래 이미지와 같이 CRUD를 구현할 수 있습니다.

 

 

 

한가지 주의할 점이 있는데요.

Room은 SQLite 기반이구요.

SQLite에서 true 나 false는 1과 0으로 표현한다는 점 입니다.

따라서, 위의 query에서 작성할 때, true 혹은 false는 반드시 1과 0으로 표현해주어야 합니다.

 

실수를 방지하기 위해서 Constant를 아래와 같이 사용할 수도 있습니다.

SQLConstants 같은 파일을 만들어서, 아래와 같이 Top-levle 상수를 선언해 주는 것도 방법입니다.

 

const val SQL_TRUE = 1
const val SQL_FALSE = 0

 

그럼 아래와 같이 사용할수도 있겠지요.

 

@Query("SELECT * FROM tests WHERE isFavorite = :favorite")
fun getFavoriteTests(favorite: Int = SQL_TRUE): Flow<List<Test>>

 

4-3. Type Converter

커스텀한 데이터객체를 저장할 필요가 있을 경우에 룸에서는 어떻게 해야 할까요?

공식문서에 따르면, 아래와 같이 Convertes클래스를 만들어서,

특정 timestamp와 Date객체를 변환할 수 있도록 하는 방법을 사용하라고 되어있습니다.

 

class Converters {
  @TypeConverter
  fun fromTimestamp(value: Long?): Date? {
    return value?.let { Date(it) }
  }

  @TypeConverter
  fun dateToTimestamp(date: Date?): Long? {
    return date?.time?.toLong()
  }
}

 

이런 Converter들은 직접 필요한 Entity에 추가하여서 적용되도록 할 수 있습니다.

 

@Entity(tableName = "chat_messages")
data class ChatMessage(
...
   @TypeConverters(ChatMessageTypeConverter::class)
   val messageType: MessageType,
...
)
enum class MessageType {
    TEXT,
    IMAGE,
    VIDEO,
    AUDIO
}

 

4-4. Database

이제 마지막 컴포넌트인 Database를 구현해 주면 되겠는데요.

studentDatabase파일을 아래와 같이 만들었습니다.

@Database 애노테이션을 사용하고요.

 

여기서 abstract class를 사용한 것은,

필요한 SQL코드등의 구현을 RoomLibrary에 맡기기 때문입니다.

우리가 Entity와 DAO에서 정의해 놓은 코드에 따라서 룸라이브러리가 필요한 SQL코드들을 생성해 처리해 주기 때문입니다.

 

entities에는 위에서 정의한 entity들을 array형태로 넣어주면 됩니다.

 

 

만약 위에서 본 것처럼, TypeConverts클래스를 가지고 있고 Entity에서 적용시키지 않았다면,

Annotation을  @Database Annotation의 아래에 추가해 줄 수 있습니다.

 

@TypeConverters(Converters::class)

 

이제 이렇게 생성한 studenDatabase를 객체로 만들어서 사용해 주면 되는데요.

참고로 아래의 fallbackToDestructiveMigration()은 데이터 베이스가 수정되면,

기존 데이터를 삭제하는 메소드인데요.

그렇지 않고 Migration을 해 줄 경우에는 addMigration메소드를 사용해 주면 됩니다.

 

 

공식문서에서는 아래와 같이, RoomDatabase 객체를 만드는 것은 비싼 작업으로,

싱글턴 패턴을 사용하는 것을 추천하고 있습니다.

 

 

싱글턴 패턴을 이용하기 위해서 companion object를 사용하는 것도 방법이지만,

Koin같은 Dependency Injection을 이용하는 것도 좋을 것 같은데요.

굳이 DI를 사용하지 않으실 분들은 아래 부분은 패스하셔도 무방합니다.

 

5. Dao 에서 Query문 사용시 팁

5-1. 가장 최신 것 부터 불러오기

아래의 Query는 가장 최신순으로 chat_messages를 보여주도록 query하고 있는데요.

ORDER BY는 어떤 컬럼을 기준으로 소팅할 것인지를 정하기 위한 키워드 이구요.

여기서는 createdAt이라는 컬럼을 기준으로 하였습니다.

DESC 는 descending즉 내림차순을 의미하는데요. 반대되는 개념이 ASC ascending 입니다.

DESC가 내려가는 것이므로, 가장 큰 것에서 작은 것을 정렬이 된다고 생각하면 됩니다.

createdAt에는 System.currentTimeMillis() 같이 시간이 갈수록 높은 값을 반환하는 값을 저장하고,

이를 DESC로 내림차순으로 query한다면, 반환값은 최신의 값부터 나오게 되겠지요.

 

SELECT * FROM chat_messages ORDER BY createdAt DESC

 

 

5-2. PrimaryKey의 auto-generate 사용시 주의할 점

PrimaryKey의 auto-generate을 true로 해놓고,

Room Entity 데이터클래스로 객체를 생성할 때 설정된 값이,

이 객체에 설정된 id 값이 그대로 DB에 반영되는 값이라고 생각하면 안됩니다.

 

auto-generate 되는 시점에 대해서 알고 있어야 하는데요.

바로, Insert 메소드가 실행될 때 입니다.

객체를 생성할 때가 아닙니다.

 

따라서, Room Entity데이터 클래스의 객체를 생성하고,

id 부분을 바로 사용해야 한다면, Insert 되고 난 시점에 사용해 주어야 합니다.

 

 

6. Hilt 또는 Koin을 이용한 객체 주입

6-1. Hilt

안드로이드에서 Hilt를 사용하는 방법에 대해서는 아래 글을 참조해 주세요.

>> HILT 에 대해서 정리해 보겠습니다. # DI Dependency Injection

 

이 글에서는 Room DB를 적용하는 방법에 대해서만 정리하겠습니다.

@Entity, @Dao, @Database를 만들었다면, 이제 Hilt가 의존성을 생성해 주면 되는데요.

아래와 같이 Module을 만들어서, Database의 객체를 생성하는 방법을 Provides Annotation을 이용해서 Hilt에게 알려주기만 하면 됩니다.

 

구현 방법은 사람마다 다르겠지만, MVVM패턴을 이용해서,

Repository 인터페이스를 아래와 같이 만들어 주고요.

 

 

이 인터페이스를 dao객체를 가지고 구현하는 구현클래스를 만들어 줍니다.

 

 

이후에 그것을 AppModule에서 Provides 애노테이션을 이용해서 Hilt에게 어떻게 만드는지 알려줍니다.

첫 번째는 Room Database객체를 만드는 방법이고요., 두 번째는 위에서 Room을 이용하는 Repository객체를 만드는 방법입니다.

 

 

db는 viewModel에서 가져다가 사용하게 되는데요.

위의 Module에서 Provide에게 어떻게 roomDB와 그것을 이용하는 Repository를 만드는지 Hilt에게 알려주었으니,

저희는 인자에 넣어서 사용만 하면 됩니다.

 

 

6-2. Koin

참고로 Koin에 관해서는 아래 글을 참조해 주시고요.

>> KOIN을 이용한 Dependency Injection (DI) 구현하기

아래와 같이 Koin을 사용해 볼 수도 있습니다.

먼저 StudentDatabase객체를 만들어 주는 클래스를 만들고요.

 

 

아래와 같이 Koin Module을 구현해 줍니다.

 

 

 

그 다음 ApplicationClass에서 startKoin을 해주면 되겠지요.

 

이제, 다음과 같이 객체를 주입해 줍니다.

 

 

이렇게 주입된 객체를 아래와 같이 사용해 주기만 하면 되겠습니다.

 

 

이상으로 Room Persistence Library에 대해서 정리해 보았고요.

더 좋은 내용이 있다면, 이 글을 통해서 업데이트하도록 하겠습니다.

 

728x90

댓글