Unit 5-1

jiwon·2022년 2월 8일
0

코틀린

목록 보기
14/16
post-thumbnail
post-custom-banner

관계형 데이터베이스 개요

관계형 데이터베이스는 데이터를 테이블과 열, 행으로 구성하는 일반적인 데이터베이스 유형이다.

위 그림처럼 테이블간의 관계를 표현할 수 있어서 관계형 데이터베이스라고 한다.

SQL

관계형 데이터베이스에 액세스할 때는 SQL이 필요하다. SQL은 구조화된 쿼리 언어를 의미하며 이를 통해 관계형 데이터베이스에서 데이터를 읽고 조작할 수 있다.

실습의 스켈레톤 코드는 기본 db가 설정되어있다. View > Tool Windows > Database Inspector 을 누르면 데이터베이스를 볼 수 있다.


원하는 테이블을 선택한 상태로 위쪽 돋보기붙은 버튼(Open New Query Tab)을 누르면 명령어를 입력할 수 있는 텍스트 상자가 뜬다.


명령어를 입력하고 오른쪽 하단 Run 버튼을 눌러 실행할 수 있다.

SELECT * FROM park
SELECT city FROM park
SELECT name, established, city FROM park

익숙한 select 문...

SELECT name FROM park
LIMIT 5

반환되는 행 수에 제한을 설정하려면 LIMIT를 사용한다.

SELECT name FROM park
WHERE type = "national_park"

SELECT name FROM park
WHERE type != "recreation_area"
AND area_acres > 100000

익숙한 where문..

SELECT COUNT(*) FROM park

SELECT SUM(park_visitors) FROM park

SELECT MAX(area_acres) FROM park
WHERE type = 'national_park'

익숙하지만 자꾸 까먹는 집계 함수..

SELECT DISTINCT type FROM park

DISTINCT 키워드를 사용하여 쿼리 결과에서 중복 값을 제거할 수 있다.

SELECT name FROM park
ORDER BY name

SELECT name FROM park
ORDER BY name DESC //내림차순

ORDER BY 절을 사용하여 SELECT 문의 결과를 정렬할 수 있다.

SELECT type, name FROM park
GROUP BY type
ORDER BY name

GROUP BY절로 묶을 수 있다..

INSERT INTO table_name
VALUES (column1, column2, ...)

익숙한 insert문..

UPDATE park
SET area_acres = 46,
established = 1088640000,
type = 'office'
WHERE name = 'Googleplex'

update문...여러개를 한꺼번에 업뎃할 수 있다..

DELETE FROM park
WHERE name = 'Googleplex'

익숙한 delete 문...

Room

Android 앱에서 데이터베이스를 쉽게 사용하는 방법은 Room이라는 라이브러리를 사용하는 것이다. Room은 ORM(객체 관계형 매핑) 라이브러리라고 하며 이름 그대로 데이터베이스의 테이블을 Kotlin 코드에서 사용할 수 있는 객체에 매핑한다.

Room을 이용해 db를 읽어서 리사이클러뷰에 띄우는 실습을 해볼것이다.

// Entity 주석 있으면 Room이 이 클래스를 db 테이블을 정의하는 데 사용할 수 있는 것으로 인식한다.
@Entity  
data class Schedule(
    @PrimaryKey val id: Int,
    @NonNull @ColumnInfo(name = "stop_name") val stopName: String,
    @NonNull @ColumnInfo(name = "arrival_time") val arrivalTime: Int
)

테이블 만들기 위한 데이터 클래스 정의. Int->Integer 같은 건 신경쓸 필요 없다. Room이 알아서 바꿔줌.

DAO

Room을 통합하기 위해 추가해야 할 다음 클래스는 DAO이다. DAO는 데이터 액세스 객체를 나타내며 데이터 액세스 권한을 제공하는 Kotlin 클래스이다. DAO에서 함수를 호출하는 것은 데이터베이스에서 SQL 명령어를 실행하는 것과 같다.

@Dao
interface ScheduleDao {
    @Query("SELECT * FROM schedule ORDER BY arrival_time ASC")
    fun getAll(): List<Schedule>

