前一篇文章介紹了 ClassroomSchedule.kt 作為 RoomRush 的 Room Entity,負責定義 classroom_schedules 資料表的欄位結構。
有了資料表之後,下一個核心問題就是:App 該如何對這張資料表進行讀寫?
今天我們要深入探討的 ClassroomScheduleDao.kt,正是資料層主線中負責定義這些 SQL 操作介面的核心主角。
起初,我其實不太確定這項檔案主要是做什麼用的,可能是我自己對 DAO 的概念不太熟悉。
後來查詢相關資料後,我才了解:在 Android 的現代資料庫架構中,所有對資料庫的 CRUD(新增、查詢、更新、刪除)操作,都不會直接寫在 Activity 或 ViewModel 裡,而是會統一封裝在 DAO(Data Access Object,資料存取物件) 中。
ClassroomScheduleDao.kt 負責什麼和 Hermes Agent 一起重讀檔案後,我理解ClassroomScheduleDao.kt是負責定義資料表的操作方法,例如查詢、新增、更新、刪除。
在 RoomRush 中,主要包含以下四大核心操作:
@Query("SELECT * FROM classroom_schedules ORDER BY classroom ASC")
fun getAll(): Flow<List<ClassroomSchedule>>
classroom 按教室名稱升冪排序,並用 Flow 回傳可被持續觀察的清單。@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(schedule: ClassroomSchedule)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(schedules: List<ClassroomSchedule>)
@Update
suspend fun update(schedule: ClassroomSchedule)
@Delete
suspend fun delete(schedule: ClassroomSchedule)
insert:新增一筆,IGNORE 表示主鍵存在就忽略。insertAll(schedules):批次新增多筆,REPLACE 表示主鍵存在就用新資料覆蓋舊資料。@Update 更新已存在的一筆教室課表。@Delete 刪除一筆教室課表。@Query("SELECT * FROM classroom_schedules WHERE classroom LIKE :buildingAndFloorPrefix ORDER BY classroom ASC")
suspend fun getSchedulesByBuildingAndFloor(buildingAndFloorPrefix: String): List<ClassroomSchedule>
LIKE 關鍵字搭配萬用字元,例如: "SF3%",就能快速比對並撈出所有以 SF3 開頭的教室,例如 SF304、SF309、SF310 。@Query("SELECT * FROM classroom_schedules WHERE classroom = :classRoomName")
fun getClassroomDetails(classRoomName: String): Flow<ClassroomSchedule?>
@Query("SELECT * FROM classroom_schedules WHERE classroom = :classroomName LIMIT 1")
suspend fun findByName(classroomName: String): ClassroomSchedule?
getClassroomDetails() 回傳 Flow,可以持續觀察資料變化;findByName() 是一次性查詢。RoomRush 裡,Entity、DAO、Repository 各自關係負責如下:
ClassroomSchedule
「定義資料結構」
│
│ Entity
▼
Room Database
「實際儲存資料」
▲
│
ClassroomScheduleDao
「定義資料庫操作」
▲
│
Repository
「整理、封裝 DAO」
▲
│
ViewModel
「取得畫面需要的資料」
ClassroomScheduleDao 是 RoomRush 的資料存取物件,負責定義如何操作 classroom_schedules 資料表。
getAll() 會取得所有教室課表並以 Flow<List<ClassroomSchedule>> 回傳,讓資料變動時可以被觀察。insert()、insertAll()、update()、delete() 分別負責新增、批次新增、更新與刪除。getSchedulesByBuildingAndFloor() 可以用前綴查某棟某層的教室。getClassroomDetails() 則用於詳細頁取得單一教室資料。DAO 不處理 UI,也不決定畫面怎麼顯示,它只定義資料庫操作。
DAO 裡確實有一些 查詢方法,但目前查詢頁真正的空教室篩選邏輯,主要還是在 QueryResultViewModel 裡完成。
也就是說,資料是從資料層拿出來的,但「哪些教室算空教室」這個判斷,還是在 ViewModel 裡進行。
實際的查詢流程:
QueryResultViewModel
↓
Repository
↓
ClassroomScheduleDao
↓
Room Database
↓
取得 ClassroomSchedule
↓
回到 QueryResultViewModel
↓
大樓 / 樓層 / 時段篩選
↓
判斷是否為 X
↓
emptyRooms
這是我原本沒注意到的細節,之後若需要維護的話,可以考慮把查詢邏輯整合在一起。
讀完 ClassroomScheduleDao 後,我開始理解 Room 架構裡 Entity 和 DAO 的分工。
ClassroomSchedule 定義資料表長什麼樣子,而 DAO 則定義 App 可以對這張表做哪些操作。這些操作包含查全部資料、新增、批次匯入、更新、刪除,也包含根據教室名稱查單一教室資料。
這個檔案也讓我注意到一個值得思考的地方:專案裡已經有 DAO 層的查詢方法,但目前查詢空教室的核心邏輯主要仍寫在 QueryResultViewModel 裡。
這不一定是錯,但它提醒我,未來如果要改善維護性,可以思考查詢邏輯究竟應該放在 ViewModel、Repository,還是 DAO 裡。
了解了 DAO 如何操作 classroom_schedules 資料表後,接下來登場的是資料層的統一接口: ClassroomScheduleRepository.kt。
為什麼 ViewModel 不該直接碰資料庫?Repository 又是如何統整資料來源並將資料流(Flow)向上拋給邏輯層?
儘管回頭檢視時,發現當時的分工有些小瑕疵,但依然清楚體現了資料庫封裝的設計意圖;下一篇,我們將拆解這個串聯「資料庫 DAO」與「邏輯 ViewModel」的關鍵橋樑。