[Android/Kotlin] Room Database 사용하기

peace w·2024년 2월 28일
0

안드로이드 todo 앱을 개발하면서 데이터를 저장하기 위해 Room Database를 사용했다.
Room Database는 SQLite를 완벽히 활용하면서 원활한 데이터베이스 액세스가 가능하도록 SQLite의 추상화 계층을 제공한다. Android Developers 공식 가이드 문서에는 다음과 같은 이유로 Room Database의 사용을 적극 권장하고 있다.

Room 의 장점

  • SQL 쿼리의 컴파일 시간 확인
  • 반복적이고 오류가 발생하기 쉬운 상용구 코드를 최소화하는 편의 주석
  • 간소화된 데이터베이스 이전 경로

Room 의 구조


Room에는 3가지 주요 구성요소가 있다.

  • 데이터베이스 클래스: 데이터베이스를 보유하고 앱의 영구 데이터와의 기본 연결을 위한 기본 액세스 포인트 역할을 함.
  • 데이터 항목: 앱 데이터베이스의 테이블을 나타냄.
  • 데이터 액세스 객체(DAO): 앱이 데이터베이스의 데이터를 쿼리, 업데이트, 삽입, 삭제하는 데 사용할 수 있는 메서드를 제공함.

Room 으로 데이터 베이스 구현하기

Entity 정의

@Entity(tableName = "todolist")
data class Todolist(
	...
}

@Entity 어노테이션을 달아서 각 Room 항목을 정의할 수 있다.
이때, 클래스는 반드시 data class 여야한다.
일반적으로 Room은 클래스 이름테이블 이름으로 사용하지만, 따로 지정할 거라면 tableName 속성으로 테이블이름을 정의할 수 있다.

@Entity(tableName = "todolist")
data class Todolist(
    @ColumnInfo(name = "title")
    var title: String,
    @ColumnInfo(name = "contents")
    var contents: String,
    @ColumnInfo(name = "category")
    var category: String,
    @ColumnInfo(name = "todo_check")
    var todoCheck: Boolean = false,
    @ColumnInfo(name = "regdate")
    var regdate: Date = Date()
) {
    @PrimaryKey(autoGenerate = true)
    var listId: Long = 0
    
    override fun toString(): String {
        return "title = $title, " +
                "contents = $contents, " +
                "category = $category, " +
                "regdate = $regdate, " +
                "todo_check = $todo_check"
    }
}

@ColumnInfo(name = "") 으로 각 컬럼을 정의한다.
Room 에서는 필드 이름데이터베이스 열 이름으로 사용한다. 테이블 및 열 이름은 대소문자를 구분하지 않는다.

@PrimaryKey 로 프라이머리 키를 설정할 수 있다.
프라이머리 키가 두 개 이상이라면 @Entity(primaryKeys = ["키이름","키이름"])으로 정의하면 된다.
autoGenerate = true 설정으로 자동으로 id에 index 값을 할당 가능하다.

※ autoGenerate 속성의 경우 값을 지정하지 않아도 가능하지만, 값을 지정할거라면 0으로 설정해야 자동으로 생성된다. 1처럼 이외의 값을 넣으면 insert 하는 값마다 전부 1을 id로 가지게 되어 android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: 테이블 이름.프라이머리키 이름 (code 1555 SQLITE_CONSTRAINT_PRIMARYKEY) 오류가 발생한다.

DAO 정의

인터페이스나 추상클래스로 정의 가능하다.

package com.example.samgim.ui.DB

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import java.util.Date

@Dao
interface TodolistDAO {
    // 삽입
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertTodos(vararg todolist: Todolist)

    @Insert
    fun insertTodo(todolist: Todolist)

    // 모든 투두 조회
    @Query("SELECT * FROM todolist ORDER BY regdate DESC, listId ASC")
    fun getAll(): List<Todolist>

    // 특정 아이디 투두 조회
    @Query("SELECT * FROM todolist WHERE listId = :listId")
    fun getTodoById(listId: Long): Todolist?

    // 갱신
    @Update
    fun updateTodos(vararg todolist: Todolist)

    // 아이디로 특정 투두 삭제
    @Query("DELETE FROM todolist WHERE listId = :listId")
    fun deleteTodos(listId: Long)
}

DAO 클래스에는 반드시 @Dao 라고 달아야 한다.
SQL 작성 없이 데이터베이스를 수정하거나 삭제할 수 있는 메서드를 제공하며, 직접 쿼리를 작성해서 데이터를 가공하는 것도 가능하다. 상황에 따라 적절한 어노테이션을 달아줘야 한다.

@Insert : 삽입
@Update : 업데이트
@Delete : 삭제
@Query : SQL 문 작성

Database 클래스 정의

@Database(entities = [Todolist::class], version = 1)
@TypeConverters(Converters::class)
abstract class TodolistDB: RoomDatabase() {
    abstract fun getDAO() : TodolistDAO

    companion object {
        private var INSTANCE: TodolistDB? = null

        fun getInstance(context: Context): TodolistDB {
            if(INSTANCE == null) {
                synchronized(this){
                    INSTANCE = Room.databaseBuilder(
                        context.applicationContext,
                        TodolistDB::class.java,
                        "todo_database"
                    ).createFromAsset("database/todo_database.db").build()
                }
            }
            return INSTANCE!!
        }
        fun destroyInstance() {
            INSTANCE = null
        }
    }
}

@Database 어노테이션으로 Database 클래스임을 선언한다.
entities로 작성해둔 Entity 클래스 명을 적어줘야 한다.
version은 처음엔 1을 사용하고, Entity의 구조를 변경할 일이 생겼을 경우에 이전 구조와 현재 구조를 구분하는 역할을 한다.

앱의 assets/ 디렉터리에 있는 데이터베이스 파일에서 Room 데이터베이스를 미리 채우고 싶다면 RoomDatabase.Builder 객체에서 createFromAsset() 메서드를 호출하고 난 다음에,build()를 호출해야 한다.

Room.databaseBuilder(context.applicationContext,
                     TodolistDB::class.java,
                     "todo_database")
                     .createFromAsset("database/todo_database.db")
                     .build()

database 폴더 안의 todo_database.db 파일로부터 "todo_database"라는 이름의 Room 데이터베이스를 미리 채우고 있다.

Room 사용하기

투두를 저장, 수정, 삭제, 조회하는 기능을 구현했다.
데이터베이스 작업은 메인 스레드에서 작업할 수 없기에 코루틴을 이용하여 비동기로 처리했다.

class 액티비티명 : AppCompatActivity() {
	private var todoDB : TodolistDB? = null
    
    override fun onCreate(saveInstanceState: Bundle?) {
    	super.onCreate(savedInstaceState)
        ...
        생략
        ...
        
        // TodolistDB 인스턴스를 초기화
        todoDB = TodolistDB.getInstance(applicationContext)
        
        CoroutineScope(Dispatchers.IO).launch {
        	// 백그라운드 작업
        	val todolist = todoDB?.getDAO()?.getTodoById(todoId)
            ...
            생략
        	
        }
    }
}
profile
더 성장하자.

0개의 댓글