ORM(Object Relational Mapping)는 객체(Class) 자체를 하나의 테이블로 매핑하여 객체를 조작하는 것으로 테이블의 데이터를 처리할 수 있도록 해주는 기술입니다.
DB를 조작하는 쿼리를 잘 모르더라도 객체를 대상으로 한 추가, 수정, 삭제동작을 그대로 DB의 테이블에 적용할 수 있으며 Android에서는 이런 ORM을 사용할 수 있도록 Room이라고 하는 라이브러리를 제공하고 있습니다.
우선 Room을 사용해 보기 위해 이전에 만들어봤던 전화번호앱을 다시 사용해 보겠습니다. 코드는 필요하지 않고 디자인만 가져올 것입니다.
[Mobile/Kotlin] - [kotlin] SQLite - 연결 및 사용하기
프로젝트에 ROOM라이브러리를 추가하기 위해 Gradle Scripts -> build.gradle(Module...) 파일을 연뒤 상단에 다음과 같은 내용을 추가합니다.
apply plugin: 'kotlin-kapt'
ROOM라이브러리를 사용하려면 kapt를 플러그인을 사용해야 합니다. kapt는 @로 시작하는 어노테이션을 코드로 생성할 수 있도록 해주는 것인데 나중에 보시면 알겠지만 테이블이나 칼럼 등을 생성할 때 @을 사용한 어노테이션을 사용하므로 kapt플러그인이 필요한 것입니다.
그리고 아래 dependencies 영역에 다음과 같이 Room라이브러리를 추가합니다.
def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
Room에 관한 자세한 설명은 아래 링크를 참고해 주세요. 예제에서 Room의 버전을 2.2.5로 지정하였는데 Room의 최신 버전도 아래 링크를 통해 확인할 수 있습니다.
https://developer.android.com/jetpack/androidx/releases/rooom
이제 Room에서 사용할 데이터 클래스를 만듭니다. 마치 하나의 테이블을 생성하는 것과 같다고 보시면 되겠습니다.
app -> java -> [패키지명]에서 마우스 오른쪽 버튼을 눌러 New -> Kotlin File/Class를 선택합니다.
이어지는 화면에서 Class를 선택하고 이름에 RoomPhoneBook를 입력합니다.
파일이 생성되면 클래스명 위에 @Entity을 추가합니다. 객체를 Table로 변환하는 것입니다.
package com.example.myapplication
import androidx.room.Entity
@Entity
class RoomPhoneBook {
}
Table의 이름은 클래스 이름을 따라가는데 만약 다른 이름의 테이블을 생성하고자 한다면 @Entity(tableName = "PhoneBook")처럼 테이블명을 지정해 주면 됩니다.
테이블을 정의했으니 이제 내부에 데이터 컬럼을 만들어 보죠. 키로 지정할 컬럼은 @PrimaryKey로 하고 값을 자동으로 생성하기 위해 autoGenerate=true를 지정합니다. 실제 컬럼은 @ColumnInfo를 사용하며 컬럼명은 아래 변수명을 따라갑니다. 만약 다른 이름의 컬럼을 생성하려고 하면 @ColumnInfo(name = "name")처럼 이름을 직접 지정할 수 있습니다.
package com.example.myapplication
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
class RoomPhoneBook {
@ColumnInfo
@PrimaryKey(autoGenerate = true)
var seq: Long? = null
@ColumnInfo
var name: String = ""
@ColumnInfo
var phone: String = ""
}
마지막으로 생성자를 추가합니다.
@Entity
class RoomPhoneBook {
@ColumnInfo
@PrimaryKey(autoGenerate = true)
var seq: Long? = null
@ColumnInfo
var name: String = ""
@ColumnInfo
var phone: String = ""
constructor(name: String, phone: String) {
this.name = name
this.phone = phone
}
}
위에서 처럼 객체(Class)를 만들고 난 이후, 객체를 대상으로 실제 데이터를 조회, 추가, 삭제는 interface를 통해 구현됩니다.
app -> java -> [패키지명]에서 마우스 오른쪽 버튼을 눌러 New -> Kotlin File/Class를 선택한 뒤 이어지는 화면에서 interface를 선택하고 이름을 IRoomPhoneBook로 입력해 interface파일을 생성합니다.
interface 상단에 @Dao를 명시합니다. DAO(Data Access Object)는 DB 쪽으로 데이터를 조회, 수정, 삽입, 삭제하는 메서드를 의미합니다.
package com.example.myapplication
import androidx.room.Dao
@Dao
interface IRoomPhoneBook {
}
먼저 조회 부분은 @Query를 통해 직접 쿼리를 작성합니다.
package com.example.myapplication
import androidx.room.Dao
import androidx.room.Query
@Dao
interface IRoomPhoneBook {
@Query("Select * From RoomPhoneBook")
fun GetItem(): List<RoomPhoneBook>
}
다음으로 데이터를 추가하는 Insert와 데이터를 삭제하는 Delete를 구현합니다. Insert에 onConflict = REPLACE는 같은 값의 데이터가 추가될 때 이를 Update로 바꾼다는 의미입니다.
package com.example.myapplication
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy.REPLACE
import androidx.room.Query
@Dao
interface IRoomPhoneBook {
@Query("Select * From RoomPhoneBook")
fun GetItem(): List<RoomPhoneBook>
@Insert(onConflict = REPLACE)
fun Add(phonebook: RoomPhoneBook)
@Delete
fun Remove(phonebook: RoomPhoneBook)
}
SQLite를 사용할 때는 SQLiteOpenHelper를 상속해 Helper를 작성했는데 Room 또한 RoomDatabase를 상속해 Helper를 구현합니다.
app -> java -> [패키지명]에서 마우스 오른쪽 버튼을 눌러 New -> Kotlin File/Class를 선택한 뒤 Class를 선택하고 RoomHelper이름으로 새로운 파일을 생성합니다.
abstract를 붙여 추상 클래스로 만들고 RoomDatabase로부터 상속받도록 합니다.
package com.example.myapplication
import androidx.room.RoomDatabase
abstract class RoomHelper: RoomDatabase() {
}
그리고 class위에 @Database 어노테이션을 작성해 Entity와 버전 등을 지정하고 클래스 안에서 앞서 만들었던 인터페이스를 사용할 수 있는 메서드를 추가합니다.
package com.example.myapplication
import androidx.room.Database
import androidx.room.RoomDatabase
@Database(entities = arrayOf(RoomPhoneBook::class), version = 1)
abstract class RoomHelper: RoomDatabase() {
}
이제 Recycler로 넘어와 Adapter 클래스를 생성합니다. Adapter클래스는 이름만 조금 바뀔 뿐 SQLite를 사용할 때와 마찬가지 방식으로 만들어집니다. 그래서 아래 글의 Adapter코드를 그대로 가져와 재사용하였으며 신규로 만든 RoomPhoneBook클래스와 인터페이스에 맞게 명칭과 관련된 약간의 코드만 수정하였습니다.
SQLite에서의 Adapter는 아래 글을 참고해 주시기 바랍니다.
[Mobile/Kotlin] - [kotlin] SQLite - 연결 및 사용하기
package com.example.myapplication
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.phone_book_item.view.*
class RecyclerAdapter: RecyclerView.Adapter<RecyclerAdapter.Holder>() {
var pbList = mutableListOf<RoomPhoneBook>()
var roomHelper: RoomHelper? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.phone_book_item, parent, false)
return Holder(view)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
val pb = pbList.get(position)
holder.GetItem(pb)
}
override fun getItemCount(): Int {
return pbList.size
}
inner class Holder(itemView: View): RecyclerView.ViewHolder(itemView) {
var pb: RoomPhoneBook? = null
init {
itemView.btnDelete.setOnClickListener {
roomHelper?.roomPhoneBook()?.Remove(pb!!)
pbList.remove(pb)
notifyDataSetChanged()
}
}
fun GetItem(phoneBook: RoomPhoneBook) {
itemView.txtSeq.text = "${phoneBook.seq.toString()}"
itemView.txtName.text = "${phoneBook.name}"
itemView.txtPhone.text = "${phoneBook.phone}"
this.pb = phoneBook
}
}
}
MainActivity에서는 helper변수를 선언하고 초기 설정을 추가합니다. 참고로 allowMainThreadQueries()는 Room이 메인 스레드에서 동작이 가능하도록 하기 위함입니다.
var roomHelper: RoomHelper? = null
roomHelper = Room.databaseBuilder(this, RoomHelper::class.java, "RoomPhoneBook").allowMainThreadQueries().build()
그다음 adapter에 helper와 초기 데이터를 설정합니다. GetItem() 메서드는 List형식을 반환하므로 형식을 일치시키기 위해 as MutableList<RoomPhoneBook>로 형 변환을 시도한 것이며 ?: mutableListOf() 구문은 데이터가 null인 경우 기본값을 지정하기 위한 것입니다.
val adapter = RecyclerAdapter()
adapter.roomHelper = roomHelper
adapter.pbList = roomHelper?.roomPhoneBook()?.GetItem() as MutableList<RoomPhoneBook> ?: mutableListOf()
또한 MainActivity에서 목록을 표시하기 위해 추가한 rccList에 adapter를 지정하고 추가 버튼 이벤트를 만들어 입력한 내용을 추가한 뒤 데이터를 표시하는 부분을 구현합니다.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var roomHelper: RoomHelper? = null
roomHelper = Room.databaseBuilder(this, RoomHelper::class.java, "testDB").allowMainThreadQueries().build()
val adapter = RecyclerAdapter()
adapter.roomHelper = roomHelper
adapter.pbList = roomHelper?.roomPhoneBook()?.GetItem() as MutableList<RoomPhoneBook> ?: mutableListOf()
rccList.adapter = adapter
rccList.layoutManager = LinearLayoutManager(this)
btnSave.setOnClickListener {
val pbItem = RoomPhoneBook(txtName.text.toString(), txtPhone.text.toString())
roomHelper?.roomPhoneBook()?.Add(pbItem)
adapter.pbList.clear()
adapter.pbList.addAll(roomHelper?.roomPhoneBook()?.GetItem() as MutableList<RoomPhoneBook> ?: mutableListOf())
adapter.notifyDataSetChanged()
}
}
}
'Mobile > Kotlin' 카테고리의 다른 글
[Kotlin] 사진저장하고 가져오기 (4) | 2021.01.05 |
---|---|
[Kotlin] 카메라 사용하기 (0) | 2020.12.31 |
[kotlin] SQLite - 연결및 사용하기 (0) | 2020.12.30 |
[kotlin] SQLite - SQLite Open Helper 구현하기 (0) | 2020.12.29 |
[kotlin] androidx.preference (0) | 2020.12.28 |