경험의 기록

안드로이드에서는 로컬 DB에 데이터를 저장하기 위해 SQLite를 지원한다.

허나 SQLite는 사용하기 복잡하여 어렵기 때문에

SQLite에 대한 추상화 레이어를 제공하는 Room 라이브러리를 지원한다.

 

2021.04.19 - [안드로이드/AAC, MVVM] - [Android] 안드로이드 AAC & MVVM

 

[Android] 안드로이드 AAC & MVVM

액티비티, 프래그먼트에 너무 많은 코드를 넣게 되면 점점 무거워져 다루기 힘들어지게 된다. 앱이 카메라 인텐트를 트리거합니다. 그러면 Android OS에서 요청을 처리하기 위해 카메라 앱을 실행

hanyeop.tistory.com

Room 라이브러리도 AAC 라이브러리에 포함되어 있다.

 

Room 라이브러리 구성요소

 

  • 엔티티(Entity) : 데이터베이스의 테이블을 뜻하며 DB에 저장될 데이터 형식을 정의
  • 데이터 접근 객체(Data Access Object) : 데이터베이스에 접근하여 수행할 작업을 메소드로 정의
  • 룸 데이터베이스(Room Database) : 데이터베이스의 전체적인 소유자 역할, DB를 새롭게 생성하거나 버전을 관리

 


사용해보기

dependencies 추가

def room_version = "2.3.0"

    // room
    implementation "androidx.room:room-runtime:$room_version"
    kapt "androidx.room:room-compiler:$room_version"

    // room 코루틴
   implementation "androidx.room:room-ktx:$room_version"

룸과 룸에서의 코루틴 작업을 위한 dependencies를 추가해주고

kapt를 추가해주어야 하는데,

kapt Kotlin Annotation Processing 의 약자로서 코틀린에서 Annotation Processing를 사용하기 위하여 필요하다.

plugins {
    id 'kotlin-kapt'
}

플러그인에 추가해준다.

 

android {

    buildFeatures {
        viewBinding true
    }
}

또한 익스텐션대신 뷰바인딩을 사용할 것이기 때문에 추가해준다.

 

 

레이아웃 생성

 

간단하게 테스트할수 있는 레이아웃을 만들어준다.

이름과 나이를 등록하고, 밑에 텍스트뷰에는 등록된 데이터들이 나열되도록 할 것이다.

 

 

Entity 생성

@Entity
data class User (
    var name : String,
    var age : String
    ) {
    @PrimaryKey(autoGenerate = true)
    var id = 0
}

@Entity 어노테이션으로

Entity 를 생성해준다.

@PrimaryKey 어노테이션으로 프라이머리키를 지정해줄수 있으며, autoGenerate 값을 true로 설정하면 id 값을 자동으로 생성해준다.

이 데이터클래스가 데이터베이스의 테이블 역할을 한다.

 

또한 @Embedded 어노테이션으로 다른 object를 인수로 받을 수 있는데,

@Entity
data class User (
    var name : String,
    var age : String
    @Embedded
    var userdata : Userdata
    ) {
    @PrimaryKey(autoGenerate = true)
    var id = 0
}

data class Userdata(

	var phone : String,
    var address : String
    )

위와 같이 Userdata 클래스를 받아와서 사용할 수도 있다.

 

 

DAO 생성

@Dao
interface UserDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insert(user : User)

    @Update
    suspend fun update(user : User)

    @Delete
    suspend fun delete(user : User)

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

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

@Dao 어노테이션으로

Dao를 생성해준다.

DB 메소드들은 기본적으로 코루틴에서 실행되기 때문에 suspend로 선언해준다.

 

@Insert, @Update, @Delete

어노테이션은 각각 삽입, 수정, 삭제의 기능을 하며

삽입에서 onConflict = OnConflictStrategy.REPLACE 를 사용하면 데이터베이스 내에 중복된 튜플( 동일한 ID) 을 삽입할 경우 덮어씌우게 해 준다.

이외의 메서드는 @Query로 쿼리를 직접 작성하면된다.

 

여기서는 예시로 테이블 내 데이터를 전부 조회하는 getAll

테이블 내 데이터를 전부 삭제하는 deleteAll를 쿼리로 작성하였다.

 

 

