안드로이드 Room

이영준·2023년 5월 2일
0

📌 Room

SQLite를 추상화한 객체이자 ORM(Object Relational Mapping)에 해당됨
ORM : 객체와 데이터베이스의 관계를 매핑해주어 오브젝트를 다루듯이 db를 다룰 수 있다.

Room의 주요 컴포넌트

  • Entity : db안의 테이블
  • Database : DAO 객체를 제공하여 데이터베이스를 이용할 수 있는 access point
  • DAO : 데이터베이스에 접근하는 메소드들이 있는 오브젝트
    • SQLite 쿼리는 직접 작성한다.

📌 Jetpack 활용 구조

Repository라는 중간객체를 만들어 Room DB를 관리하도록 하곤 한다. Room Coroutine을 기반으로 동작한다.

📌 구현

🔑 Entity 생성

데이터베이스의 테이블과 구성요소를 표현해야 한다.
표현은 애너테이션을 붙여서 한다

  • @Entity
  • @PrimaryKey
  • @NonNull : null 될수 없음 의미
  • @ColumnInfo(name = "word") : column 이름 설정
@Entity(tableName = "note")
data class NotesDto(val TITLE: String = "title", val BODY: String = "body") {

    //auto increment 1부터 들어가도록
    @PrimaryKey(autoGenerate = true)
    var ID: Long = 0

    constructor(id: Long, title: String, content: String) : this(title, content) {
        this.ID = id;
    }
}

tablename 설정 안하면 dto가 그대로 들어감

🔑 DAO 생성

@Dao
interface NoteDao {

    @Query("select * from note")
    suspend fun getNotes(): MutableList<NotesDto>

    @Query("select * from note where id = (:id)")
    suspend fun getNote(id: Long): NotesDto

    //기본키와 중복되면 해당 필드와 replace 해라
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertNote(dto: NotesDto)

    @Update
    suspend fun updateNote(dto: NotesDto)

    @Delete
    suspend fun deleteNote(dto: NotesDto)

    //id만으로 삭제
    @Query("delete from note where id = (:id)")
    suspend fun deleteNote(id: Long)
}

🔑 database 생성

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

    abstract fun noteDao() : NoteDao

}

🔑 DB 생성 작업 위한 repository 생성

class NoteRepository private constructor(context: Context) {

    private val database: NoteDatabase = Room.databaseBuilder(
        context.applicationContext,
        NoteDatabase::class.java,
        DATABASE_NAME
    ).build()

    private val noteDao = database.noteDao()

    suspend fun getNotes(): MutableList<NotesDto> {
        return noteDao.getNotes()
    }

    suspend fun getNote(id: Long): NotesDto {
        return noteDao.getNote(id)
    }

    suspend fun insertNote(dto: NotesDto) = database.withTransaction {
        noteDao.insertNote(dto)
    }

    suspend fun updateNote(dto: NotesDto) = database.withTransaction {
        noteDao.updateNote(dto)
    }

    suspend fun deleteNote(dto: NotesDto) = database.withTransaction {
        noteDao.deleteNote(dto)
    }

    companion object {
        private var INSTANCE: NoteRepository? = null

        fun initialize(context: Context) {
            if (INSTANCE == null) {
                INSTANCE = NoteRepository(context)
            }
        }

        fun get(): NoteRepository {
            return INSTANCE ?: throw IllegalStateException("NoteRepository must be initialized")
        }
    }

}

역시 dao의 suspend fun을 불러오므로 코루틴 스코프 안에서 부를게 아니라면 suspend로 선언해야 하고,
C U D의 경우에는 트랜잭션으로 실행되도록 해야 한다.

Respository는 하나만 만들어 안의 메서드를 가져다 쓰는 것이 효율적이므로 싱글톤으로 만들어지도록 코드를 추가했다.

🔑 Application에서 Repository 생성

class NoteApplicationClass : Application(){
    override fun onCreate() {
        super.onCreate()
        NoteRepository.initialize(this)
    }
}

만든 repository 클래스를 앱이 시작할 때 객체로 만들어준다.

🔑 액티비티에서 DB 접근

자 이제 repository까지 만들었기 때문에 db에 접근만 하면 된다.

하지만 ROOM 작업은 main 스레드에서 하는 것이 불가능하고, 코루틴을 통해 다른 스레드에서 실행해야 한다. 왜냐하면 roomDB는 async await 방식으로 데이터를 불러오기 때문이다.

위의 DAO의 함수들이 suspend로 만들어야 하는 이유가 그것이다.

따라서 이를 참조하는 함수들을 suspend로 만들고
코루틴 스코프에서 이 suspend 함수를 실행시켜준다.

 //DB 연결하고 Adapter 초기화
    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_notes_list)

        noteRepository = NoteRepository.get()
        initAdapter()
    }
    
    
    private fun initAdapter(){
        listAdapter = NoteListAdapter()
        CoroutineScope(Dispatchers.Main).launch{
            listAdapter.listData = getData()
            listAdapter.notifyDataSetChanged()
        }

        findViewById<RecyclerView>(R.id.list_recyle).apply{
            adapter = listAdapter
            layoutManager = LinearLayoutManager(this@NoteListActivity)
        }
    }
    
    
     //전체 데이터 조회해서 리턴
    private suspend fun getData(): MutableList<NotesDto> {
        return noteRepository.getNotes()
    }

📌 Room + LiveData + ViewModel

조회 부분을 dto가 아닌 dto 라이브데이터를 받는 것으로 수정해보자

    @Query("SELECT * FROM note")
    fun getNotes(): LiveData<MutableList<NotesDto>>

    @Query("SELECT * FROM note WHERE ID = (:id)")
    fun getNote(id: Long): LiveData<NotesDto>

livedata는 observer를 통해 비동기적으로 동작하기 떄문에 suspend 함수일 필요가 없다.

마찬가지로 다른 물려있는 코드들도 livedata 형으로 리턴값을 바꿔주고 suspend를 지우고,

코루틴 스코프 내에서 suspend로 동작하는 함수를 부르는 대신

getData().observe(this){
            listAdapter.listData = it
            listAdapter.notifyDataSetChanged()
        }

observing을 통해 받아올 수 있다.

여기서 viewModel까지 사용한다면
Repository를 viewModel에서 만들고 getData등의 함수를 뷰 모델에서 만든다음 뷰 모델의 변수를 observing 하면 될 것이다.

profile
컴퓨터와 교육 그사이 어딘가

0개의 댓글