Android/MVVM (based on AAC)

Room

네모메모 2022. 7. 6. 22:31
반응형

Room

: SQLite(스마트폰 내장 DB)에 데이터를 저장하기 위해 사용하는 라이브러리

 

- SQLite를 활용해서 객체 매핑을 해주는 역할을 한다.(SQLite에 추상화 계층을 제공)

- Room을 사용하면 다음과 같은 이점이 있습니다.

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

 

 

 


 

0.설정

dependencies {
    val roomVersion = "2.4.2"

    implementation("androidx.room:room-runtime:$roomVersion")
    annotationProcessor("androidx.room:room-compiler:$roomVersion")

    // To use Kotlin annotation processing tool (kapt)
    kapt("androidx.room:room-compiler:$roomVersion")
    // To use Kotlin Symbolic Processing (KSP)
    ksp("androidx.room:room-compiler:$roomVersion")

ㄴ> 사용 부분에 따라 아래와 같이 의존성 더 추가 

    // optional - Kotlin Extensions and Coroutines support for Room
    implementation("androidx.room:room-ktx:$roomVersion")

    // optional - RxJava2 support for Room
    implementation("androidx.room:room-rxjava2:$roomVersion")

    // optional - RxJava3 support for Room
    implementation("androidx.room:room-rxjava3:$roomVersion")

    // optional - Guava support for Room, including Optional and ListenableFuture
    implementation("androidx.room:room-guava:$roomVersion")

    // optional - Test helpers
    testImplementation("androidx.room:room-testing:$roomVersion")

    // optional - Paging 3 Integration
    implementation("androidx.room:room-paging:2.5.0-alpha02")
}

 

 

 


 

Room 구성요소 3가지

 

- 동작순서

1) '데이터베이스 클래스'는 데이터베이스와 연결된 'DAO 인스턴스'를 '앱'에 제공합니다.

2) 그러면 '앱'은 'DAO'를 사용하여 데이터베이스의 데이터를 연결된 데이터 항목 객체의 인스턴스로 검색할 수 있게 됩니다.

3) 은 정의된 데이터 항목을 사용하여 상응하는 테이블의 행을 업데이트하거나 삽입할 새 행을 만들 수도 있습니다.

 

ㄴ> 위 사진에서 Room Database, Data Access Objects, Entities 이렇게 3개가 Room의 구성 요소이고

Rest of The App은 앱의 나머지 부분을 뜻한다.

 

 

 


 

1. Room 구성요소들 생성

1-1) Entity를 생성해야 한다. (음...DB 테이블을 만든다고 보자)

- data class에 @Entity 어노테이션을 붙여주고 저장하고 싶은 속성의 변수 이름과 타입을 정해준다.

- primaryKey는유일한(Unique) 값이어야 한다. 직접 지정해도 되지만 autoGenerate를 true로 주면 자동으로 값을 생성한다.

@Entity
data class User(
    @PrimaryKey(autoGenerate = true) val uid: Int,
    @ColumnInfo(name = "first_name") val firstName: String?,
    @ColumnInfo(name = "last_name") val lastName: String?
)

 

 

1-2) DAO를 생성

- @Dao 어노테이션을 붙이고 그 안에 메서드를 정의하게 되는데

@Insert를 붙이면 테이블에 데이터 삽입

@Update를 붙이면 테이블의 데이터 수정

@Delete를 붙이면 테이블의 데이터 삭제

@Query 어노테이션을 붙이고 그 안에 어떤 동작을 할 건지 sql 문법으로 작성

@Dao
interface UserDao {
    @Query("SELECT * FROM user")
    fun getAll(): List<User>

    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    fun loadAllByIds(userIds: IntArray): List<User>

    @Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
           "last_name LIKE :last LIMIT 1")
    fun findByName(first: String, last: String): User

    @Insert
    fun insertAll(vararg users: User)

    @Delete
    fun delete(user: User)
}

ㄴ> DAO에 관한 다양한 활용은 Room DAO를 사용하여 데이터 액세스를 참고

 

 

1-3) 데이터베이스를 보유할 'Database 클래스'를 정의

- @Database 어노테이션으로 데이터베이스임을 표시하고,

   데이터베이스와 연결된 데이터 항목을 모두 나열하는 entities (<< 1-1번에서 생성함) 배열이 포함되어야 한다.

  ㄴ> version : 앱을 업데이트하다가 entity의 구조를 변경해야 하는 일이 생겼을 때 이전 구조와 현재 구조를 구분해주는 역할 (만약 구조가 바뀌었는데 버전이 같다면 에러가 뜨며 디버깅이 되지 않는다.) (처음은 그냥 1을 넣어주면 된다.)

 

