Room

오라클, mysql과 같은 데이터베이스를 사용하기 위해서는 외부 데이터베이스 구축과 웹서버가 필요하다. 다시 말해 인터넷이 연결되어있어야 한다. 그러나 로컬 데이터베이스인 SQlite를 활용하면 데이터베이스가 앱 내부에 구축되어서 인터넷이 연결되지 않아도 사용할 수 있는 이점이 있다. 그리고 안드로이드는 SQlite를 쉽게 사용할 수 있는 Room을 지원하고 있다.

 

MainThread에서 동작하지 않는 Room

안드로이드는 Room이 메인 스레드를 오래 점유할 것을 염려하여 메인 스레드에서의 Room 동작을 차단하고 있다. 따라서 이 글에서는 메인 스레드에서 Room동작을 허용하는 방법, Coroutine을 활용하는 방법 두 가지를 사용한다. 기본적인 Room 구현을 작성하기 위해 메인 스레드에서 Room동작을 허용하는 방법도 설명하지만 실제 구현은 코루틴과 같은 다른 방법을 사용하기를 권장한다.

코루틴에 대한 개념은 아래 글을 추천한다.

 

 

코틀린 코루틴(coroutine) 개념 익히기 · 쾌락코딩

코틀린 코루틴(coroutine) 개념 익히기 25 Aug 2019 | coroutine study 앞서 코루틴을 이해하기 위한 두 번의 발악이 있었지만, 이번에는 더 원론적인 코루틴에 대해서 알아보려 한다. 코루틴의 개념이 정확

wooooooak.github.io

 

메인 스레드에서 Room 동작을 허용하여 DB구축

이 글은 데이터베이스 구축에 필요한 필수사항만 작성되었다. 추가적인 내용은 아래 글을 참고한다.

https://developer.android.com/reference/android/arch/persistence/room/Entity?hl=en

https://developer.android.com/training/data-storage/room/defining-data?hl=ko

https://codelabs.developers.google.com/codelabs/android-room-with-a-view-kotlin/?hl=ko#0

 

build.gradle(:app)

Room을 사용하기 위해서 build.gradle를 수정한다.

상단에 아래 플러그인을 추가한다.

 

apply plugin: 'kotlin-kapt'

 

또한 dependencies에 항목을 추가한다.

 

dependencies {
    ....
    implementation "androidx.room:room-runtime:2.2.5"
    kapt "androidx.room:room-compiler:2.2.5"
}

 

Entity

아래 글들부터는 모두 MainActivity.kt에 작성되었다.

 

@Entity에서 테이블을 정의한다.

아래 엑셀과 같은 테이블을 만든다.

 

 

@Entity
data class NationalWeatherTable(
    @PrimaryKey val code: Int,
    val name1: String,
    val name2: String,
    val name3: String,
    val x: Int,
    val y: Int
)

 

클래스 이름이 테이블 이름이며 @PrimaryKey는 기본키를 나타낸다.

 

Dao

Dao는 데이터베이스에 접근하는 인터페이스이며 쿼리들을 정의할 수 있다.

각 쿼리 다음 줄에 작성된 함수로 쿼리를 사용한다.

 

@Query, @Insert, @Update, @Delete가 있다.

 

@Dao
interface NationalWeatherInterface {
    @Query("SELECT * FROM NationalWeatherTable")
    fun getAll(): List<NationalWeatherTable>

    @Insert
    fun insert(nationalWeatherTable: NationalWeatherTable)

    @Query("DELETE FROM NationalWeatherTable")
    fun deleteAll()
}

 

데이터베이스 정의

작성한 Entity, Dao를 이용하여 데이터베이스를 정의한다.

아래 코드는 단일 테이블인 경우이다. 여러 테이블이 필요할 경우 위에 소개한 글을 참고한다.

 

@Database(entities = [NationalWeatherTable::class], version = 1)
abstract class AppDatabase: RoomDatabase() {
    abstract fun nationalWeatherInterface(): NationalWeatherInterface
}

 

version이 바뀔 경우 데이터베이스의 내용이 초기화된다. 로컬 데이터베이스이기 때문에 앱을 초기화하여도 마찬가지다.

 

데이터베이스 빌드

정의한 데이터베이스를 생성한다.

이때 .allowMainThreadQueries()를 이용하여 메인 스레드에서 Room을 사용할 수 있게 허용해준다.

 

val NationalWeatherDB = Room.databaseBuilder(this, AppDatabase::class.java,"db").allowMainThreadQueries().build()

 

쿼리 사용과 로그 확인

 

// Query
val input = NationalWeatherTable(1114062500,"seoul", "jongrogu", "dasandong", 60, 126)
NationalWeatherDB.nationalWeatherInterface().deleteAll()
NationalWeatherDB.nationalWeatherInterface().insert(input)
// Log
var output = NationalWeatherDB.nationalWeatherInterface().getAll()[0]
Log.d("db_test", "$output")

 

