Room เป็นไลบรารีสำหรับทำงานกับ SQLite ใน Android ซึ่งเป็นส่วนหนึ่งของ Jetpack โดยให้เลเยอร์นามธรรมเหนือ SQLite ดั้งเดิม ทำให้การสร้างตาราง การดำเนินการค้นหา และการแปลงข้อมูลเป็นวัตถุ Kotlin และ Java เป็นอัตโนมัติ ตาม Android Developers Room จะคอมไพล์คำสั่ง SQL ในเวลาสร้าง ตรวจสอบความถูกต้องของไวยากรณ์และความสัมพันธ์ระหว่าง Entity และตาราง
ประเด็นสำคัญ
Room คือไลบรารีการคงอยู่ของ Android Jetpack ที่ให้การแมปวัตถุ-เชิงสัมพันธ์สำหรับ SQLite Room แก้ปัญหาหลักสามประการของ SQLite ดั้งเดิม: การเขียนโค้ดต้นแบบปริมาณมากสำหรับสร้างตาราง การขาดการตรวจสอบคำสั่ง SQL ในเวลาคอมไพล์ และการแปลง Cursor เป็นวัตถุด้วยตนเอง
ไลบรารีใช้ตัวประมวลผลคำอธิบายประกอบ (kapt หรือ KSP) ที่สร้างการใช้งานของคลาสนามธรรม RoomDatabase และ DAO ในเวลาสร้าง สิ่งนี้ทำให้มั่นใจว่าข้อผิดพลาดทางไวยากรณ์ใน SQL และความไม่ตรงกันของชนิดถูกค้นพบก่อนเรียกใช้แอปพลิเคชัน แทนที่จะเป็นในรันไทม์หลังจากเผยแพร่บน Google Play
ตาม Google I/O 2023 Room ถูกใช้ใน 68% ของแอปพลิเคชัน Android ที่ทำงานกับข้อมูลภายในเครื่อง เป็นมาตรฐานสำหรับการจัดเก็บข้อมูลบนอุปกรณ์ที่ Google แนะนำสำหรับโปรเจกต์ใหม่ทั้งหมด — แทนที่ SQLiteOpenHelper และ ContentProvider ที่ล้าสมัย
นำ Room ไปใช้ในโปรเจกต์ที่ต้องการแคชข้อมูลภายในเครื่องจากเซิร์ฟเวอร์ โหมดออฟไลน์ หรือการจัดเก็บข้อมูลผู้ใช้ที่มีโครงสร้างพร้อมความสามารถในการดำเนินการคำสั่ง SQL ที่ซับซ้อน
Room เป็นส่วนหนึ่งของ Android Jetpack และได้รับการแนะนำอย่างเป็นทางการโดย Google สำหรับโปรเจกต์ใหม่ทั้งหมดที่ทำงานกับข้อมูลภายในเครื่อง แตกต่างจาก Realm หรือ ObjectBox Room ใช้ SQLite ดั้งเดิม ซึ่งรับประกันความเข้ากันได้กับเครื่องมือฐานข้อมูลของบริษัทอื่นใด ๆ ตั้งแต่ DB Browser ถึง DataGrip นักพัฒนาสามารถเปิดไฟล์ .db ของแอปพลิเคชันและดำเนินการคำสั่ง SQL ได้โดยตรง ซึ่งช่วยให้การดีบักและวิเคราะห์ข้อมูลระหว่างการพัฒนาง่ายขึ้น
Entity คือคลาสข้อมูลที่มีคำอธิบายประกอบ @Entity ซึ่ง Room แปลงเป็นตารางฐานข้อมูล แต่ละฟิลด์ของคลาสกลายเป็นคอลัมน์ของตาราง และแต่ละอินสแตนซ์กลายเป็นแถว Room ใช้รีเฟลกชันเพื่อเข้าถึงฟิลด์ ดังนั้นจึงจำเป็นต้องมีคำอธิบายประกอบ @PrimaryKey สำหรับตัวระบุที่จำเป็น
คำอธิบายประกอบ @Entity บอก Room ว่าคลาสเป็นตาราง พารามิเตอร์ tableName กำหนดชื่อตารางหากแตกต่างจากชื่อคลาส @PrimaryKey กำหนดคีย์หลักพร้อมรองรับการสร้างอัตโนมัติผ่าน autoGenerate = true
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
@ColumnInfo(name = "full_name")
val name: String,
@Ignore
val tempData: String?
)
@ColumnInfo ระบุชื่อคอลัมน์ในตารางหากแตกต่างจากชื่อฟิลด์ Kotlin @Ignore ไม่รวมฟิลด์ออกจากตาราง — ฟิลด์นั้นจะไม่ถูกบันทึกลงฐานข้อมูล @ForeignKey อธิบายคีย์ต่างประเทศสำหรับความสัมพันธ์ระหว่างตารางกับการดำเนินการแบบเรียงซ้อนเมื่อลบหรืออัปเดต
Room รองรับวัตถุที่ซ้อนกันผ่านคำอธิบายประกอบ @Embedded ฟิลด์ของคลาสที่ซ้อนกันจะขยายเป็นคอลัมน์ของตารางหลักด้วยคำนำหน้าเพื่อหลีกเลี่ยงความขัดแย้งของชื่อ ตัวอย่างเช่น คลาส Address ที่มีฟิลด์ city และ street ฝังอยู่ใน User จะสร้างคอลัมน์ address_city และ address_street ในตาราง users ทำให้ไม่จำเป็นต้องมีตารางแยกสำหรับวัตถุค่าอย่างง่าย
Room รองรับเฉพาะชนิดดั้งเดิมและตัวหุ้มเท่านั้น สำหรับจัดเก็บลิสต์ Date หรือชนิดที่กำหนดเอง ให้ใช้ @TypeConverter — เมธอดแบบสแตติกสำหรับแปลงระหว่างชนิดที่กำหนดเองและชนิดดั้งเดิมของ SQLite ตัวอย่างเช่น ระหว่าง List และสตริง JSON
DAO (วัตถุเข้าถึงข้อมูล) คืออินเทอร์เฟซหรือคลาสนามธรรมที่มีคำอธิบายประกอบ @Dao ซึ่งมีเมธอดสำหรับเข้าถึงข้อมูล แต่ละเมธอดมีคำอธิบายประกอบด้วยการดำเนินการ SQL: @Insert, @Update, @Delete หรือ @Query พร้อมคำสั่ง SQL ที่ชัดเจน
คำอธิบายประกอบ @Query รับสตริง SQL ที่ Room ตรวจสอบในเวลาคอมไพล์เพื่อความถูกต้องของไวยากรณ์และการจับคู่ชื่อคอลัมน์กับฟิลด์ Entity Room รองรับคำสั่งแบบมีพารามิเตอร์ผ่านไวยากรณ์ :paramName
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getUserById(userId: Int): User?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(user: User)
@Query("SELECT * FROM users ORDER BY name ASC")
fun getAllUsers(): Flow<List<User>>
}
@Insert รองรับตัวเลือก OnConflictStrategy สำหรับจัดการความขัดแย้งเมื่อแทรกเรกคอร์ดที่ซ้ำกัน Flow เป็นชนิดส่งคืนให้การอัปเดต UI เชิงโต้ตอบทุกครั้งที่มีการเปลี่ยนแปลงข้อมูลในตาราง — การสมัครรับข้อมูลจะเริ่มใหม่โดยอัตโนมัติเมื่อมีการ INSERT, UPDATE หรือ DELETE
คำอธิบายประกอบ @Transaction รับประกันการดำเนินการแบบอะตอมมิกของการดำเนินการหลายรายการในบล็อกธุรกรรมเดียว Room ล็อกฐานข้อมูลระหว่างการดำเนินการ ป้องกันสภาวะการแข่งขันเมื่อเข้าถึงพร้อมกันจากหลายเธรด
RoomDatabase คือคลาสนามธรรมที่รวม Entity และ DAO เข้าเป็นจุดเข้าถึงฐานข้อมูลเดียว สร้างขึ้นผ่าน Room.databaseBuilder พร้อมเวอร์ชันสคีมาและรายการคลาส Entity ควรสร้างอินสแตนซ์ฐานข้อมูลเป็นซิงเกิลตันผ่านตัวแทนขี้เกียจเพื่อหลีกเลี่ยงการเชื่อมต่อหลายครั้ง
การโยกย้าย ใน Room คือคลาส Migration ที่อธิบายสคริปต์ SQL สำหรับการเปลี่ยนจากเวอร์ชันสคีมาเก่าไปยังเวอร์ชันใหม่ หากไม่ได้ระบุการโยกย้ายเมื่อสคีมาเปลี่ยนแปลง Room จะ throw IllegalStateException ซึ่งป้องกันการสูญเสียข้อมูลผู้ใช้โดยไม่ได้ตั้งใจเมื่ออัปเดตแอปพลิเคชัน
val migration_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE users ADD COLUMN age INTEGER NOT NULL DEFAULT 0")
}
}
val db = Room.databaseBuilder(
getApplication(),
AppDatabase::class.java,
"app_database"
).addMigrations(migration_1_2)
.build()
สำหรับการพัฒนา คุณสามารถใช้ fallbackToDestructiveMigration ซึ่งจะลบฐานข้อมูลเก่าและสร้างใหม่เมื่อเวอร์ชันไม่ตรงกัน โหมดนี้มีไว้สำหรับการดีบักเท่านั้น — รุ่นโปรดักชันต้องรวมการโยกย้ายที่เหมาะสม
สำหรับการทดสอบฐานข้อมูล Room มีคลาสพิเศษ Room.inMemoryTestBuilder ที่สร้างฐานข้อมูลในหน่วยความจำโดยไม่บันทึกลงดิสก์ หลังจากการทดสอบแต่ละครั้งเสร็จสมบูรณ์ ฐานข้อมูลจะถูกทำลายโดยอัตโนมัติ ทำให้มั่นใจได้ถึงการแยกสถานการณ์การทดสอบอย่างสมบูรณ์ เมื่อรวมกับไลบรารี android-arch-core-testing นักพัฒนาสามารถจัดการวงจรชีวิตของฐานข้อมูลและตรวจสอบความถูกต้องของการโยกย้ายโดยไม่ต้องทำความสะอาดสถานะด้วยตนเอง
ประสิทธิภาพของ Room ขึ้นอยู่กับโครงสร้างคำสั่งและดัชนีโดยตรง สำหรับการวิเคราะห์คำสั่งที่ช้า Room มีแฟล็ก enableQueryCallback ซึ่งบันทึกคำสั่ง SQL ทั้งหมดพร้อมเวลาในการดำเนินการ นักพัฒนาสามารถใช้บันทึกนี้เพื่อค้นหาคำสั่งที่ใช้เวลานานกว่า 100 มิลลิวินาทีและปรับให้เหมาะสมโดยเพิ่มดัชนีผสมผ่านคำอธิบายประกอบ @Index ใน @Entity หรือเขียนคำสั่งย่อยใหม่เป็นการดำเนินการ JOIN โดยตรงโดยใช้ @Relation
Room ยังรองรับการเข้ารหัสฐานข้อมูลผ่าน SQLCipher การเพิ่มไลบรารี net.zetetic:android-database-sqlcipher และใช้ SupportFactory แทนมาตรฐาน จะให้การเข้ารหัสที่โปร่งใสของข้อมูลทั้งหมดบนดิสก์โดยไม่เปลี่ยนคำสั่ง DAO หรือโครงสร้าง Entity ซึ่งจำเป็นสำหรับแอปพลิเคชันที่จัดการข้อมูลส่วนบุคคลของผู้ใช้และสอดคล้องกับ GDPR และกฎหมายของรัฐบาลกลางรัสเซีย 152-FZ เกี่ยวกับการคุ้มครองข้อมูลส่วนบุคคล รหัสผ่านการเข้ารหัสสามารถจัดเก็บใน Android Keystore เพื่อป้องกันการดึงข้อมูลผ่านเครื่องมือบนอุปกรณ์ที่รูท
Room รองรับ Kotlin Coroutines ตั้งแต่เวอร์ชัน 2.1 เมธอด DAO สามารถเป็นฟังก์ชัน suspend ที่ดำเนินการคำสั่งในเธรดพื้นหลังโดยไม่บล็อกเธรดหลัก Room จัดการตัวจัดส่งโดยอัตโนมัติ โดยใช้ Dispatchers.IO สำหรับคำสั่งอ่านและเขียน
สำหรับคำสั่งเชิงโต้ตอบ Room จะส่งคืน Flow — สตรีมข้อมูลเย็นที่ปล่อยค่าใหม่ทุกครั้งที่มีการเปลี่ยนแปลงในตารางที่เกี่ยวข้อง ViewModel สมัครรับ Flow ผ่าน stateIn หรือ collect ให้การอัปเดต UI อัตโนมัติโดยไม่ต้องแจ้งอะแดปเตอร์ด้วยตนเอง
Room ยังรองรับ Paging 3 ผ่านการใช้งาน PagingSource พิเศษที่โหลดข้อมูลทีละหน้าจาก SQLite ซึ่งมีประสิทธิภาพสำหรับรายการขนาดใหญ่ที่มีเรกคอร์ดนับพัน: Paging 3 โหลดเฉพาะแถวที่มองเห็นบนหน้าจอและอัปเดตโดยอัตโนมัติเมื่อมีการเปลี่ยนแปลงในฐานข้อมูล
ใช้ Paging 3 กับ Room สำหรับแสดงฟีดข่าว บันทึกการดำเนินการ หรือรายการสินค้าที่มีการเข้าถึงแบบออฟไลน์และการเลื่อนแบบไม่สิ้นสุด
คำถามที่พบบ่อย
Room ทำให้การสร้างตาราง การแปลง Cursor เป็นวัตถุ และการตรวจสอบ SQL ในเวลาคอมไพล์เป็นอัตโนมัติ SQLiteOpenHelper ต้องเขียนสคีมาด้วยตนเอง จัดการ Cursor และไม่มีการตรวจสอบคำสั่งก่อนเรียกใช้แอปพลิเคชัน ซึ่งเพิ่มความเสี่ยงของข้อผิดพลาด
ใช่ เมื่อเปลี่ยน Entity (เพิ่ม/ลบฟิลด์ เปลี่ยนชนิด) จำเป็นต้องมีการโยกย้าย หากไม่มี Room จะ throw IllegalStateException เมื่อเริ่มต้น สำหรับการพัฒนา คุณสามารถเปิดใช้ fallbackToDestructiveMigration แต่รุ่นโปรดักชันต้องมีสคริปต์การโยกย้ายที่ถูกต้อง
Room รองรับ @ForeignKey สำหรับการดำเนินการแบบเรียงซ้อนและ @Relation สำหรับวัตถุที่ซ้อนกัน สำหรับคำสั่ง JOIN ที่ซับซ้อน ให้ใช้คำอธิบายประกอบ @Transaction กับ @Query ที่ส่งคืน POJO พร้อมเอนทิตีที่ซ้อนกันผ่าน @Embedded และ @Relation
ใช่ Room เข้ากันได้อย่างสมบูรณ์กับ Java แทนที่จะใช้ฟังก์ชัน suspend ให้ใช้ LiveData หรือ RxJava Observable แทน Flow ให้ใช้ LiveData Room กับ Java รองรับคำอธิบายประกอบเดียวกันทั้งหมด แต่ต้องใช้โค้ดต้นแบบมากกว่าสำหรับการดำเนินการแบบอะซิงโครนัส
Room รองรับการเข้ารหัสผ่าน SQLCipher โดย Zetetic แทนที่จะใช้ Room.databaseBuilder ให้ใช้ SupportFactory จากไลบรารี net.zetetic:android-database-sqlcipher โดยส่งรหัสผ่านการเข้ารหัส ข้อมูลทั้งหมดบนดิสก์จะถูกเข้ารหัสอย่างโปร่งใสสำหรับคำสั่ง DAO
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