Nutri Capture 백엔드 게시글 시리즈
#1 개요
#1-1 우아함
이전 게시글에서 만든, 가상 테이블을 참조하는 DAO 함수는 가상 테이블의 모든 레코드를 통째로 가져오는 방식이었다. 하지만 이는 우아함과는 거리가 있는 방식이다. 처음에는 화면을 가득 채울 정도의 레코드만 가져오고, 무한 스크롤을 통해서 필요한 만큼만 또 가져오는 방식을 구현할 것이다.
"View가 보유한 마지막 레코드가 무엇인지 알고, 해당 레코드를 기반으로 (테이블 위치 상) 더 뒤에 있는 레코드들을 뽑아오면 될 것이다."라고 처음에는 간단하게만 생각했다. 그러나 시행착오를 겪으며 시간이 오래 걸렸다 (고작 DAO 함수하나 만드는 건데 이전 게시글과 별도의 게시글로 분리할 이유기도 하다).
#1-2 첫 시도: Cursor
Room DAO를 사용하여 데이터 액세스 | Android Developers
Room 라이브러리의 일부인 DAO(데이터 액세스 객체)를 사용하여 데이터베이스 테이블을 수정하는 방법 알아보기
developer.android.com
제일 좋은 방법은, Cursor를 이용하는 방법이라고 생각했다. 왜냐하면 이전 게시글에서 만들었던 가상 테이블은 총 3개의 정렬 기준으로 이미 정렬된 상태의 테이블이기 때문이었다. 그러니까, View가 보유한 마지막 레코드의 직후 레코드에 Cursor를 두고 한 칸씩 내려가며 데이터를 불러오고 싶었던 거다. 듣기에는 직관적으로 보인다.
허나 위 공식 문서의 경고문이 마음에 걸린다. "Cursor API를 사용하는 것은 권장하지 않습니다. 행이 존재하는지 또는 행에 어떤 값이 들어 있는지 보장하지 않기 때문입니다. 커서를 예상하는 코드가 이미 있고 쉽게 리팩토링할 수 없는 경우에만 이 기능을 사용하세요."라는 경고는 괜히 있는 게 아닐 것이다.
이론상으로 Nutri Capture 프로젝트에 Cursor를 쓰는 게 나빠보이지는 않는다만, 그래도 공식문서의 우려(?)에 따라주기로 한다. Cursor를 사용하는 것이 자원 효율 혹은 DB 속도에 관해 더 빠르다는 판단이 선다면, 그리고 Cursor 사용에 대한 잠재적인 에러를 통제할 수 있다는 생각이 든다면 나중에 Cursor 방식으로 변경할지도 모르겠다.
#1-3 두번째 시도: 평범한 SELECT문
여느 DAO에서나 볼 법한 평범한 SELECT문 함수로 가상 테이블의 레코드를 가져온다. 그 구현은 #2에 있다.
#2 코드
#2-1 DAO (MainDAO.kt)
...
@Dao
interface MainDAO {
...
@Query("SELECT * FROM DayMealView")
suspend fun getAllDayMeals(): List<DayMealView>
@Query("""
SELECT * FROM DayMealView
WHERE day_date <= :lastDate
AND meal_time <= :lastTime
AND meal_id != :lastId
LIMIT :limit
""")
suspend fun getNextDayMealsAfter(
lastDate: LocalDate,
lastTime: LocalTime,
lastId: Long,
limit: Int
): List<DayMealView>
@Query("SELECT * FROM DayMealView WHERE meal_id = :mealId LIMIT 1")
suspend fun getDayMeal(mealId: Long): DayMealView
}
내림차순으로 DayMeal을 View에 표시할 것이기 때문에 마지막 DayMeal보다 시간적으로 오래된 것을 가져오게 한다. 해당 함수의 이름을 getNextDayMealsAfter()로 명명했는데, 이전 게시글에서 만든 함수 getAllMeals()와의 이름 통일감을 위해서 getAllMeals()를 getAllDayMeals()로 변경한다.
또, getDayMeal() 함수도 정의한다. 로그 확인을 위해 필요하다.
#2-2 Repository (MainRepository.kt)
...
class MainRepository(private val dao: MainDAO) {
...
suspend fun getAllDayMeals(): List<DayMealView> {
return dao.getAllDayMeals()
}
suspend fun getNextDayMealsAfter(lastDayMeal: DayMealView, limit: Int): List<DayMealView> {
return dao.getNextDayMealsAfter(
lastDayMeal.date, lastDayMeal.time, lastDayMeal.mealId, limit
)
}
suspend fun getDayMeal(mealId: Long): DayMealView {
return dao.getDayMeal(mealId)
}
}
DAO에서는 문법의 한계로 DayMeal에 대한 정보를 3개의 인수로 나눠 받았다. Repository에서는 DAO와 똑같이 답습하지 않는다. DayMeal 객체를 받아서 정보를 뽑아낸다. 그리고 DAO에 잘 전달해준다. 또, DAO에서 이름을 변경했던 함수를 참조하는 Repository의 함수도 이름을 (getAllDayMeals()로) 바꿔준다.
DAO의 getDayMeal()를 참조하는 Repository의 getDayMeal()도 만들어준다.
#2-3 ViewModel (NutrientViewModel.kt)
...
class NutrientViewModel(private val repository: MainRepository) : ViewModel() {
// (1) 화면 표시용 State
...
// (2) ViewModel용 내부 변수
...
// (3) View에서 받아 처리할 이벤트
...
// (4) View로부터 받은 이벤트 처리
...
// (5) DayMealView 작동 확인용 로그
fun log() {
viewModelScope.launch {
Log.i("interfacer_han, getAllDayMeals()", repository.getAllDayMeals().toString())
Log.i("interfacer_han, getNextDayMealsAfter()",
repository.getNextDayMealsAfter(
repository.getDayMeal(6),
100
).toString()
)
}
}
}
뷰모델의 log() 함수를 위와 같이 변경한다. 새롭게 만든 2번째 로그 메시지에서는, (DayMealView에서) mealId가 6인 레코드 이후의 레코드를 보여줄 것이다.
#3 요약
가상테이블 참조에 필요한 DAO 및 Repository 함수를 정의했다. 다음 게시글에선 프론트에서 DAO 및 Repository의 함수를 참조하게 만들어본다.
#4 완성된 앱
#4-1 로그 메시지 (2번째 로그 메시지)
[
DayMealView(
dayId=2, mealId=5, date=2024-11-12, time=07:48:08.852377, ...
),
DayMealView(
dayId=2, mealId=4, date=2024-11-12, time=07:48:08.591206, ...
),
DayMealView(
dayId=1, mealId=3, date=2024-11-11, time=07:48:07.773742, ...
)),
DayMealView(
dayId=1, mealId=2, date=2024-11-11, time=07:48:07.481481, ...
),
DayMealView(
dayId=1, mealId=1, date=2024-11-11, time=07:48:07.159289, ...
)
]
로그 메시지가 잘 출력되는 모습이다. 첫번째 로그 메시지는 여기에 있다.
#4-2 이 게시글 시점의 Commit
GitHub - Kanmanemone/nutri-capture-new
Contribute to Kanmanemone/nutri-capture-new development by creating an account on GitHub.
github.com
#4-3 본 프로젝트의 가장 최신 Commit
GitHub - Kanmanemone/nutri-capture-new
Contribute to Kanmanemone/nutri-capture-new development by creating an account on GitHub.
github.com
'개발 일지 > Nutri Capture' 카테고리의 다른 글
Nutri Capture 백엔드 - Room 무결성 보완 (1) | 2024.11.15 |
---|---|
Nutri Capture 프론트엔드 - 채팅UI 구조 잡기 (0) | 2024.11.14 |
Nutri Capture 백엔드 - Room 가상 테이블 선언 (0) | 2024.11.10 |
Nutri Capture 방향성 - 채팅 UI (1) | 2024.11.08 |
Nutri Capture 백엔드 - View에서 INSERT 트리거 (0) | 2024.10.23 |