// 로그캣 결과
D/db_test: NationalWeatherTable(name1=seoul, name2=jongrogu, name3=dasandong, x=60, y=126)

 

MainActivity.kt 전체 코드

 

@Entity
data class NationalWeatherTable(
    @PrimaryKey val code: Int,
    val name1: String,
    val name2: String,
    val name3: String,
    val x: Int,
    val y: Int
)

@Dao
interface NationalWeatherInterface {
    @Query("SELECT * FROM NationalWeatherTable")
    fun getAll(): List<NationalWeatherTable>

    @Insert
    fun insert(nationalWeatherTable: NationalWeatherTable)

    @Query("DELETE FROM NationalWeatherTable")
    fun deleteAll()

}

@Database(entities = [NationalWeatherTable::class], version = 1)
abstract class AppDatabase: RoomDatabase() {
    abstract fun nationalWeatherInterface(): NationalWeatherInterface
}

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val NationalWeatherDB = Room.databaseBuilder(this, AppDatabase::class.java,"db").allowMainThreadQueries().build()

        val input = NationalWeatherTable(1114062500,"seoul", "jongrogu", "dasandong", 60, 126)
        NationalWeatherDB.nationalWeatherInterface().deleteAll()
        NationalWeatherDB.nationalWeatherInterface().insert(input)
        var output = NationalWeatherDB.nationalWeatherInterface().getAll()[0]
        Log.d("db_test", "$output")
    }
}

 

코루틴을 사용한 DB 구축

위의 작성된 코드를 수정하는 방향으로 진행한다.

 

build.gradle(:app)

코루틴을 사용하기 위해서 모듈을 추가한다.

 

dependencies {
    ...
    implementation "androidx.room:room-runtime:2.2.5"
    kapt "androidx.room:room-compiler:2.2.5"
    implementation "androidx.room:room-ktx:2.2.5" // Added
}

 

Dao

작성했던 쿼리 함수에 suspend를 추가한다.

 

@Dao
interface NationalWeatherInterface {
    @Query("SELECT * FROM NationalWeatherTable")
    suspend fun getAll(): List<NationalWeatherTable>

    @Insert
    suspend fun insert(nationalWeatherTable: NationalWeatherTable)

    @Query("DELETE FROM NationalWeatherTable")
    suspend fun deleteAll()
}

 

데이터베이스 빌드

.allowMainThreadQueries()를 제거한다.

 

val NationalWeatherDB = Room.databaseBuilder(this, AppDatabase::class.java,"db").build()

 

쿼리 사용과 로그 확인

쿼리 함수들을 서브루틴에서 실행하기 위해 CoroutineScope(Dispatchers.IO).launch { } 내부로 이동시킨다.

Dispatchers.IO는 서브루틴을 IO스레드에서 실행시킨다.

 

CoroutineScope(Dispatchers.IO).launch {
    NationalWeatherDB.nationalWeatherInterface().deleteAll()
    NationalWeatherDB.nationalWeatherInterface().insert(input)
    var output = NationalWeatherDB.nationalWeatherInterface().getAll()[0]
    Log.d("db_test", "$output")
}

 

 

MainActivity.kt 전체 코드

 

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.util.Log
import androidx.room.*
import kotlinx.coroutines.*

@Entity
data class NationalWeatherTable(
    @PrimaryKey val code: Int,
    val name1: String,
    val name2: String,
    val name3: String,
    val x: Int,
    val y: Int
)

@Dao
interface NationalWeatherInterface {
    @Query("SELECT * FROM NationalWeatherTable")
    suspend fun getAll(): List<NationalWeatherTable>

    @Insert
    suspend fun insert(nationalWeatherTable: NationalWeatherTable)

    @Query("DELETE FROM NationalWeatherTable")
    suspend fun deleteAll()

}

@Database(entities = [NationalWeatherTable::class], version = 1)
abstract class AppDatabase: RoomDatabase() {
    abstract fun nationalWeatherInterface(): NationalWeatherInterface
}

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val NationalWeatherDB = Room.databaseBuilder(this, AppDatabase::class.java,"db").build()
        val input = NationalWeatherTable(1114062500,"seoul", "jongrogu", "dasandong", 60, 126)
        CoroutineScope(Dispatchers.IO).launch {
            NationalWeatherDB.nationalWeatherInterface().deleteAll()
            NationalWeatherDB.nationalWeatherInterface().insert(input)
            var output = NationalWeatherDB.nationalWeatherInterface().getAll()[0]
            Log.d("db_test", "$output")
        }
    }
}

 

참고 깃허브

https://github.com/HydroponicGlass/2021_Example_Android/tree/main/Room