- RoomDatabase를 확장하는 추상 클래스

 

- 데이터베이스와 연결된 각 DAO 클래스에서 데이터베이스 클래스는 인수가 0개이고 DAO 클래스의 인스턴스를 반환하는 추상 메서드를 정의해야 합니다.

@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
}

 

- 여러 엔티티 사용 시

더보기

@Database(entities = arrayOf(User::class, Student::class), version = 1)
abstract class UserDatabase: RoomDatabase() {
    abstract fun userDao(): UserDao
}

 

 

- 참고) 앱이 단일 프로세스에서 실행되면 AppDatabase 객체를 인스턴스화할 때 싱글톤 디자인 패턴을 따르고,

여러 프로세스에서 실행되는 경우 데이터베이스 빌더 호출에 enableMultiInstanceInvalidation()을 포함

더보기

참고: 앱이 단일 프로세스에서 실행되면 AppDatabase 객체를 인스턴스화할 때 싱글톤 디자인 패턴을 따라야 합니다. 각 RoomDatabase 인스턴스는 리소스를 상당히 많이 소비하며 단일 프로세스 내에서 여러 인스턴스에 액세스해야 하는 경우는 거의 없습니다.

 

앱이 여러 프로세스에서 실행되는 경우 데이터베이스 빌더 호출에 enableMultiInstanceInvalidation()을 포함하세요. 이렇게 하면 각 프로세스에 AppDatabase 인스턴스가 있을 때 한 프로세스에서 공유 데이터베이스 파일을 무효화할 수 있으며 이 무효화는 다른 프로세스 내의 AppDatabase 인스턴스로 자동 전파됩니다.

 

ㄴㄴ> AppDatabase 객체를 인스턴스화할 때 싱글톤 디자인 패턴으로 ▽

@Database(entities = [User::class], version = 1)
abstract class UserDatabase: RoomDatabase() {
    abstract fun userDao(): UserDao
 
    companion object {
        private var instance: UserDatabase? = null
 
        @Synchronized
        fun getInstance(context: Context): UserDatabase? {
            if (instance == null) {
                synchronized(UserDatabase::class){
                    instance = Room.databaseBuilder(
                        context.applicationContext,
                        UserDatabase::class.java,
                        "user-database"
                    ).build()
                }
            }
            return instance
        }
    }
}

 


2. Room 사용

// 1. 데이터베이스 객체 정의
        // 싱글톤 패턴을 사용한 경우
        val db = UserDatabase.getInstance(applicationContext)
        db!!.userDao().insert(newUser)

        // 싱글톤 패턴을 사용하지 않은 경우
        /**
        val db = Room.databaseBuilder(
            applicationContext,
            AppDatabase::class.java, "database-name"
        ).build()
        */

// 2. AppDatabase의 추상 메서드를 사용하여 DAO 인스턴스를 가져와 데이터베이스와 상호작용
        val userDao = db.userDao()
        val users: List<User> = userDao.getAll()

        var newUser = User("김동길", "20", "010-1111-5555")
        userDao.insert(newUser)

 

이제 실행하면

"Cannot access database on the main thread since it may potentially lock the UI for a long period of time" 에러나온다.

메인 스레드에게 DB 같은 요런 거 시키지 말라고 하는 말이니 비동기 처리한다.

개인적으로 사용해야 하므로 코루틴을 사용하겠다.

 

class MainActivity : AppCompatActivity() {

    private lateinit var db: UserDatabase

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
       
        db = UserDatabase.getInstance(applicationContext)!!
        getAllUserList()
    }
    
    private fun getAllUserList(){
        var userList: String

        CoroutineScope(Dispatchers.Main).launch {
            val users = CoroutineScope(Dispatchers.IO).async {
                db.userDao().getAll()
            }.await()

            for(user in users){
                userList += "name: ${user.name}, age: ${user.age}, phone: ${user.phone}\n"
            }
            Log.d(TAG, ">> getAllUserList \n ${userList}")
        }
    }

}

 

 

 

 

 

 

 


 

기타

+) 대량의 데이터를 처리하게 될 경우는 Room보다 Realm을 사용하면 좋다. 

+) 정말 간단한 정보를 저장할 경우 Room보다 sharedpreferences를 사용하면 좋다. 

 

 


[출처]

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

- https://todaycode.tistory.com/39

 

 

 

반응형