*데이터베이스-ORM라이브러리 Room사용
*ORM(Object-Relational Mapping)
-객체(Class)와 관계형 데이터베이스의 데이터(테이블)를 매핑하고 변환하는 기술
-코드의 클래스파일에 ORM적용 시 자동으로 쿼리로 변환해 테이블 생성 가능
-쿼리를 잘 몰라도 코드만드로 데이터베이스의 모든 것을 컨트롤할 수 있음
-안드로이드는 SQLite를 코드 관점에서 접근할 수 있도록 ORM라이브러리 Room을 제공
*Room
-데이터베이스에 읽고 쓰는 메서드를 인터페이스 형태로 설계하고 사용
-코드없이 이름만 명시하는 형태로 인터페이스를 만들면 Room이 나머지 코드 자동 생성함
-Room을 사용 시 클래스명이나 변수명 위에 @어노테이션을 사용해 코드로 변환함
*kapt
-plugins {} 에 id 'kotlin-kapt' 추가하여 사용 설정함
-자바6부터 도입된 Pluggable Annotation Processing API를 코틀린에서의 사용
-Annotation Processing(어노테이션 프로세싱) : @명령어 처럼 사용하는 주석 형태의 문자열을 실제 코드로 생성해 주는 것
-Annotation(어노테이션) : @으로 시작하는 명령어, 컴파일 시 코드로 생성되기 때문에 처리속도를 빠르게 할 수 있음
-Room은 빠른 처리 속도를 위해 어노테이션 프로세서 사용
-코틀린에서는 어노테이션 프로세서 대신에 kapt를 사용하기 때문에 kapt 플러그인 추가
-Room버전 확인: https://developer.android.com/jetpack/androidx/releases/room#groovy
*DAO(Data Access Object):
-데이터베이스에 접근해서 DML쿼리를 실행하는 메서드 모음
-인터페이스를 만들어 @Dao로 사용하여 DML쿼리를 명시하고 메서드 생성해서 사용
-DML쿼리: SELECT, INSERT, UPDATE, DELETE
*@(어노테이션)의 종류
*@Database 어노테이션 속성
-entities : Room라이브러리가 사용할 엔터티(테이블) 클래스 목록
-version : 데이터베이스의 버전
-exportSchema : true설정 시 스키마 정보 파일로 출력
-스키마 : 데이터베이스의 구조와 제약 조건에 관한 전반적인 명세를 기술한 메타데이터의 집합
--스키마는 데이터베이스를 구성하는 데이터 개체(Entity), 속성(Attribute), 관계(Relationship) 및 데이터 조작 시 데이터 값들이 갖는 제약 조건 등에 관해 전반적으로 정의함
*ORM기술, Room라이브러리 사용
-build.gradle
-- android{} 에 뷰바인딩 추가
--plugins{} 에 kapt 플러그인 추가
--dependencies{} 에 Room라이브러리 추가
--Room버전 확인: https://developer.android.com/jetpack/androidx/releases/room#groovy
//뷰바인딩
buildFeatures{
viewBinding true
}
//1
//kapt사용 설정
//kapt: 자바6부터 도입된 Pluggable Annotation Processing API를 코틀린에서의 사용
//Annotation Processing(어노테이션 프로세싱): @명령어 처럼 사용하는 주석 형태의 문자열을
//실제 코드로 생성해 주는 것
//어노테이션: @으로 시작하는 명령어, 컴파일 시 코드로 생성되기 때문에 발생할 수 있는 성능문제 개선됨
//빠른 처리속도를 위한 것
//코틀린에서는 어노테이션 프로세서 대신 kapt 사용하기 때문에 kapt 플러그인 추가
id 'kotlin-kapt'
//2
//Room라이브러리 추가
//Room은 빠른 처리 속도를 위해 어노테이션 프로세서를 사용하는데
//코틀린에서는 이것 대신 kapt 사용
//kapt사용하기 위해서는 kapt플러그인이 먼저 추가되어 있어야 함
//Room을 사용하면 클래스명이나 변수명 위에 @어노테이션을 사용해서 코드로 변환할 수 있음
//Room버전 확인: https://developer.android.com/jetpack/androidx/releases/room#groovy
def room_version = "2.4.3"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
// To use Kotlin annotation processing tool (kapt)
kapt "androidx.room:room-compiler:$room_version"
-MainActivity.kt
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.room.Room
import com.heeyjinny.room.databinding.ActivityMainBinding
//1
//ORM의 Room라이브러리 사용하기
//app - java 밑 패키지명 우클릭 - New - Kotlin Class/File 생성
//RoomMemo.kt 클래스 생성
class MainActivity : AppCompatActivity() {
//뷰바인딩
val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
//2
//helper변수를 생성하여 RoomHelper를 사용할 수 있도록 함
var helper: RoomHelper? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//뷰바인딩
setContentView(binding.root)
//2-1
//RoomHelpe를 사용할 수 있는 변수 helper생성
//Room라이브러리의 databaseBuilder 속성 사용
//databaseBuilder의 세 번째 파라미터는 실제 생성되는 DB파일의 이름
//Room은 기본적으로 서브 스레드에서 동작하도록 설계되어 있어서
//메인 스레드에서 동작하도록 만들어줘야함
//이전에 메인스레드에서 동작할 수 있도록 만드는 코드 찾아와 적용하기...ㅠㅠ
//지금은 allowMainThreadQueries()옵션을 사용하지만 권장되지 않음
helper = Room.databaseBuilder(this, RoomHelper::class.java, "room_memo")
.allowMainThreadQueries().build()
//3
//어댑터 생성
val adapter = RecyclerAdapter()
//8
//데이터 삭제를 위해 생성해둔 helper를 어댑터에 전달
//RecyclerAdapter.kt에 helper프로퍼티 생성...
adapter.helper = helper
//4
//어댑터에있는 목록(listData)에서 조회하여(select) 가져온 데이터 세팅
//helper에 null이 허용되므로 ?. 의 형태로 사용
//roomMemoDao() 와 Dao의 메서드 getAll()도 null허용 ?. 사용하고
//adapter의 listData에는 null이 허용되지 않기 때문에 앞의2개가 null일 경우 사용하는
//엘비스 연산자 ?: 를 사용하여 디폴트 값 설정함
adapter.listData.addAll(helper?.roomMemoDao()?.getAll()?: listOf())
//5
//리사이블러뷰 위젯에 어댑터를 연결하고 레이아웃 매니저 설정
binding.recyclerMemo.adapter = adapter
binding.recyclerMemo.layoutManager = LinearLayoutManager(this)
//6
//리사이클러뷰 아이템 목록에 있는 저장버튼에 클릭이벤트 설정
binding.buttonSave.setOnClickListener {
//6-1
//조건식을 사용하여 메모입력 위젯인 EditText에 값이 있으면 해당 내용으로 Memo클래스 생성
if (binding.editText.text.toString().isNotEmpty()){
//6-2
//입력한 텍스트 값이 있다면 RoomMemo클래스를 생성해 파라미터로 값을 전달하고 변수에 저장
val memo = RoomMemo(binding.editText.text.toString(), System.currentTimeMillis())
//6-3
//helper클래스의 roomMemoDao()메서드에 변수memo의 값을 삽입해(insert) 데이터베이스에 저장
helper?.roomMemoDao()?.insert(memo)
//6-4
//저장이 끝났으면 어댑터의 데이터 모두 초기화
adapter.listData.clear()
//6-5
//데이터베이스에서 새로운 목록을 읽어와 어댑터에 다시 세팅하고 갱신(새로고침 개념...)
//새로 생성되는 메모에는 번호가 자동 입력되므로 번호를 갱신하기 위해 새로운 데이터를 세팅함
adapter.listData.addAll(helper?.roomMemoDao()?.getAll() ?: listOf())
//6-6
//어댑터의 세팅이 끝났다는 것(데이터가 변경되었다는 것) 알려주고 갱신
adapter.notifyDataSetChanged()
//6-7
//editText위젯에 써있는 텍스트 내용 지워서(빈 칸으로) 초기화
binding.editText.setText("")
}
}//클릭리스너...
//7
//메모 목록에 삭제버튼 추가하여 메모 삭제 기능 구현
//item_recycler.xml 수정...
}//onCreate
}//ManinActivity
-RoomMemo.kt
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
//1
//@Entity 어노테이션 적용: 정의된 class 위에 사용
//@Entity: Room라이브러리는 @Entity이 적용된 클래스를 찾아 테이블로 변환
//@Entity(tableName = "테이블명"): 테이블명을 클래스명과 다르게 하고 싶을 때 옵션
//RoomMemo클래스의 내용을 테이블명이 room_memo인 테이블로 생성하여 변환
@Entity(tableName = "room_memo")
class RoomMemo {
//2
//테이블의 컬럼인 num, content, date를 멤버변수로 선언하고 위에
//@ColumnInfo 어노테이션 작성
//@ColumnInfo: 테이블의 컬럼명으로 사용
//@ColumnInfo(name = "컬럼명")테이블의 컬럼명을 변수명과 다르게 하고 싶을 때 옵션
//3
//num변수에 키(Key)라는 점을 명시하고 자동증가 옵션 추가
//@PrimaryKey(autoGenerate = true)
@PrimaryKey(autoGenerate = true)
@ColumnInfo
var num: Long? = null
@ColumnInfo
var content: String = ""
@ColumnInfo(name = "date")
var datetime: Long = 0
//4
//content와 datetime을 받는 생성자 작성
//num은 값이 자동증가되므로 생성자로 내용을 받지 않아도 됨
constructor(content: String, datetime: Long) {
//4-1
//이 클래스에 있는(this) 변수 content와 datetime에 생성자로 받은 값넣기
this.content = content
this.datetime = datetime
}
//5
//만약 변수를 테이블의 컬럼으로 사용하고 싶지 않을 때
//@Ignore: 해당 변수가 테이블과 관계없는 변수라는 정보를 알림
@Ignore
var temp: String = "임시로 사용되는 데이터"
//6
//DAO(Data Access Object): 데이터베이스에 접근해서 DML쿼리를 실행하는 메서드 모음
//DML쿼리: SELECT, INSERT, UPDATE, DELETE
//RoomMemoDAO 인터페이스 정의하기
//app - java밑 패키지 우클릭 - New - Kotlin Class/File
//Interface선택 - RoomMemoDao 생성
}//class RoomMomo
-RoomMemoDao.kt
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy.REPLACE
import androidx.room.Query
//1
//DAO(Data Access Object): 데이터베이스에 접근해서 DML쿼리를 실행하는 메서드 모음
//DML쿼리: SELECT, INSERT, UPDATE, DELETE
//RoomMemoDAO 인터페이스에 @Dao(쿼리메서드모음) 명시
@Dao
interface RoomMemoDao {
//2
//조회, 삽입수정, 삭제 3개 메서드 생성 후 각 어노테이션 붙임
//2-1
//조회메서드 생성
//다른 ORM툴과 다르게 조회하는 select쿼리는 직접 작성해야함
//그래서 @Query어노테이션을 사용해 직접 작성
//조회한다(select) 모든내용을(*) room_memo에 있는(from)
//room_memo안에 있는 테이블의 모든 내용을 RoomMemo클래스의 배열타입을 가지는 메서드 getAll()생성
@Query("select * from room_memo")
fun getAll(): List<RoomMemo>
//2-2
//삽입, 수정 메서드 생성
//onConflict = REPLACE 옵션 적용: 동일한 키를 가진 값이 입력되었을 때 덮어쓰기(Update,수정됨)
//REPLACE 옵션 적용 시 import할 때 androidx.room 패키지로 선택함
@Insert(onConflict = REPLACE)
fun insert(memo: RoomMemo)
//2-3
//삭제 메서드 생성
@Delete
fun delete(memo: RoomMemo)
//3
//RoomHelper 클래스 정의
//SQLiteOpenHelper()를 상속받아 구현했던 것 처럼 Room도 유사한 구조로 사용
//RoomDatabase()를 상속받아 클래스 생성하여 사용함
//상속받아서 클래스 생성 시 주의할점은 추상클래스(abstract)로 생성해야 함
//app - java밑 클래스명 우클릭 - New - Kotlin Class/File -
//Class클릭 - RoomHelper클래스 생성
}
-RoomHelper.kt
import androidx.room.Database
import androidx.room.RoomDatabase
//1
//RoomHelper 클래스 정의하기
//SQLiteOpenHelpe())를 상속받아 구현했던 것 처럼 Room도 유사한 구조로 사용
//RoomDatabase()를 상속받아 클래스 생성하여 사용함
//상속받아서 클래스 생성 시 주의할점은 추상클래스(abstract)로 생성해야 함
//RoomDatabase를 상속받는 추상클래스 RoomHelper 클래스 생성하고
//@Database 어노테이션 작성
//@Database의 속성
//entities: Room라이브러리가 사용할 엔터티(테이블) 클래스 목록
//version: 데이터 베이스의 버전
//exportSchema: true사용 시 스키마 정보를 파일로 출력함
@Database(entities = arrayOf(RoomMemo::class), version = 1, exportSchema = false)
abstract class RoomHelper: RoomDatabase() {
//2
//RoomMemoDao 인터페이스 구현하는 메서드 생성
//인터페이스 구현하는 메서드만 작성해도 Room라이브러리를 통해 미리 만들어져있는
//코드를 사용할 수 있음
abstract fun roomMemoDao(): RoomMemoDao
//3
//화면에 보여질 뷰 작성을 위해
//activity_main.xml, item_recycler.xml 작성 후
//어댑터 클래스 생성...RecyclerAdapter
}
-activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".MainActivity">
<!-- 리사이클러뷰 생성하여 화면 구성하기 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerMemo"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@+id/editText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- 메모 입력 창, 버튼 생성 -->
<EditText
android:id="@+id/editText"
android:layout_width="0dp"
android:layout_height="65sp"
android:hint="메모를 입력하세요"
android:inputType="textMultiLine"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/buttonSave"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/buttonSave"
android:layout_width="wrap_content"
android:layout_height="65dp"
android:text="저장"
android:backgroundTint="@color/teal_200"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/editText" />
<!-- 리사이클러뷰 아이템 용도로 사용할 레이아웃 파일 생성 -->
<!-- app - res - layout 우클릭 - Layout Resource File -->
<!-- item_recycler.xml 생성 -->
</androidx.constraintlayout.widget.ConstraintLayout>
-item_recycler.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp">
<!-- 데이터베이스에 저장할 번호, 내용, 시간 표시할 텍스트뷰 배치 -->
<TextView
android:id="@+id/textNum"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:text="01"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- android:maxLines="2" : 텍스트뷰의 줄 제한 2줄 -->
<!-- android:ellipsize="end" : 줄 제한이 넘어가면 말줄임표 속성(...) -->
<!-- FIN. macLine, ellipsize 삭제하여 전체내용 보여주기 -->
<TextView
android:id="@+id/textContent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:padding="16sp"
android:text="메모 내용 표시"
android:layout_marginBottom="32dp"
app:layout_constraintEnd_toStartOf="@+id/btnDelete"
app:layout_constraintStart_toEndOf="@+id/textNum"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textDateTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="2022/01/01 08:40"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textContent"/>
<!-- 배치 후 소스코드 연결하기 -->
<!-- 리사이클러뷰와 액티비티 연결을 위한 어댑터 클래스 만들기 -->
<!-- app - java 밑 패키지 우클릭 - 클래스생성 -->
<!-- RecyclerAdapter.kt 클래스 생성 -->
<!-- 삭제 버튼 생성 -->
<Button
android:id="@+id/btnDelete"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginEnd="16dp"
android:text="삭제"
android:gravity="center"
android:layout_marginBottom="32dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- 삭제버튼을 눌러서 RoomMemo의 데이터와 어댑터에있는 Memo컬렉션의 데이터 삭제 -->
<!-- MainActivity.kt 에서 코드 추가 -->
</androidx.constraintlayout.widget.ConstraintLayout>
-RecyclerAdapter.kt
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.heeyjinny.room.databinding.ItemRecyclerBinding
import java.text.SimpleDateFormat
//리사이클러뷰 어댑터를 상속받는 어댑터클래스 생성
//리사이클러뷰 목록만들기 과정과 동일함...
//Holder클래스 inner클래스로 어댑터 클래스 안에서 생성하기만 바뀜...
//2
class RecyclerAdapter: RecyclerView.Adapter<RecyclerAdapter.Holder>() {
//8
//데이터 삭제 구현을 위해 생성하는 helper프로퍼티 생성하고
//Holder클래스에 클릭리스너 달기...
var helper: RoomHelper? = null
//3
var listData = mutableListOf<RoomMemo>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
//4
val binding = ItemRecyclerBinding.inflate(LayoutInflater.from(parent.context),parent,false)
return Holder(binding)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
//6
val memo = listData.get(position)
holder.setRoomMemo(memo)
}
override fun getItemCount(): Int {
//3
return listData.size
}
//1
//10
//삭제버튼을 클릭하면 어댑터의 helper와 listData에 접근해야함
//어댑터 클래스의 멤버변수인 helper와 listData에 접근을 위해
//Holder클래스 전체를 어댑터 클래스 안에 옮기고 class앞에 inner키워드를 붙여줌
inner class Holder(val binding: ItemRecyclerBinding): RecyclerView.ViewHolder(binding.root){
//11
//홀더는 한 화면에 그려지는 개수만큼 만든 후 재사용함 그래서
//클릭하는 시점에 어떤 데이터가 있는지 알아야 하므로 변수 선언 후 setRoomMemo()를 통해 넘어온 RoomMemo임시 저장
var mRoomMemo: RoomMemo? = null
//9
//삭제버튼을 누르면 반응하는 메서드 만들기
init {
binding.btnDelete.setOnClickListener {
//12
//roomMemo데이터를 먼저 삭제하고 listData데이터도 삭제
//mRoomMemo는 null허용설정이 되었기 때문에 !!강제함
//RoomHelper를 사용할 때 여러개의 Dao가 있을 수 있기 때문에
//헬퍼?.Dao()?.메서드() 형태로 가운데에 어떤 Dao를 쓸 건지 명시해야함
helper?.roomMemoDao()?.delete(mRoomMemo!!)
listData.remove(mRoomMemo)
//12-1
//어댑터 갱신
notifyDataSetChanged()
}
}//init
//13
//메모내욜 전체보기를 위한 xml 약간 수정...
//item_recycler.xml
//5
fun setRoomMemo(memo: RoomMemo){
binding.textNum.text = "${memo.num}"
binding.textContent.text = memo.content
val sdf = SimpleDateFormat("yyyy/MM/dd hh:mm:ss")
binding.textDateTime.text = sdf.format(memo.datetime).toString()
//11-1
//setRoomMemo()를 통해 넘어온 RoomMemo임시 저장
this.mRoomMemo = memo
}
}//Holder
}//RecyclerAdapter
//7
//MainActivity.kt 에서 모든 코드를 조합해 동작가능하게 작성
-결과
이 포스팅에 작성한 내용은 고돈호, ⌜이것이 안드로이드다⌟, 한빛미디어(주), 2022 에서 발췌하였습니다.
'Android App > Kotlin' 카테고리의 다른 글
ActivityResultLanuncher의 사용 (0) | 2022.11.28 |
---|---|
카메라 및 갤러리 사용, 권한처리(CAMERA-지원중단) (0) | 2022.11.28 |
관계형 데이터베이스-SQLite 사용 (0) | 2022.11.21 |
데이터베이스-관계형 데이터베이스, 쿼리 (0) | 2022.11.21 |
파일 입출력-내부 저장소 사용(SharedPreferences, 설정화면 만들고 설정 값 사용하기) (0) | 2022.11.20 |