[Android/Kotlin] Room의 기본과 예제

최지원·2024년 1월 24일
0

[Android/Kotlin]

목록 보기
1/9

Room Database를 활용해서 내장DB를 자유자재로

🎁[Android/Kotlin] Room의 기본과 예제

🙄 Room이 뭐지?

  • SharedPreference 쓰기에는 좀더 세밀한 컨트롤이 필요할 것 같고, firebase같은 플랫폼을 사용하기엔 굳이 그럴 필요 없을 때 적합한 스마트폰 내장DB가 Room이다.
  • SQLite를 활용하여 원활한 데이터베이스 엑세스가 가능하다.

🛒 Room의 기본 구성과 특징

👌 SQLite와 Room의 차이점

  • SQLite는 저수준 데이터베이스 솔루션으로, 직접적인 데이터 베이스 작업과 SQL쿼리를 통해 작동한다.
  • Room은 SQLite의 상위 레벨 추상화를 제공하며, SQL쿼리를 직접 작성하는 대신 애노테이션과 DAO를 통해 데이터베이스와 상호작용 할 수 있게 해준다.

👌 Room의 장점

  • 컴파일 타임 검증 : Room은 SQL쿼리를 컴파일 시점에 검증하여 실행 시간 오류를 줄여준다.
  • 간결한 데이터베이스 작업 : Room은 데이터베이스 작업을 더 간결하고 직관적으로 만들어준다.
  • LiveData와의 통합 : Room은 LiveData와 같은 다른 Android Architecture Components와 쉽게 통합되어 UI와 데이트베이스 상태의 동기화를 간소화한다.
  • 자동 마이그레이션 도구 : Room은 데이트베이스 스키마 변경 시 자동으로 마이그레이션을 지원하는 도구를 제공한다.

👌 Room의 기본 구조

1. Entity

  • Entity는 Room에서 데이터베이스의 테이블을 나타내는 클래스이다. 각 Entity 클래스는 데이터베이스 내의 테이블과 매핑되며, 클래스 내의 필드는 테이블의 컬럼에 해당한다.

2. DAO

  • DAO는 데이터베이스에 접근하기 위한 메서드들을 포함하는 인터페이스 또는 추상 클래스이다. 이를 통해 애플리케이션이 데이터베이스에 저장된 데이터를 쿼리, 업데이트, 삭제, 삽입할 수 있다.

3. Database

  • Database는 데이터베이스 홀더를 포함하며, 앱의 지속적인 데이터와의 주 연결점이다. 이 클래스는 데이터베이스를 구성하고, 버전 관리를 제공한다.

📜 Room 사용 방법

매우 간단한 예제로 Room의 사용법을 살짝 알아볼까요?

EditText에 입력한 값을 저장, 가져오기, 삭제하는 예제를 만들어 봅시다.

1. gradle에 의존성 추가

build.gradle (:app)

plugins {
    id "org.jetbrains.kotlin.kapt"
}
implementation("androidx.room:room-runtime:2.5.1")
annotationProcessor("androidx.room:room-compiler:2.5.1")
implementation "androidx.room:room-ktx:2.5.1"
kapt "androidx.room:room-compiler:2.5.1"

물론 위 코드에서 Room버전은 최신버전이 아닐 수 있으므로 자세한건 공식 문서를 참고바랍니다.

Room version

2. Entity 정의

MyEntity.kt

@Entity
data class MyEntity (

    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    val text: String

)
  • @Entity 애노테이션 적용된 Room 데이터베이스를 생성.
  • @PrimaryKey(autoGenerate = true)로 해당 필드를 기본 키로 선언. 이 필드는 자동 생성되며 반드시 있어야하는 속성이다.
  • EditText에 입력된 값만 다룰 것이니 val text: String필드만 선언.

3. DAO 정의

MyDao.kt

@Dao
interface MyDao {

    @Insert
    suspend fun insert(myEntity: MyEntity)

    @Query("SELECT * FROM MyEntity")
    suspend fun getAll(): List<MyEntity>

    @Query("DELETE FROM MyEntity")
    suspend fun deleteAll()
}
  • @Dao 애노테이션으로 데이터 접근 객체 생성.
  • @insert로 MyEntity 타입의 객체를 데이터에 삽입한다.
  • @Query문으로 저장된 모든 MyEntity 타입의 객체를 가져오기 & 삭제하기 구현.
  • 코루틴을 지원하는 suspend함수로 insert함수를 선언.

Room은 코루틴과 함께 사용될 때 데이터베이스 작업을 메인 스레드에서 분리하여 블로킹이 방지되어 더 효율적인 인터페이스 반응을 유지할 수 있다.

4. Database 클래스 정의

MyAppDatabase.kt

@Database(entities = [MyEntity::class], version = 1)
abstract class MyAppDatabase :RoomDatabase(){

    abstract fun myDao(): MyDao

    companion object {
        @Volatile
        private var INSTANCE: MyAppDatabase? = null

        fun getDatabase(context: Context): MyAppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    MyAppDatabase::class.java,
                    "my-database"
                ).build()
                INSTANCE = instance
                instance
            }
        }
    }

}
  • @Database 애노테이션으로 데이터베이스 생성.
  • entities: 데이터베이스에 포함될 엔티티들을 지정.
  • version: 데이터베이스의 버전을 나타낸다. 데이터베이스 스키마가 변경될 때마다 버전을 증가시켜야 한다.
  • 데이터베이스 접근의 효율성을 위해 싱글톤으로 인스턴스 관리.

5. Room 사용하기

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/edit"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

    <Button
        android:id="@+id/saveButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/view"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:text="저장하기"/>

    <TextView
        android:id="@+id/view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/edit"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>
    <Button
        android:id="@+id/getButon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/saveButton"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginTop="10dp"
        android:text="가져오기"/>

    <Button
        android:id="@+id/deleteButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/getButon"
        app:layout_constraintStart_toStartOf="@id/getButon"
        app:layout_constraintEnd_toEndOf="@id/getButon"
        android:layout_marginTop="10dp"
        android:text="삭제하기"/>
</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity.kt

class MainActivity : AppCompatActivity() {

    private lateinit var binding : ActivityMainBinding
    private lateinit var database: MyAppDatabase

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        database = MyAppDatabase.getDatabase(this)

        binding.saveButton.setOnClickListener {
            val text = binding.edit.text.toString()
            if (text.isNotEmpty()) {
                saveText(text)
            }
        }

        binding.getButon.setOnClickListener {
            loadTexts()
        }

        binding.deleteButton.setOnClickListener {
            deleteTexts()
        }
    }

    private fun saveText(text:String){
        CoroutineScope(Dispatchers.IO).launch {
            database.myDao().insert(MyEntity(text=text))
            binding.edit.text = null
        }
    }

    private fun loadTexts() {
        CoroutineScope(Dispatchers.IO).launch {
            val texts = database.myDao().getAll().joinToString("\n") { it.text }
            withContext(Dispatchers.Main) {
                binding.view.text = texts
            }
        }
    }

    private fun deleteTexts(){
        CoroutineScope(Dispatchers.IO).launch {
            database.myDao().deleteAll()
            loadTexts()
        }
    }
}
  • 3개의 버튼으로 삽입, 가져오기, 삭제.
  • dao함수가 suspend이므로 CoroutineScope로 비동기 처리.


공식 문서

profile
안드로이드, 플러터 주니어입니다

0개의 댓글