/* 본 게시물은 '참고 자료' 의 내용을 토대로 작성되었습니다. */
참고 자료
[안드로이드 부트캠프 Room 사용법] : https://www.youtube.com/watch?v=_A2FDHPQX0E
[안드로이드 Room의 사용법과 예제] : https://todaycode.tistory.com/39
#Room
1. Room이란?
Room은 스마트폰 내장 DB에 데이터를 저장하기 위해 사용하는 라이브러리이다. SQLite를 통해 스마트폰 내장 DB에 접근할 수 있지만 아래와 같은 단점 또한 존재한다.
우리가 직접 sql문을 써서 관리하기 때문에 오타로 인한 오류 가능성이 높다. 이러한 단점을 해결하기 위해 Room을 사용한다. Room은 기존 SQLite를 활용해서 객체를 매핑하는 역할을 한다.
2. Room 의 구조
1. Entity
Entity는 데이터베이스의 테이블이라 생각하면된다. 한국말로는 '개체'로 여러 속성들이 모여 만든 하나의 정보 단위이다.
//테이블 생성
@Entity(tableName = "video")
data class VideoEntity(
@PrimaryKey(autoGenerate = true)
var id : Int?,
var videoId: String,
var title: String,
var videoTime: String,
var view: String
)
여기에서는 id, videoId, title, videoTime, view의 속성이 모여 만든 개체이다. 이 때 id의 값이 null이어도 autoGenerate = true기 때문에 자동으로 id값을 넣어준다.
2. DAO
Data Access Object의 줄임말로 데이터에 접근할 수 있는 메서드를 정의해놓은 인터페이스이다.
@Dao
interface VideoDAO {
// onConflict : id 가 같은 것이 있으면 데이터 덮어쓰기
@Insert(onConflict = REPLACE)
fun insert(video: VideoEntity)
@Query("SELECT * FROM video")
fun getAll() : List<VideoEntity>
@Delete
fun delete(video: List<VideoEntity>)
}
@Insert, @Delete 어노테이션을 이용해 데이터를 넣고 삭제하는 기능을 자동으로 완성해준다.
@Query 어노테이션을 이용하면 원하는 쿼리문 함수를 만들 수 있다.
3. Room Database
@Database(entities = arrayOf(VideoEntity::class), version = 1)
abstract class VideoDatabase : RoomDatabase() {
abstract fun videoDAO() : VideoDAO
// 객체를 생성하는 비용을 줄이기 위해 싱글톤 패턴을 사용한다.
companion object{
var instance : VideoDatabase? = null
fun getInstance(context: Context): VideoDatabase? {
if (instance == null){
synchronized(VideoDatabase::class){
instance = Room.databaseBuilder(context.applicationContext,
VideoDatabase::class.java, "video.db")
.fallbackToDestructiveMigration() // database 에서 스키마의 변화가 생길 때 모든 데이터를 drop 하고 새로 생성
.build()
}
}
return instance
}
}
}
VideoDatabase : 데이터베이스를 생성하고 관리하는 데이터베이스 객체를 만들기 위해서 RoomDatabase()를 상속받는 추상 클래스이다. 여기서 싱글톤 패턴을 이용해 VideoDatabase 객체를 하나만 생성한다.(객체를 생성하는 비용을 줄일 수 있다.)
getInstance 함수는 instance(VideoDatabase 객체)를 반환한다. database 객체를 생성하기 위해서는 Room.databaseBuilder를 사용한다. 이 때 fallbackToDestructiveMigration은 database에서 스키마의 변화가 생길 때 모든 데이터를 드랍하고 새로 생성하는 옵션이다.
4. 데이터 베이스 사용
// db 가져오기
db = VideoDatabase.getInstance(this)!!
fun insertVideo(video : VideoEntity){
val insertTask = object : AsyncTask<Unit, Unit, Unit>(){
override fun doInBackground(vararg p0: Unit?) {
db.videoDAO().insert(video)
}
override fun onPostExecute(result: Unit?) {
super.onPostExecute(result)
getAllVideo()
}
}
insertTask.execute()
}
fun getAllVideo(){
val getTask = object : AsyncTask<Unit,Unit,Unit>(){
override fun doInBackground(vararg p0: Unit?) {
videoList = db.videoDAO().getAll()
}
override fun onPostExecute(result: Unit?) {
super.onPostExecute(result)
setRecyclerView(videoList)
}
}
getTask.execute()
}
fun deleteVideo(videoList: List<VideoEntity>){
val deleteTask = object : AsyncTask<Unit,Unit,Unit>(){
override fun doInBackground(vararg p0: Unit?) {
db.videoDAO().delete(videoList)
}
}
deleteTask.execute()
}
db.vdeoDAO().insert / getAll /delete 함수를 사용해 데이터베이스의 CRUD 작업을 수행할 수 있다. 데이터베이스 작업은 백그라운드에서 처리한다. 그리고 보통 onStop() 콜백함수에서 데이터베이스를 다룬다.
2. Room 예제
이번 예제는 recyclerView + RoomDatabase를 이용한 예제이다. 클라이언트가 데이터를 입력하면 데이터베이스에 추가하고 화면에 표시한다.
1. RoomActivity
class RoomActivity : AppCompatActivity() {
lateinit var db : VideoDatabase
var videoList = listOf<VideoEntity>()
lateinit var binding : ActivityRoomBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// db 가져오기
db = VideoDatabase.getInstance(this)!!
val _binding = ActivityRoomBinding.inflate(layoutInflater)
binding = _binding
setContentView(binding.root)
binding.videoAddBtn.setOnClickListener {
val title = binding.videoEditText.text.toString()
var video = VideoEntity(null, "videoId", title, "8:00", "조회수 200만 회")
insertVideo(video)
}
binding.removeAllBtn.setOnClickListener {
deleteVideo(videoList)
getAllVideo()
}
}
fun insertVideo(video : VideoEntity){
val insertTask = object : AsyncTask<Unit, Unit, Unit>(){
override fun doInBackground(vararg p0: Unit?) {
db.videoDAO().insert(video)
}
override fun onPostExecute(result: Unit?) {
super.onPostExecute(result)
getAllVideo()
}
}
insertTask.execute()
}
fun getAllVideo(){
val getTask = object : AsyncTask<Unit,Unit,Unit>(){
override fun doInBackground(vararg p0: Unit?) {
videoList = db.videoDAO().getAll()
}
override fun onPostExecute(result: Unit?) {
super.onPostExecute(result)
setRecyclerView(videoList)
}
}
getTask.execute()
}
fun deleteVideo(videoList: List<VideoEntity>){
val deleteTask = object : AsyncTask<Unit,Unit,Unit>(){
override fun doInBackground(vararg p0: Unit?) {
db.videoDAO().delete(videoList)
}
}
deleteTask.execute()
}
fun setRecyclerView(videoList: List<VideoEntity>){
binding.recyclerView.layoutManager = LinearLayoutManager(this)
binding.recyclerView.adapter = CustomAdapter(videoList = videoList)
}
}
2. CustomAdapter
class CustomAdapter(videoList: List<VideoEntity>) : RecyclerView.Adapter<CustomAdapter.Holder>(){
var listData = videoList
lateinit var binding: ItemRecyclerBinding
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val view = ItemRecyclerBinding.inflate(LayoutInflater.from(parent.context),parent, false)
binding = view
return Holder(view.root)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
val video = listData[position]
holder.setVideo(video)
}
override fun getItemCount(): Int {
return listData.size
}
inner class Holder(itemView: View) : RecyclerView.ViewHolder(itemView){
init { // 목록클릭 이벤트 처리
itemView.setOnClickListener{
Toast.makeText(itemView?.context, "클릭된 아이템 = ${binding.videoTitle.text}", Toast.LENGTH_LONG).show()
}
}
fun setVideo(video: VideoEntity){
binding.textNo.text = "${video.id}"
binding.videoTitle.text = video.title
binding.videoView.text = video.view
binding.textVideoId.text = video.videoId
}
}
}
실행 결과
느낀점
SQLite과 다르게 직접 sql문을 입력하고 관리하지 않아도 돼서 편했다. 또한, Entity / DAO / Database 로 나누다 보니 유지보수 관점에서 SQLite보다 이점이 크다 생각된다.