SQLite를 추상화한 객체이자 ORM(Object Relational Mapping)에 해당됨
ORM : 객체와 데이터베이스의 관계를 매핑해주어 오브젝트를 다루듯이 db를 다룰 수 있다.
Room의 주요 컴포넌트
Repository라는 중간객체를 만들어 Room DB를 관리하도록 하곤 한다. Room Coroutine을 기반으로 동작한다.
데이터베이스의 테이블과 구성요소를 표현해야 한다.
표현은 애너테이션을 붙여서 한다
@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
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(entities = [NotesDto::class], version = 1)
abstract class NoteDatabase :RoomDatabase(){
abstract fun noteDao() : NoteDao
}
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는 하나만 만들어 안의 메서드를 가져다 쓰는 것이 효율적이므로 싱글톤으로 만들어지도록 코드를 추가했다.
class NoteApplicationClass : Application(){
override fun onCreate() {
super.onCreate()
NoteRepository.initialize(this)
}
}
만든 repository 클래스를 앱이 시작할 때 객체로 만들어준다.
자 이제 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()
}
조회 부분을 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 하면 될 것이다.