เมื่อใช้ไลบรารี Room Persistence เพื่อจัดเก็บข้อมูลของแอป คุณจะโต้ตอบ กับข้อมูลที่จัดเก็บไว้โดยการกำหนด ออบเจ็กต์การเข้าถึงข้อมูล หรือ DAO DAO แต่ละรายการมีฟังก์ชันที่ให้สิทธิ์เข้าถึงฐานข้อมูลของแอปแบบนามธรรม เมื่อคอมไพล์ Room จะสร้างการติดตั้งใช้งาน DAO ที่คุณกำหนดโดยอัตโนมัติ
การใช้ DAO เพื่อเข้าถึงฐานข้อมูลของแอปแทนที่จะใช้เครื่องมือสร้างการค้นหาหรือการค้นหาโดยตรง จะช่วยให้คุณแยกความกังวลออกจากกันได้ ซึ่งเป็นหลักการออกแบบที่สำคัญ นอกจากนี้ DAO ยังช่วยให้คุณจำลองการเข้าถึงฐานข้อมูลเมื่อคุณ ทดสอบแอปได้ด้วย
โครงสร้างของ DAO
คุณสามารถกำหนด DAO แต่ละรายการเป็นอินเทอร์เฟซหรือคลาสนามธรรมก็ได้ โดยปกติแล้วคุณจะใช้อินเทอร์เฟซสำหรับกรณีการใช้งานพื้นฐาน ไม่ว่าในกรณีใดก็ตาม คุณต้องใส่คำอธิบายประกอบ @Dao ใน DAO เสมอ DAO ไม่มีพร็อพเพอร์ตี้ แต่จะกำหนดฟังก์ชันอย่างน้อย 1 รายการสำหรับการโต้ตอบกับข้อมูลในฐานข้อมูลของแอป
โค้ดต่อไปนี้เป็นตัวอย่างของ DAO ที่กำหนดฟังก์ชันสำหรับการแทรก การลบ และการเลือกออบเจ็กต์ User ในฐานข้อมูล Room
@Dao interface UserDao { @Insert suspend fun insertAll(vararg users: User) @Delete suspend fun delete(user: User) @Query("SELECT * FROM user") suspend fun getAll(): List<User> }
ฟังก์ชัน DAO มี 2 ประเภทที่กำหนดการโต้ตอบกับฐานข้อมูล ได้แก่
- ฟังก์ชันอำนวยความสะดวกที่ช่วยให้คุณแทรก อัปเดต และลบแถวในฐานข้อมูลได้โดยไม่ต้องเขียนโค้ด SQL
- ฟังก์ชันการค้นหาที่ช่วยให้คุณเขียนการค้นหา SQL ของคุณเองเพื่อโต้ตอบกับฐานข้อมูล
ส่วนต่อไปนี้จะแสดงวิธีใช้ฟังก์ชัน DAO ทั้ง 2 ประเภทเพื่อกำหนดการโต้ตอบกับฐานข้อมูลที่แอปต้องการ
ฟังก์ชันอำนวยความสะดวก
Room มีคำอธิบายประกอบอำนวยความสะดวกสำหรับการกำหนดฟังก์ชันที่ทำการแทรก อัปเดต และลบโดยไม่จำเป็นต้องเขียนคำสั่ง SQL
หากต้องการกำหนดการแทรก อัปเดต หรือลบที่ซับซ้อนมากขึ้น หรือหากคุณ ต้องการค้นหาข้อมูลในฐานข้อมูล ให้ใช้ฟังก์ชันการค้นหาแทน
แทรก
คำอธิบายประกอบ @Insert ช่วยให้คุณกำหนดฟังก์ชันที่แทรกพารามิเตอร์
ลงในตารางที่เหมาะสมในฐานข้อมูลได้ โค้ดต่อไปนี้แสดงตัวอย่างฟังก์ชัน @Insert ที่ถูกต้องซึ่งแทรกออบเจ็กต์ User อย่างน้อย 1 รายการลงในฐานข้อมูล
@Dao interface UserDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertUsers(vararg users: User) @Insert suspend fun insertBothUsers(user1: User, user2: User) @Insert suspend fun insertUsersAndFriends(user: User, friends: List<User>) }
พารามิเตอร์แต่ละรายการสำหรับฟังก์ชัน @Insert ต้องเป็นอินสแตนซ์ของคลาสเอนทิตีข้อมูล Room ที่มีคำอธิบายประกอบ @Entity หรือคอลเล็กชันของอินสแตนซ์คลาสเอนทิตีข้อมูล
เมื่อมีการเรียกใช้ฟังก์ชัน @Insert Room จะแทรกอินสแตนซ์เอนทิตีแต่ละรายการที่ส่งผ่านลงในตารางฐานข้อมูลที่เกี่ยวข้อง
หากฟังก์ชัน @Insert ได้รับพารามิเตอร์เดียว ฟังก์ชันจะแสดงผลค่า Long ซึ่งเป็น rowId ใหม่สำหรับรายการที่แทรก หากพารามิเตอร์เป็นอาร์เรย์หรือคอลเล็กชัน ฟังก์ชันควรแสดงผลอาร์เรย์หรือคอลเล็กชันของค่า Long แทน โดยแต่ละค่าจะเป็น rowId สำหรับรายการที่แทรกรายการใดรายการหนึ่ง
ดูข้อมูลเพิ่มเติมเกี่ยวกับการแสดงผลค่า rowId ได้ในเอกสารอ้างอิง
สำหรับคำอธิบายประกอบ @Insert และเอกสารประกอบ SQLite
สำหรับตาราง rowid
อัปเดต
คำอธิบายประกอบ @Update ช่วยให้คุณกำหนดฟังก์ชันที่อัปเดตแถวที่เฉพาะเจาะจง
ในตารางฐานข้อมูลได้ ฟังก์ชัน @Update จะยอมรับอินสแตนซ์เอนทิตีข้อมูลเป็นพารามิเตอร์เช่นเดียวกับฟังก์ชัน @Insert โค้ดต่อไปนี้แสดงตัวอย่างฟังก์ชัน @Update ที่พยายามอัปเดตออบเจ็กต์ User อย่างน้อย 1 รายการในฐานข้อมูล
@Dao interface UserDao { @Update suspend fun updateUsers(vararg users: User) }
Room ใช้คีย์หลักเพื่อจับคู่อินสแตนซ์เอนทิตีในอาร์กิวเมนต์กับแถวใน ฐานข้อมูล หากไม่มีแถวที่มีคีย์หลักเดียวกัน Room จะไม่ทำการเปลี่ยนแปลงใดๆ
ฟังก์ชัน @Update สามารถแสดงผลค่า Int ซึ่งระบุจำนวนแถวที่อัปเดตสำเร็จได้
ลบ
คำอธิบายประกอบ @Delete ช่วยให้คุณ
กำหนดฟังก์ชันที่ลบแถวที่เฉพาะเจาะจงออกจากตารางฐานข้อมูลได้ ฟังก์ชัน @Delete จะยอมรับอินสแตนซ์เอนทิตีข้อมูลเป็นพารามิเตอร์เช่นเดียวกับฟังก์ชัน @Insert โค้ดต่อไปนี้แสดงตัวอย่างฟังก์ชัน @Delete ที่พยายามลบออบเจ็กต์ User อย่างน้อย 1 รายการออกจากฐานข้อมูล
@Dao interface UserDao { @Delete suspend fun deleteUsers(vararg users: User) }
Room ใช้คีย์หลักเพื่อจับคู่อินสแตนซ์เอนทิตีในอาร์กิวเมนต์กับแถวใน ฐานข้อมูล หากไม่มีแถวที่มีคีย์หลักเดียวกัน Room จะไม่ทำการเปลี่ยนแปลงใดๆ
ฟังก์ชัน @Delete สามารถแสดงผลค่า Int ซึ่งระบุจำนวนแถวที่ลบสำเร็จได้
Upsert
คำอธิบายประกอบ @Upsert ช่วยให้คุณ
กำหนดฟังก์ชันที่แทรกอินสแตนซ์เอนทิตีเมื่อไม่มีแถวที่ตรงกัน หรือ
อัปเดตอินสแตนซ์เอนทิตีหากมีแถวที่มีคีย์หลักเดียวกันอยู่แล้ว
ฟังก์ชัน @Upsert จะยอมรับอินสแตนซ์เอนทิตีข้อมูลเป็นพารามิเตอร์เช่นเดียวกับฟังก์ชัน @Insert และ @Update โค้ดต่อไปนี้แสดงตัวอย่างฟังก์ชัน @Upsert ที่พยายาม upsert ออบเจ็กต์ User อย่างน้อย 1 รายการในฐานข้อมูล
@Dao interface UserDao { @Upsert suspend fun upsertUsers(vararg users: User) }
หากฟังก์ชัน @Upsert ได้รับพารามิเตอร์เดียว ฟังก์ชันจะแสดงผลค่า Long หากฟังก์ชันส่งผลให้มีการแทรกแถวใหม่ ฟังก์ชันจะแสดงผล rowId ของแถวที่แทรกใหม่ หากฟังก์ชันส่งผลให้มีการอัปเดตแถวที่มีอยู่ ฟังก์ชันจะแสดงผล -1 หากพารามิเตอร์เป็นอาร์เรย์หรือคอลเล็กชัน ฟังก์ชันควรแสดงผลอาร์เรย์หรือคอลเล็กชันของค่า Long แทน
ฟังก์ชันการค้นหา
คำอธิบายประกอบ @Query ช่วยให้คุณ
เขียนคำสั่ง SQL และแสดงคำสั่งเหล่านั้นเป็นฟังก์ชัน DAO ได้ ใช้ฟังก์ชันการค้นหาเหล่านี้เพื่อค้นหาข้อมูลจากฐานข้อมูลของแอป หรือเมื่อคุณต้องการทำการแทรก อัปเดต และลบที่ซับซ้อนมากขึ้น
Room จะตรวจสอบการค้นหา SQL เมื่อคอมไพล์ ซึ่งหมายความว่าหากการค้นหามีปัญหา ระบบจะแสดงข้อผิดพลาดในการคอมไพล์แทนที่จะแสดงข้อผิดพลาดขณะรันไทม์
การค้นหาแบบง่าย
โค้ดต่อไปนี้กำหนดฟังก์ชันที่ใช้การค้นหา SELECT เพื่อแสดงผลออบเจ็กต์ User ทั้งหมดในฐานข้อมูล
@Query("SELECT * FROM user") suspend fun loadAllUsers(): List<User>
ส่วนต่อไปนี้จะแสดงวิธีแก้ไขตัวอย่างนี้สำหรับกรณีการใช้งานทั่วไป
แสดงผลคอลัมน์บางส่วนของตาราง
ส่วนใหญ่แล้วคุณจะต้องแสดงผลคอลัมน์บางส่วนจากตารางที่คุณกำลังค้นหาเท่านั้น ตัวอย่างเช่น UI อาจแสดงเฉพาะชื่อและนามสกุลของผู้ใช้แทนที่จะแสดงรายละเอียดทั้งหมดเกี่ยวกับผู้ใช้รายนั้น ให้ค้นหาเฉพาะพร็อพเพอร์ตี้ที่คุณต้องการเพื่อประหยัดทรัพยากรและเพิ่มประสิทธิภาพการดำเนินการค้นหา
Room ช่วยให้คุณแสดงผลออบเจ็กต์ข้อมูลจากการค้นหาใดก็ได้ตราบใดที่คุณแมปชุดคอลัมน์ผลลัพธ์กับออบเจ็กต์ที่แสดงผลได้ ตัวอย่างเช่น คุณสามารถกำหนดออบเจ็กต์ต่อไปนี้เพื่อเก็บชื่อและนามสกุลของผู้ใช้
data class NameTuple( @ColumnInfo(name = "first_name") val firstName: String, @ColumnInfo(name = "last_name") val lastName: String )
จากนั้นคุณจะแสดงผลออบเจ็กต์ข้อมูลดังกล่าวจากฟังก์ชันการค้นหาได้
@Query("SELECT first_name, last_name FROM user") suspend fun loadFullName(): List<NameTuple>
เนื่องจากการค้นหาแสดงผลค่าสำหรับคอลัมน์ first_name และ last_name Room จึงแมปค่าเหล่านี้กับพร็อพเพอร์ตี้ในคลาส NameTuple หากการค้นหาแสดงผลคอลัมน์ที่ไม่ได้แมปกับพร็อพเพอร์ตี้ในออบเจ็กต์ที่แสดงผล Room จะแสดงคำเตือน
แม้ว่าตัวอย่างก่อนหน้าจะใช้คลาสข้อมูลที่กำหนดเองเพื่อดึงข้อมูลคอลัมน์บางส่วน แต่ Room ยังรองรับการแสดงผล kotlin.Pair และ kotlin.Triple เพื่อความสะดวกเมื่อการค้นหาแสดงผลคอลัมน์ 2 หรือ 3 คอลัมน์พอดี เมื่อใช้ประเภทเหล่านี้ ระบบจะแมปคอลัมน์ตามลำดับที่กำหนดไว้ในคำสั่งการค้นหา ดังนั้นลำดับของคอลัมน์ในคำสั่ง SELECT ต้องตรงกับลำดับของประเภทใน Pair หรือ Triple
ส่งพารามิเตอร์อย่างง่ายไปยังการค้นหา
ส่วนใหญ่แล้วฟังก์ชัน DAO ต้องยอมรับพารามิเตอร์เพื่อให้ฟังก์ชันดำเนินการกรองได้ Room รองรับการใช้พารามิเตอร์ฟังก์ชันเป็นพารามิเตอร์การผูกในการค้นหา
ตัวอย่างเช่น โค้ดต่อไปนี้กำหนดฟังก์ชันที่แสดงผลผู้ใช้ทั้งหมดที่มีอายุมากกว่าที่กำหนด
@Query("SELECT * FROM user WHERE age > :minAge") suspend fun loadAllUsersOlderThan(minAge: Int): Array<User>
นอกจากนี้ คุณยังส่งพารามิเตอร์หลายรายการหรืออ้างอิงพารามิเตอร์เดียวกันหลายครั้งในการค้นหาได้ตามที่แสดงในโค้ดต่อไปนี้
@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge") suspend fun loadAllUsersBetweenAges(minAge: Int, maxAge: Int): Array<User> @Query( """ SELECT * FROM user WHERE first_name LIKE :search OR last_name LIKE :search """ ) suspend fun findUserWithName(search: String): List<User>
ส่งคอลเล็กชันของพารามิเตอร์ไปยังการค้นหา
ฟังก์ชัน DAO บางฟังก์ชันอาจกำหนดให้คุณส่งพารามิเตอร์จำนวนตัวแปรที่ไม่ทราบจนกว่าจะถึงเวลาที่รันไทม์ หากพารามิเตอร์แสดงถึงคอลเล็กชัน ระบบจะขยายพารามิเตอร์โดยอัตโนมัติเมื่อรันไทม์ตามจำนวนค่า
ตัวอย่างเช่น โค้ดต่อไปนี้กำหนดฟังก์ชันที่แสดงข้อมูลเกี่ยวกับผู้ใช้ทั้งหมดจากภูมิภาคบางส่วน
@Query("SELECT * FROM user WHERE region IN (:regions)") suspend fun loadUsersFromRegions(regions: List<String>): List<User>
ค้นหาตารางข้อมูลหลายรายการ
การค้นหาบางรายการอาจต้องเข้าถึงตารางข้อมูลหลายรายการเพื่อคำนวณผลลัพธ์ คุณสามารถใช้คําสั่ง JOIN ในการค้นหา SQL เพื่ออ้างอิงตารางข้อมูลมากกว่า 1 รายการ
โค้ดต่อไปนี้กำหนดฟังก์ชันที่รวมตารางข้อมูล 3 รายการเข้าด้วยกันเพื่อแสดงผลหนังสือที่ผู้ใช้รายหนึ่งยืมอยู่ในปัจจุบัน
@Query( """ SELECT * FROM book INNER JOIN loan ON loan.book_id = book.id INNER JOIN user ON user.id = loan.user_id WHERE user.name LIKE :userName """ ) suspend fun findBooksBorrowedByName(userName: String): List<Book>
นอกจากนี้ คุณยังกำหนดออบเจ็กต์ข้อมูลเพื่อแสดงผลคอลัมน์บางส่วนจากตารางข้อมูลหลายรายการที่รวมกันได้ด้วย ดูข้อมูลเพิ่มเติมได้ที่ หัวข้อแสดงผลคอลัมน์บางส่วนของตาราง โค้ดต่อไปนี้กำหนด DAO ที่มีฟังก์ชันที่แสดงผลชื่อผู้ใช้และชื่อหนังสือที่ผู้ใช้ยืม
interface UserBookDao { @Query( """ SELECT user.name AS userName, book.name AS bookName FROM user, book WHERE user.id = book.user_id """ ) fun loadUserAndBookNames(): Flow<List<UserBook>> } data class UserBook(val userName: String, val bookName: String)
แสดงผล Multimap
สำหรับการดำเนินการรวม คุณยังค้นหาคอลัมน์จากตารางข้อมูลหลายรายการได้โดยไม่ต้อง กำหนดคลาสข้อมูลเพิ่มเติมด้วยการเขียนฟังก์ชันการค้นหาที่แสดงผล Multimap
ดูตัวอย่างจากหัวข้อค้นหาตารางข้อมูลหลายรายการ แทนที่จะแสดงผลรายการอินสแตนซ์ของคลาสข้อมูลที่กำหนดเองซึ่งเก็บการจับคู่ของอินสแตนซ์ User และ Book คุณสามารถแสดงผลการแมป User และ Book ได้โดยตรงจากฟังก์ชันการค้นหา
@Query( """ SELECT * FROM user JOIN book ON user.id = book.user_id """ ) suspend fun loadUserAndBookNames(): Map<User, List<Book>>
เมื่อฟังก์ชันการค้นหาแสดงผล Multimap คุณจะเขียนการค้นหาที่ใช้คําสั่ง GROUP BY ได้ ซึ่งจะช่วยให้คุณใช้ประโยชน์จากความสามารถของ SQL ในการคำนวณและการกรองขั้นสูงได้ ตัวอย่างเช่น คุณสามารถแก้ไขฟังก์ชัน loadUserAndBookNames เพื่อแสดงผลเฉพาะผู้ใช้ที่ยืมหนังสือ 3 เล่มขึ้นไป
@Query( """ SELECT * FROM user JOIN book ON user.id = book.user_id GROUP BY user.name HAVING COUNT(book.id) >= 3 """ ) suspend fun loadUserAndBookNamesGrouped(): Map<User, List<Book>>
หากไม่จำเป็นต้องแมปออบเจ็กต์ทั้งหมด คุณยังแสดงผลการแมประหว่าง
คอลัมน์ที่เฉพาะเจาะจงในการค้นหาได้ด้วยการใช้คำอธิบายประกอบ @MapColumn ใน
พารามิเตอร์ทั่วไปของประเภทการแสดงผล
@Query( """ SELECT user.name AS username, book.name AS bookname FROM user JOIN book ON user.id = book.user_id """ ) suspend fun loadUserAndBookNamesColumns(): Map< @MapColumn(columnName = "username") String, List<@MapColumn(columnName = "bookname") String> >
ประเภทการแสดงผลพิเศษ
Room มีประเภทการแสดงผลพิเศษบางประเภทสำหรับการผสานรวมกับไลบรารี API อื่นๆ
การค้นหาแบบแบ่งหน้าด้วยไลบรารี Paging
Room รองรับการค้นหาแบบแบ่งหน้าผ่านการผสานรวมกับไลบรารี Paging หากต้องการใช้ประเภทการแสดงผล Paging 3 คุณต้องลงทะเบียนตัวแปลงประเภทการแสดงผล Paging ในฐานข้อมูลหรือ DAO โดยทำดังนี้
- รวมอาร์ติแฟกต์
androidx.room3:room3-pagingไว้ในการกำหนดค่าบิลด์ - ใส่คำอธิบายประกอบ
@Databaseหรือ@DaoDeclaration ด้วย@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class)
เมื่อลงทะเบียนแล้ว DAO จะแสดงผลออบเจ็กต์ PagingSource เพื่อใช้กับ
Paging 3 ได้
@Dao @DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class) interface UserDao { @Query("SELECT * FROM users WHERE label LIKE :query") fun pagingSource(query: String): PagingSource<Int, User> }
ดูข้อมูลเพิ่มเติมเกี่ยวกับการเลือกพารามิเตอร์ประเภทสำหรับ PagingSource ได้ที่
หัวข้อเลือกประเภทคีย์และค่า
สิทธิ์เข้าถึงการเชื่อมต่อฐานข้อมูลโดยตรง
หากตรรกะของแอปต้องเข้าถึงการเชื่อมต่อฐานข้อมูลโดยตรงในระดับต่ำ คุณสามารถใช้ API การเชื่อมต่อของ Room แทนได้ คุณสามารถรับการเชื่อมต่อได้โดยใช้useReaderConnectionสำหรับการดำเนินการแบบอ่านอย่างเดียว หรือuseWriterConnectionสำหรับการดำเนินการเขียนในอินสแตนซ์ RoomDatabase และใช้usePreparedเพื่อเรียกใช้คำสั่ง
val result: List<Pair<Long, String>> = roomDatabase.useReaderConnection { connection -> connection.usePrepared( "SELECT * FROM user WHERE age > :minAge LIMIT 5" ) { stmt -> // Bind arguments if needed stmt.bindLong(1, minAge.toLong()) buildList { // Step through the results while (stmt.step()) { add(stmt.getLong(0) to stmt.getText(1)) } } } }
หากต้องการทำธุรกรรมฐานข้อมูลระดับต่ำในการเชื่อมต่อโดยตรง คุณสามารถใช้ฟังก์ชัน Helper immediateTransaction,
deferredTransaction หรือ exclusiveTransaction ในอินสแตนซ์ Transactor ภายในบล็อก useWriterConnection
roomDatabase.useWriterConnection { transactor -> transactor.immediateTransaction { // Perform transactional database operations using transactor } }
หรือหากต้องการเรียกใช้การดำเนินการ DAO ระดับสูงในธุรกรรม ให้ใช้ฟังก์ชันส่วนขยาย Helper withReadTransaction หรือ withWriteTransaction ในอินสแตนซ์ RoomDatabase
// Perform transactional read operations (DEFERRED transaction) val userCount = roomDatabase.withReadTransaction { userDao.countUsers() } // Perform transactional write operations (IMMEDIATE transaction) roomDatabase.withWriteTransaction { userDao.insert(newUser) userDao.update(existingUser) }