ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • (AAC) Room DB
    안드로이드 학습/Android 기술면접 대비 코드 2023. 6. 14. 10:59
    차례:
    1. Room 라이브러리란?
    2. SQLite과 Room
    3. 기본 구성요소
    4. 코드 예제

     

    1. Room 라이브러리란?

    Room 라이브러리는 JetPack이라는 구글에서 제공하는 안드로이드 컴포넌트 라이브러리 모음중에 AAC에 속하는 라이브러리이다. (참고: 링크)

     

    스마트폰의 Local DB에 데이터를 저장하기 위해 사용하는 라이브러리이다.

     

    원래 Room 데이터베이스 라이브러리가 나오기전에는 SQLite를 사용해서 내부 DB를 만들어 데이터를 저장했었다.

     

    2. SQLite과 Room

    기존에 사용해왔던  SQLite 몇가지 단점들이 존재한다. 

    생각해보면 잘쓰고 있었으면 굳이 다른게 나올 이유가 없긴 하다 

    • Query의 유효성 검사 기능을 제공하지 못했던 점
    • Scheme가 바뀔 때 자동적으로 업데이트를 하지 못했던 점
    • ORM 지원이 안 되어 데이터를 객체로 변환시키기 위해 데이터 처리를 더 해야했던 점
    • Observer 패턴의 데이터(LiveData, RxJava)를 생성하고 동작시키기 위해 추가적인 Boiler Plate 코드를 작성해야하는 점

    이러한 단점들을 보완한게 Room 데이터베이스 라이브러리고 현재는 구글에서도 Room을 사용하는 것을 추천하고 있다.

     

    Room 라이브러리에서 개선되거나 추가 된 것을 알아보자면

    • Room은 컴파일하는 시간에 SQL 유효성 검사를 수행한다. (개선)
    • Scheme가 바뀌었을 때 영향 받는 SQL 쿼리를 직접 바꾸지 않아도 된다. (개선)
    • 상용구 코드 없이 DB 객체를 자바 객체에 매핑한다. (개선)
    • LiveData, RxJava와 같이 작동할 수 있다. (추가)

    좀 더 자세한 내용

    더보기

    1. Room은 컴파일하는 시간에 SQL 유효성 검사를 수행한다. (개선)

     

    Room은 컴파일 시간에 SQL 유효성 검사를 수행하여 개발자가 잘못된 Query를 사용하는 것을 방지합니다.

    예를 들어 User 내용이 바뀌어서 age가 사라졌다고 하면 런타임이 아니라 컴파일때 알려주기 때문에 더 빠르게 알 수 있다.

    @Dao
    public interface UserDao {
        @Query("SELECT * FROM users WHERE age > :minAge")
        List<User> getUsersOlderThan(int minAge);
    }​

     

    2.Scheme 변경 시 자동 업데이트 기능:

     

    SQLite에서는 Scheme이 변경되었을 때 개발자가 수동으로 데이터베이스를 업데이트해야 했습니다. 그러나 Room은 Scheme 변경 시 자동으로 업데이트를 수행할 수 있는 기능을 제공합니다.

     

    @Database(entities = {User.class}, version = 2)
    public abstract class AppDatabase extends RoomDatabase {
        public abstract UserDao userDao();
    }

     

    3. 상용구 코드 없이 DB 객체를 자바 객체에 매핑한다.

     

    @Entity 어노테이션을 사용하여 User 클래스를 데이터베이스 테이블에 매핑합니다. 이를 통해 객체와 데이터베이스 간의 매핑이 간단해지고, 데이터를 객체로 변환하기 위해 추가적인 데이터 처리 작업이 필요하지 않습니다.

    @Entity
    public class User {
        @PrimaryKey
        public int id;
        public String name;
        public int age;
    }
     

    3. 기본 구성요소

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

    (1) MainActivity 같은 곳에서 RoomDB class를 이용해 DAO (Data Access Objects)를 가져오고 

    (2) Dao를 이용해서 Entities를 CRUD를 수행하고

    (3) Entities를 통해 getter setter 메소드로 데이터를 가져온다.

     

    4. 코드 예제

    1. build.gradle

     

    build.gradle (app)

    plugins {
        id 'com.android.application'
        id 'org.jetbrains.kotlin.android'
        id 'kotlin-kapt'
    }
    
    ... 생략 
    
    dependencies {
        var room_version = "2.5.1"
        implementation("androidx.room:room-runtime:$room_version")
        annotationProcessor("androidx.room:room-compiler:$room_version")
        kapt("androidx.room:room-compiler:$room_version")
    }

    kapt("androidx.room:room-compiler:$room_version") 를 사용하기 위해서는 plugins에 'kotlin-kapt' 도 넣어줘야 한다는 것을 처음 알았다...

     

    build.gradle (project)

    plugins {
        id 'com.android.application' version '7.3.1' apply false
        id 'com.android.library' version '7.3.1' apply false
        id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
    }

     

    2. User.kt

     

    @Entity
    data class User(
        @ColumnInfo(name = "name") val name: String?
    ){
        @PrimaryKey(autoGenerate = true)
        var id: Int = 0
    }

     

     

    @Entity(tableName = “table_name”)

    • Table 정의 어노테이션
    • 기본적으로 Room은 클래스 이름을 Dabatabse 테이블 이름으로 사용합니다.
    • 다르게 지정하기 위해서는 tableName 을 선언해야 합니다.
    • SQLite의 테이블 이름은 대소문자를 구분하지 않습니다.

     

    @PrimaryKey

    • 각각의 Entity에서는 최소 한 개의 필드에 PrimaryKey 어노테이션을 선언해야 한다.

     

    @ColumnInfo

    • 테이블을 구성하는 필드 

     

    @Ignore : 

    • 유효하지 않는 필드일 경우 무시할 수 있습니다.

     

    3.UserDao.kt

     

    @Dao
    interface UserDao {
        @Query("SELECT * FROM user")
        fun getAll(): List<User>
    
        @Query("SELECT * FROM user WHERE id IN (:userIds)")
        fun loadAllByIds(userIds: IntArray): List<User>
    
        @Query("SELECT * FROM user WHERE name LIKE :name LIMIT 1")
        fun findByName(name: String): User
    
        @Insert
        fun insertAll(vararg users: User)
    
        @Insert
        fun insert(users: User)
    
        @Delete
        fun delete(user: User)
    }

     

    4. AppDatabase

     

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

     

    RoomDB에 Singleton Pattern이 적용된 부분

     

     

    5. MainActivity.kt

     

    class MainActivity : AppCompatActivity() {
    
        private lateinit var db: AppDatabase
        private lateinit var userDao: UserDao
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            val button = findViewById<Button>(R.id.button)
            val editText = findViewById<EditText>(R.id.nameEditText)
            val textView = findViewById<TextView>(R.id.textView)
    
            val handler = Handler(Looper.getMainLooper())
    
            db = AppDatabase.getInstance(applicationContext)!!
            userDao = db.userDao()
    
            button.setOnClickListener {
                CoroutineScope(Dispatchers.IO).launch {
                    val str = editText.text.toString()
                    userDao.insert(User(name = str))
                    val userList = userDao.getAll()
                    var newStr = ""
                    for (user in userList) {
                        newStr += "${user.name} \n"
                    }
                    handler.post {
                        // UI 변경 작업을 수행하는 코드
                        textView.text = newStr
                        // 다른 UI 관련 작업
                    }
                }
            }
        }
    }

     

    RoomDB에 데이터를 CRUD 하려면 메인 쓰레드에서 하면 안되고 (1) 코루틴이나 or (2) Worker 쓰레드에서 해줘야 한다

     

    6. 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">
    
        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="200dp"
            android:text="Hello World!"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
        <EditText
            android:id="@+id/nameEditText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="200dp"
            android:ems="10"
            android:inputType="textPersonName"
            android:hint="Name"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/textView" />
    
    
        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="60dp"
            android:text="추가"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/nameEditText" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>

     

     

    추가로 RoomDatabase에 Entity를 2개 이상 넣어서 사용할수도 있다. (예제와는 관련없음)

    import androidx.room.Database
    import androidx.room.RoomDatabase
    
    @Database(entities = [EntityClass1::class, EntityClass2::class, EntityClass3::class], version = 1, exportSchema = false)
    abstract class MyDatabase : RoomDatabase() {
        // 데이터베이스와 관련된 DAO 인터페이스들을 선언할 수 있습니다.
        // 예시: abstract fun myDao(): MyDao
    
        // Entity Data 클래스들의 DAO 인터페이스들을 선언할 수도 있습니다.
        // 예시: abstract fun entity1Dao(): Entity1Dao
    }

     

    최종 결과물 :

     

    귀찮아서 삭제하는 부분은 없고 DB에 데이터를 추가하면 Textview에 추가해서 보여주는 방식으로 만들었습니다.'

     

Designed by Tistory.