[안드로이드스튜디오_문화][Room]

기말 지하기포·2023년 12월 8일
0

#Room Android Developer 공식문서
=>https://developer.android.com/training/data-storage/room?hl=ko
(여기에 들어가면 좌측에 "로컬 데이터베이스에 데이터 저장" 하단에 Room에 대한 정보가 많음)

-큰 규모의 데이터를 처리해야 하는 앱은 데이터를 로컬에 유지하면 휴대폰을 네트워크에 연결하지 않아도 사용자가 휴대폰의 데이터를 탐색할 수 있는 장점이 있다. Room은 SQLite의 장점은 받아들이면서 SQLite의 단점(컴파일시 발생하는 에러 확인X , boilerplate 코드)은 상쇄시킨 SQLite 위에 구축된 라이브러리로 우리에게 더 높은 편의성을 제공해준다.

-Room을 사용하기 위해서는 모듈수준의 build.gradle에 종석성을 아래와 같이 추가해주어야 한다. 아래는 기본적인 Room 관련 종속성 추가이고 Ksp를 쓰거나 Pagging , Coroutine 등등과 함께 사용하려면 추가적인 종속성 추가를 해주어야 한다.

implementation("androidx.room:room-ktx:${room_version}")
kapt("androidx.room:room-compiler:${room_version}")

// 아래는 추가적인 Room 관련 종속성 추가 내용임.
ksp("androidx.room:room-compiler:$room_version")
implementation("androidx.room:room-ktx:$room_version")
testImplementation("androidx.room:room-testing:$room_version")
implementation("androidx.room:room-paging:$room_version")

-Room의 주요 구성요소는 총 3가지가 존재한다.
[1. 데이터베이스 클래스 : 데이터 베이스를 실제로 가지고 있다. + 기본 접근 역할].
[2. 데이터 항목 : 앱의 데이터베이스의 테이블의 행 하나를 나타낸다. + 고유의 접근 키값이 있다.].
[3. 데이터 엑세스 객체(DAO) : 앱에서 데이터베이스의 데이터를 처리 할 수 있는 메서드를 제공한다.]

-AppInspection을 클릭하면 안드로이드 스튜디오 내부에서 현재 에뮬레이터 기기를 누르면 데이터베이스 검사를 할 수 있다.

RoomDatabase 3요소

Database Class

-Database Class는 데이터베이스를 보유하는 역할과 RoomDatabase에 대한 구성을 정의하고 영구 데이터에 대한 앱의 기본 엑세스 지점 역할도 한다.

-Database Class는 3가지의 조건을 충족해야 한다.

  • Database Class는 RoomDatabase와 연결된 데이터 항목을 모두 나열하는 entities 배열이 포함된 @Database 주석이 달려야 한다.

  • Database Class는 RoomDatabase를 확장하는 추상 클래스여야 한다.

  • Database Class는 앱이 사용할 각 테이블에 대한 데이터 접근 객체(DAO)를 제공하는 Property를 포함해야 하며, 이 Property는 매개변수 없이 정의되어 DAO 객체의 인스턴스를 반환하거나 DAO 객체 타입이어야 한다.

-abstract class ContactDatabase가 RoomDatabase임을 Android RoomLibrary에게 알려서 실제 Database를 생성하기 위해서 Database Annotation을 사용하는 것이다.

-Database Annotation의 Parameter인 entities에는 RoomDatabase에서 사용 할 테이블을 정의한 클래스를 넣어주면 된다.

-Database Annotation의 Parameter인 version에는 RoomDatabase의 버전을 지정해주면 된다.

-RoomDatabase의 작업을 수행하는데 필요한 코드를 자동으로 만들어주기 위해서 RoomDatabase()를 상속 받는다.

-아래는 위 내용을 기반으로 Database Class를 만든 예시코드이다.

>abstract class ContactDatabase가 RoomDatabase임을 Android RoomLibrary에게 알려서
실제 Database를 생성하게 하고 싶으면 Database Annotation을 사용하면 된다.
>Database Annotation의 Parameter인 entities에는 RoomDatabase에서 사용 할 테이블을 정의한 
클래스를 넣어주면 된다.
>Database Annotation의 Parameter인 version에는 RoomDatabase의 버전을 지정해주면 된다.
@Database(
    entities = [Contact::class],
    version = 1
)
>RoomDatabase의 작업을 수행하는데 필요한 코드를 자동으로 만들어주기 위해서 RoomDatabase()를\
상속 받는다.
abstract class ContactDatabase: RoomDatabase() {
    // RoomDatabase 작업을 위해서 정의한 Dao 인터페이스 타입의 추상 변수를 만든다.
    abstract val dao: ContactDao
}

