정상에서 IT를 외치다

[Android, Room] Room One to Many 관계 살펴보기 본문

안드로이드

[Android, Room] Room One to Many 관계 살펴보기

Black-Jin 2020. 9. 19. 15:54
반응형

안녕하세요. 블랙진입니다.


안드로이드 라이브러리 Room에 관한 포스팅을 해보겠습니다. 여기서 주로 다룰 내용은 Room의 One to Many 관계에 대해서 예제 코드와 함께 다뤄 보겠습니다.



Room 이란?


안드로이드 아키텍처 컴포넌트(AAC) 룸 라이브러리는 안드로이드 앱에서 SQLite 데이터베이스를 쉽고 편리하게 사용할 수 있도록 해주는 라이브러리입니다. 간단한 사용법은 이전 저의 포스팅을 확인해 주시면 감사하겠습니다.


Room 라이브러리 사용하기




One to Many 관계


One to Many 관계를 폴더와 파일을 예시로 작업해 보겠습니다. 여러분도 아시다시피 한 폴더에는 여려개의 파일이 들어갈 수 있습니다. 



1. Entity

@Entity(tableName = "folders")
data class FolderEntity(
@PrimaryKey(autoGenerate = true)
val folderId: Int = 0,
val name: String
)

@Entity(tableName = "files")
data class FileEntity(
@PrimaryKey(autoGenerate = true)
val fileId: Int = 0,
val parentFolderId: Int,
val name: String
)

data class FolderAndFiles(
@Embedded val folder: FolderEntity,

@Relation(
parentColumn = "folderId",
entityColumn = "parentFolderId"
)
val files: List<FileEntity>
)


폴더와 파일의 Entity를 각각 생성해 줍니다. 여기서 파일에는 폴더에 대한 정보가 필요하기 때문에 parentFolderId를 추가해 줍니다.

FolderAndFiles는 폴더와 파일의 Entity를 같이 보여주기 위한 클래스 입니다.


@Embedded

Room에서 obejct를 표현하기 위한 어노테이션입니다.


@Relation

부모와 자식 관계를 정의하기 위한 어노테이션입니다. 

parentColum에는 부모의 값을, entityColumn에는 자식에서 사용하고 있는 부모의 값을 보여줍니다.



2. Dao


폴더와 관련된 Dao

/**
* Folder
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertFolder(folder: FolderEntity)

@Delete
fun deleteFolder(folder: FolderEntity)

@Query("SELECT * FROM folders ORDER BY folderId DESC") //ASC
fun getFolders(): List<FolderEntity>

@Query("SELECT * FROM folders WHERE name = :name")
fun getFolderByName(name: String): FolderEntity

@Query("DELETE FROM folders WHERE name = :name")
fun deleteFolderByName(name: String)

@Query("DELETE FROM folders")
fun clearFolders()


파일과 관련된 Dao

/**
* File
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertFile(file: FileEntity)

@Delete
fun deleteFile(file: FileEntity)

@Query("SELECT * FROM files")
fun getFiles() : List<FileEntity>

@Query("DELETE FROM files WHERE name = :name")
fun deleteFilerByName(name: String)

@Query("DELETE FROM files")
fun clearFiles()


폴더과 파일의 관계를 보여주는 Dao

/**
* Folder And File
*/
@Transaction
@Query("SELECT * FROM folders ORDER BY folderId DESC") //ASC
fun getFolderAndFilesData() : List<FolderAndFiles>


@Transaction

위 예제의 경우 폴더와 파일을 가져오기 위해 Room에서는 두개의 쿼리문이 돌아갑니다. 이 과정을 자동으로 이뤄지게 하기 위한 어노테이션입니다.



3. 문제 인식


폴더와 파일의 관계가 잘 동작됩니다. 하지만 위 예제 코드에서는 파일을 지우면 그 안에 있는 폴더는 지워지지는 않습니다. 


그럼 폴더가 지워졌을 때 파일까지 함께 지워지기 위해서는 어떻게 작업해야 될까요? 수동으로 쿼리문을 동작시켜 주면 되겠지만 저희가 원하는 대답은 이게 아니겠죠! 여기서 ForeignKey를 등록시켜 줌으로써 데이터의 관계를 정의할 수 있습니다.



4. ForeginKey 적용

@Entity(
tableName = "files",
foreignKeys = [
ForeignKey(
entity = FolderEntity::class,
parentColumns = arrayOf("folderId"),
childColumns = arrayOf("parentFolderId"),
onDelete = ForeignKey.CASCADE
)
]
)
data class FileEntity(
@PrimaryKey(autoGenerate = true)
val fileId: Int = 0,
val parentFolderId: Int,
val name: String
)


FileEntity에 위와 같이 ForeignKey를 적용해주면 부모 폴더가 제거되면 그 안에 있던 자식 파일들도 모두 제거할 수 있습니다. 위에서 중요하게 볼 것은 onDelete 속성인데요. onDelete 속성 NO_ACTION, RESETICT, SET_NULL, SET_DEFAUT, CASCADE 값을 적용할 수 있으며 이는 SQLIte에서 사용하던 것과 동일합니다.


여기서 CASCADE를 사용하게 되면 부모가 삭제되거나 수정되게 되면 자식 또한 삭제 혹은 수정되게 됩니다.



5. 부모 폴더 생성


여기서 더 나아가 우리는 이미 알고 있습니다! 폴더 안에 파일만 넣을 수 있는게 아니라 폴더를 넣을 수 있고 그 폴더안에 또 폴더를 넣을 수 있습니다. 폴더안에 폴더를 넣을 수 있는 구조로 코드를 변환해 보겠습니다.


@Entity(tableName = "folders",
foreignKeys = [
ForeignKey(
entity = FolderEntity::class,
parentColumns = arrayOf("folderId"),
childColumns = arrayOf("parentId"),
onDelete = ForeignKey.CASCADE
)])
data class FolderEntity(
@PrimaryKey(autoGenerate = true)
val folderId: Int = 0,
val parentId: Int? = null,
val name: String
)


간단하게 자식 폴더에 부모 폴더의 정보만 추가시켜주면 우리가 알고있는 폴더와 파일 구조를 Room으로 모두 구현할 수 있게 됩니다!



정리


Room의 One to Many 관계에 대한 포스팅은 많이 있습니다. 하지만 좀더 구체적인 예제와 함께 정리하고 싶어 포스팅을 작성하게 되었습니다. 위에서 사용한 예제는 깃허브 링크에서 확인할 수 있습니다.

반응형
Comments