Room Database 생성

// entities = 사용할 엔티티 선언, version = 엔티티 구조 변경 시 구분해주는 역할
@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.db"
                    ).build()
                }
            }
            return instance
        }
    }
}

데이터베이스를 자주 생성하는것은 비효율적이므로 싱글톤 패턴으로 구현하는것이 권장되어 있기 때문에 싱글톤 패턴으로 구현해준다.

 

 

MainActivity에서 사용하기

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private lateinit var db : UserDatabase

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

        db = UserDatabase.getInstance(applicationContext)!!
        fetchUserList()
    }

    fun fetchUserList(){
        var userListText = "사용자 목록"

        CoroutineScope(Dispatchers.Main).launch {

            val load = async(Dispatchers.IO) {
                val userList = db.userDao().getAll()
                for(i in userList){
                    userListText += "\n${i.id} ${i.name}, ${i.age}"
                }
            }
            load.await()
            binding.textView.text = userListText
        }
    }

    fun addUser(view : View){
        val user = User(binding.nameEditView.text.toString(),binding.ageEditView.text.toString())

        CoroutineScope(Dispatchers.IO).launch {
            db.userDao().insert(user)
        }
        fetchUserList()
    }

    fun deleteAllUser(view : View){
        CoroutineScope(Dispatchers.Main).launch {
            val delete = async(Dispatchers.IO) {
                db.userDao().deleteAll()
            }
            delete.await()
            fetchUserList()
        }
    }
}

전체코드에서 하나씩 살펴보면

 

 

private lateinit var binding: ActivityMainBinding
    private lateinit var db : UserDatabase

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

        db = UserDatabase.getInstance(applicationContext)!!
        fetchUserList()
    }

oncreate에서 뷰바인딩을 해주고 db를 초기화해준다.

또한 생성시 데이터를 조회하여 나열하는 메소드인 fetchUserList를 호출하여 데이터를 표시해준다.

 

fun fetchUserList(){
        var userListText = "사용자 목록"

        CoroutineScope(Dispatchers.Main).launch {

            val load = async(Dispatchers.IO) {
                val userList = db.userDao().getAll()
                for(i in userList){
                    userListText += "\n${i.id} ${i.name}, ${i.age}"
                }
            }
            load.await()
            binding.textView.text = userListText
        }
    }

코루틴의 메인쓰레드에서

데이터 읽기,쓰기는 Dispatchers.IO 에서 해야하고, UI작업은 메인쓰레드에서 해야하므로 따로 생성해주고

그 쓰레드가 종료되면 텍스트뷰에 텍스트를 할당해준다.

 

 

fun addUser(view : View){
        val user = User(binding.nameEditView.text.toString(),binding.ageEditView.text.toString())

        CoroutineScope(Dispatchers.IO).launch {
            db.userDao().insert(user)
        }
        fetchUserList()
    }

이름과 나이를 적은 에디트뷰에서 텍스트를 가져와서 데이터베이스에 넣고 데이터를 다시 표시해준다.

 

fun deleteAllUser(view : View){
        CoroutineScope(Dispatchers.Main).launch {
            val delete = async(Dispatchers.IO) {
                db.userDao().deleteAll()
            }
            delete.await()
            fetchUserList()
        }
    }

테이블에 존재하는 데이터를 삭제하고 다시 표시해준다.

등록된 데이터가 없을 때는 이렇게 표시되며

 

데이터를 등록하면 그 데이터가 출력되고, 어플을 종료하고 다시 실행하여도 저장되어 출력된다.

 

전부 삭제를 누르면 데이터가 다 삭제된다.

 

 

github.com/HanYeop/AndroidStudio-Practice/tree/master/RoomTest

 

HanYeop/AndroidStudio-Practice

안드로이드 학습 내용 저장소. Contribute to HanYeop/AndroidStudio-Practice development by creating an account on GitHub.

github.com

 

 

참조

www.youtube.com/watch?v=lEFCMOs5iVo

todaycode.tistory.com/39

juyeop.tistory.com/30

developer.android.com/training/data-storage/room?hl=ko#kotlin

 

 

 

반응형

공유하기

facebook twitter kakaoTalk kakaostory naver band
loading