Database Item

-RoomLibrary를 사용하여 Data를 저장할 때는 저장하려는 객체를 나타내는 항목 , 즉, 테이블이라고 불리는 것을 정의해주어야 한다.

-@Entity

-데이터 테이블의 행은 data class 객체가 들어가고 , 데이터 테이블의 열은 data class의 각각의 속성들이 들어간다. 이를 RoomLibrary에게 알려주기 위해서는 Entity Annotation을 data class 위에 작성해주어야 한다.

-RoomDatabase의 각 단일 테이블'행'은 고유의 키 값을 가지고 있어야지 DAO에서 접근이 가능하기 때문에 키 값을 저장할 수 있는 변수를 꼭 작성해주어야 하고, 데이터베이스의 테이블에 접근 할 때 해당 id를 가진 테이블이 단 한개만 존재해야 하기 때문에 PrimaryKey Annotation을 작성해서 Room에게 id 변수가 고유 접근 키값이라고 알려주어야 한다.

-또한 데이터베이스에 테이블을 추가 할 때마다 일일이 id 값을 업데이트 해주기 불편하므로 "autoGenerate = true"로 설정해주어야 한다. PrimaryKey(autoGenerate = true)는 데이터 테이블'행'이 추가 될 때마다 자동으로 1씩 키값이 증가하도록 도와준다.

-아래는 Database Item의 예시코드이다.

> 아래의 data class Contact를 RoomDatabase 내부의 테이블 행으로 사용하겠다는 것을 Room에게 
알려주려면 @Entity Annotation을 사용해야 한다.
>아래의 data class Contact의 각 속성들은 RoomDatabase의 Column에 매칭된다.
@Entity
data class Contact(
    val firstName: String,
    val lastName: String,
    val phoneNumber: String,

>데이터베이스의 각 단일 테이블에는 고유한 접근 키 값을 가지고 있어야 하기 때문에 data class에 
id를 넣어준것이다.
>데이터베이스의 테이블에 접근 할 때 해당 id를 가진 테이블이 단 한개만 존재해야 하기 때문에
PrimaryKey Annotation을 작성해서 Room에게 id 변수가 고유 접근 키값이라고 알려주어야 한다.
>또한 데이터베이스에 테이블을 추가 할 때마다 일일이 id 값을 업데이트 해주기 불편하므로
"autoGenerate = true"로 설정해주어야 한다.
>PrimaryKey(autoGenerate = true)는 데이터 테이블'행'이 추가 될 때마다 자동으로 1씩 키값이 
증가하도록 도와준다.
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0
)

DataAccess Object(DAO)

-DAO는 RoomDatabase에 저장된 데이터를 처리하는 방법을 정의한 인터페이스이다. 이렇게 인터페이스를 사용해서 정의하였기 때문에 클린아키텍처의 원칙인 관심사의 분리를 유지할 수 있다.

-RoomDatabase에 접근해서 데이터를 관리하고 싶을 때 필요한 메서드를 정의하고 싶으면 Dao Annotation을 작성해주면 된다. Dao Annotation을 사용하게 되면 Room이 Dao Annotation을 적용한 인터페이스를 구현한 데이터 엑세스 객체를 자동으로 생성해서 해당 interface를 활용해서 데이터를 관리 및 처리 할 수 있다.

-@Dao Interface 내부에 데이터를 처리 및 관리 할 수 있는 메서드를 정의 할 때 parameter에 Entity Annotation을 달아준 데이터 항목을 넣어주어야 한다.

-Dao Interface에 들어가는 메서드는 두가지의 유형이 있다.

  • 편의 메서드 : Room Library에 내장된 기본 기능을 구현하는 메서드이다.
// RoomDatabase에 새로운 테이블'행'을 추가한다.
@Insert
suspend fun InsertContact(contact: Contact)

// RoomDatabase에서 테이블'행'을 삭제한다.
@Delete
suspend fun deleteContact(contact: Contact)