    @Query("SELECT * FROM schedule WHERE stop_name = :stopName ORDER BY arrival_time ASC")
    fun getByStopName(stopName: String): List<Schedule>
}


뷰에 노출하는 DAO의 일부를 뷰 모델이라는 별도의 클래스로 분리하는 것이 좋다. 이는 모바일 앱의 일반적인 아키텍처 패턴이다. 뷰 모델을 사용하면 앱의 UI 코드와 데이터 모델을 명확하게 구분할 수 있다. 이전 코드랩에서 배웠든 ViewModel은 수명주기도 관리하기 때문에 Dao는 데이터 로드에 집중할 수 있다.

//아까 만든 Dao 클래스를 매개변수로 사용
class BusScheduleViewModel(private val scheduleDao: ScheduleDao): ViewModel() {
    fun fullSchedule(): List<Schedule> = scheduleDao.getAll()
    fun scheduleForStopName(name: String): List<Schedule> = scheduleDao.getByStopName(name)
}
//수명 주기에 응답할 수 있는 객체를 인스턴스화
class BusScheduleViewModelFactory(
    private val scheduleDao: ScheduleDao
) : ViewModelProvider.Factory {
    
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(BusScheduleViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return BusScheduleViewModel(scheduleDao) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

데이터베이스 클래스 만들기 및 데이터베이스 미리 채우기

AppDatabase 클래스를 사용하면 모델과 DAO 클래스, 실행하려는 모든 데이터베이스 설정을 완벽하게 제어할 수 있다.

//RoomDatabase에서 상속받는 추상 클래스 AppDatabase
@Database(entities = arrayOf(Schedule::class), version = 1)
abstract class AppDatabase: RoomDatabase() {
    abstract fun scheduleDao(): ScheduleDao

    //데이터베이스 객체는 한개만 있으면 됨
    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null
        // AppDatabase 인스턴스를 반환하는 함수

        fun getDatabase(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context,
                    AppDatabase::class.java,
                    "app_database")
                    .createFromAsset("database/bus_schedule.db") //데이터 로드
                    .build()
                INSTANCE = instance

                instance
            }
        }


    }
}

이제 Application 클래스의 맞춤 서브클래스를 제공하면 db를 읽을 수 있다.

class BusScheduleApplication: Application() {
    //getDatabase()의 결과를 보유할 lazy 속성
    val database: AppDatabase by lazy { AppDatabase.getDatabase(this)}
}


ListAdapter를 적절히 구현하고 나면 대강 어플이 만들어진다.

Flow

앱에서는 아직 동적 업데이트를 처리할 수 없다. db에 변경이 있을시, 확인하려면 앱을 종료했다 다시 실행해야 한다.
이 문제는 List<Schedule>이 각 DAO 함수에서 한 번만 반환되기 때문에 발생한다. 이 문제를 수정하려면 DAO가 데이터베이스에서 데이터를 지속적으로 내보낼 수 있는 Flow라는 Kotlin 기능을 활용하면 된다.

Dao 클래스 먼저 수정하자.

@Dao
interface ScheduleDao {
    @Query("SELECT * FROM schedule ORDER BY arrival_time ASC")
    //fun getAll(): List<Schedule>
    fun getAll(): Flow<List<Schedule>> //변경

    @Query("SELECT * FROM schedule WHERE stop_name = :stopName ORDER BY arrival_time ASC")
    //fun getByStopName(stopName: String): List<Schedule>
    fun getByStopName(stopName: String): Flow<List<Schedule>> //변경
}

DAO에 액세스하는 뷰 모델의 함수도 업데이트한다.

class BusScheduleViewModel(private val scheduleDao: ScheduleDao): ViewModel() {
    //아래 두 줄 변경
    fun fullSchedule(): Flow<List<Schedule>> = scheduleDao.getAll()
    fun scheduleForStopName(name: String): Flow<List<Schedule>> = scheduleDao.getByStopName(name)
}

이제 실시간으로 db가 변하면 ui도 업뎃된다.

profile
개발 공부합니다. 파이팅!
post-custom-banner

0개의 댓글