// RoomDatabase에 기존 테이블'행'을 업데이트한다.
@Update
suspend fun UpdateContact(contact: Contact)

// RoomDatabase에 새로운 테이블'행'을 추가하거나 이미 존대한다면 , 테이블'행'을 업데이트한다
@Upsert
suspend fun upsertContact(contact: Contact)
  • 쿼리 메서드 : SQL 문법을 사용하여 데이터를 처리 할 수 있게 해주는 커스텀 메서드이다.

    SELECT 열이름 : 열이름을 조회한다.
    / FROM 테이블이름
    / WHERE 조건문(관계연산자 , 논리연산자 사용가능) : 특정 조건만 조회한다.
    / ORDER BY 열이름 : 열이름을 조건에 맞게 정렬한다.(ASC||DESC)
    / LIMIT 숫자 : 조회할 수 있는 갯수 제한
// SELECT * : 다 선택해라
// FROM contact : contact Table에서
// ORDER BY firstName ASC : firstName 열의 값을 오름차순 정렬해라
// ORDER BY lastName ASC : lastName 열의 값을 오름차순 정렬해라
// ORDER BY phoneNumber ASC : phoneNumber 열의 값을 오름차순 정렬해라
// ORDER BY AAA DESC : AAA 열의 값을 내림차순 정렬해라

@Query("SELECT * FROM contact ORDER BY firstName ASC")
fun getContactsOrderedByFirstName(): Flow<List<Contact>>

@Query("SELECT * FROM contact ORDER BY lastName ASC")
fun getContactsOrderedByLastName(): Flow<List<Contact>>

@Query("SELECT * FROM contact ORDER BY phoneNumber ASC")
fun getContactsOrderedByPhoneNumber(): Flow<List<Contact>>

-위의 기본 개념을 기반으로 작성한 전체적인 DAO에 대한 예시코드

> RoomDatabase에 접근해서 데이터를 관리하고 싶을 때 필요한 메서드를 정의하고 싶으면 
Dao Annotation을 작성해주면 되고, Dao Annotation을 사용하게 되면 Room이 Dao Annotation을
적용한 인터페이스를 구현한 데이터 엑세스 객체를 자동으로 생성해서 해당 interface를 활용해서 
데이터를 관리 및 처리 할 수 있다.

@Dao
interface ContactDao {

>@Dao Interface 내부에 데이터를 처리 및 관리 할 수 있는 메서드를 정의 할 때 parameter에
Entity Annotation을 달아준 데이터 항목을 넣어주어야 한다.

    // RoomDatabase에 데이터를 Update + Insert
    @Upsert
    suspend fun upsertContact(contact: Contact)

    // RoomDatabase에 데이터를 삭제
    @Delete
    suspend fun deleteContact(contact: Contact)

    @Query("SELECT * FROM contact ORDER BY firstName ASC")
    fun getContactsOrderedByFirstName(): Flow<List<Contact>>

    @Query("SELECT * FROM contact ORDER BY lastName ASC")
    fun getContactsOrderedByLastName(): Flow<List<Contact>>

    @Query("SELECT * FROM contact ORDER BY phoneNumber ASC")
    fun getContactsOrderedByPhoneNumber(): Flow<List<Contact>>
}


RoomDatabase Instance 생성하기

-RoomDatabase의 구성요소 3단계를 만들었다면, Room.databaseBuilder()를 활용하여 실제 RoomDatabase 객체를 생성해야 한다. RoomDatabase 객체는 단 하나의 객체만 존재해야 하기 때문에 lazy{} 코들 블록 내부에서 생성해줘야 한다. lazy{} 블록을 사용하면 단 한번만 객체를 생성하고 그 뒤로는 생성한 객체를 재사용하기 때문이다.

  • RoomDatabase 객체 생성 예시코드
>lazy를 사용하여 한 번만 객체를 만들고 그 뒤로는 재사용 하도록 하였다.
private val db by lazy {
	>Room.databaseBuilder()는 RoomDatabase Instance를 생성하는 빌더 함수이다.
Room.databaseBuilder(
	>RoomDatabase가 applicationContext에서 작동한다는 것을 알려준다.
	applicationContext,
    
    >RoomDatabase Instance를 생성할 때 사용될 Database Class를 알려준다.
	ContactDatabase::class.java,
    
    >RoomDatabase Instance의 이름을 알려준다.
	"contacts.db"
).build()
>Room.databaseBuilder()의 parameter에 설정된 옵션들을 사용하여 RoomDatabase Instance 생성한다.
    }

-Room.databaseBuilder(context , klass , name)에 알맞는 정보를 넣어준 후 build() 메서드를 호출하면 해당 정보를 사용해서 RoomDatabase Instance를 반환해준다.

실제 RoomDatabase 테이블 관리하는 방법

-

-위의 gif는 실제로 앱상에서 Room 을 구현한 예시이다.

DAO 객체를 활용예시

-현재 코드상에는 아래와 같이 이벤트를 모아놓은 sealed interface가 존재함을 참고바람.

sealed interface ContactEvent {
    object SaveContact: ContactEvent
    data class SetFirstName(val firstName: String): ContactEvent
    data class SetLastName(val lastName: String): ContactEvent
    data class SetPhoneNumber(val phoneNumber: String): ContactEvent
    object ShowDialog: ContactEvent
    object HideDialog: ContactEvent
    data class SortContacts(val sortType: SortType): ContactEvent
    data class DeleteContact(val contact: Contact): ContactEvent
}

-아래의 코드는 현재 코드의 ViewModel에서 발췌한 코드이며 UPSERT를 실행하는 과정을 설명하는데 필요한 코드이므로 참고바람.


	private val _sortType = MutableStateFlow(SortType.FIRST_NAME.string)
    private val _contacts = _sortType
        .flatMapLatest { sortType ->
            when(sortType) {
                SortType.FIRST_NAME.string -> dao.getContactsOrderedByFirstName()
                SortType.LAST_NAME.string -> dao.getContactsOrderedByLastName()
                SortType.PHONE_NUMBER.string -> dao.getContactsOrderedByPhoneNumber()
                else -> throw IllegalArgumentException("Unknown SortType")

            }
        }
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())

    private val _state = MutableStateFlow(ContactState())
    val state = combine(_state, _sortType, _contacts) { state, sortType, contacts ->
        state.copy(
            contacts = contacts,
            sortType = sortType
        )
    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), ContactState())

-현재 코드에서 실제로 RoomDatabase에 UPSERT를 가능하게 하는 코드는 다음과 같다.

  • 우선 Floating 버튼을 누른 후 등록 성 이름 전화번호부를 다 입력한 후 저장 버튼을 누르면 onEvent(ContactEvent.SaveContact)가 실행되게 되어있고 그러면 아래 코드가 실행되게 된다.
  • 아래코드를 보면 "dao.upsertContact(contact)" 코드가 보이는데 ContactDao는 인터페이스 이기 때문에 구현해야되는것 아닌가 생각이 들겠지만 사실, dao의 구현은 RoomLibrary에 의해서 자동으로 이루어지며 , 우리같은 개발자는 @Dao Annotation이 달린 인터페이스만 정의해주기만 하면된다. 즉, 인터페이스를 정의만 한다면 구현은 자동으로 이루어지므로 해당 인터페이스 내부의 함수들을 자유롭게 사용할 수 있다는 의미이다.

-즉, DAO를 활용하여 RoomDatabase의 Table을 다루는 작업은 단지 Dao 객체를 생성한 후 인터페이스 내부의 메서드를 호출하기만 하면 RoomLibrary에서 능동적으로 데이터테이블 관리를 진행해준다.

ContactEvent.SaveContact -> {
	val firstName = state.value.firstName
	val lastName = state.value.lastName
	val phoneNumber = state.value.phoneNumber

	if(firstName.isBlank() || lastName.isBlank() || phoneNumber.isBlank()) {
		return
	}

	val contact = Contact(
		firstName = firstName,
		lastName = lastName,
		phoneNumber = phoneNumber
	)
	viewModelScope.launch {
		dao.upsertContact(contact)
	}
	_state.update { it.copy(
		isAddingContact = false,
		firstName = "",
		lastName = "",
		phoneNumber = ""
	) }
}

-앱에서 어떤 클릭이벤트가 발생하거나 특정 로직이 충족 되었을 때 등등의 후행 람다식 내부에서 Dao 객체를 사용해서 RoomDatabase의 DataTable에 접근 및 관리를 활 수 있다.

profile
포기하지 말기

0개의 댓글