Room เป็นไลบรารี ORM จาก Android Jetpack ที่ให้เลเยอร์นามธรรมเหนือ SQLite สำหรับทำงานกับฐานข้อมูลภายในบน Android ตามเอกสารอย่างเป็นทางการที่ Android Developers, 2025 Room จะสร้างการใช้งาน DAO โดยอัตโนมัติตามคำอธิบายประกอบในเวลาคอมไพล์ ซึ่งช่วยลดโค้ดเทมเพลตได้ประมาณ 70% เมื่อเทียบกับการใช้ SQLiteOpenHelper โดยตรง ไลบรารีดำเนินการตรวจสอบความถูกต้องของคำสั่ง SQL ในเวลาคอมไพล์ ซึ่งช่วยให้ค้นหาข้อผิดพลาดทางไวยากรณ์ได้ก่อนที่จะเรียกใช้แอปพลิเคชันบนอุปกรณ์
ประเด็นสำคัญ
Room เป็นไลบรารี ORM จาก Android Jetpack ที่สร้างโดย Google เพื่อทำให้การทำงานกับฐานข้อมูล SQLite ภายในบนแพลตฟอร์ม Android ง่ายขึ้น โดยมีคำอธิบายประกอบสำหรับอธิบายสกีมาข้อมูลและสร้างการใช้งานอินเทอร์เฟซ DAO โดยอัตโนมัติในเวลาคอมไพล์ ต่างจากการใช้ SQLiteOpenHelper โดยตรง Room ช่วยให้นักพัฒนาไม่ต้องเขียนโค้ดเทมเพลตจำนวนมากสำหรับการสร้าง เปิด และจัดการการเชื่อมต่อฐานข้อมูล
ไลบรารีนี้ถูกนำเสนอในงาน Google I/O 2017 ซึ่งเป็นส่วนหนึ่งขององค์ประกอบสถาปัตยกรรม Android ตั้งแต่นั้นมา Room ได้กลายเป็นมาตรฐานโดยพฤตินัยสำหรับการจัดเก็บข้อมูลภายใน แซงหน้าโซลูชันอย่าง GreenDAO และ Realm สำหรับ Android ในด้านความนิยม ตามข้อมูลของ Google ไลบรารีนี้ถูกใช้ในมากกว่า 60% ของแอปพลิเคชันที่เผยแพร่บน Google Play ซึ่งทำงานกับข้อมูลภายในบนอุปกรณ์
คุณสมบัติหลักคือการตรวจสอบความถูกต้องของคำสั่ง SQL ในเวลาคอมไพล์โดยใช้ตัวประมวลผลคำอธิบายประกอบ หากนักพัฒนาทำผิดพลาดในคำสั่ง SQL เช่น ระบุชื่อคอลัมน์ที่ไม่มีอยู่ การคอมไพล์จะล้มเหลวพร้อมข้อผิดพลาดก่อนการติดตั้งแอปพลิเคชัน ซึ่งแตกต่างจากแนวทางของ SQLiteOpenHelper โดยพื้นฐาน ซึ่งข้อผิดพลาดดังกล่าวจะถูกตรวจพบในเวลารันไทม์เท่านั้น ซึ่งมักเกิดขึ้นในระบบจริง
SQLite รองรับข้อมูลเพียงห้าประเภท: TEXT, INTEGER, REAL, BLOB และ NULL อย่างไรก็ตาม Java และ Kotlin ใช้ประเภทที่ซับซ้อน: Date, List, Enum และออบเจ็กต์ที่กำหนดเอง เพื่อจัดเก็บข้อมูลเหล่านี้ Room มีกลไก TypeConverters — เมธอดสแตติกที่แปลงประเภทที่ซับซ้อนเป็นประเภทพื้นฐานที่ SQLite เข้าใจได้ ตัวอย่างเช่น ออบเจ็กต์ Date จะถูกแปลงเป็น Long (การประทับเวลา) และ List<String> จะถูกแปลงเป็นสตริง JSON ผ่าน Gson หรือ Moshi
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
val db = Room
.databaseBuilder(context, AppDatabase::class.java, "app-db")
.build()
ในการประกาศตัวแปลง เพียงเพิ่มคำอธิบายประกอบ @TypeConverter ลงในเมธอดสแตติกและระบุคลาสตัวแปลงในคำอธิบายประกอบ @TypeConverters ที่ระดับฐานข้อมูล Room จะใช้ตัวแปลงโดยอัตโนมัติเมื่ออ่านและเขียนประเภทที่เกี่ยวข้องในแต่ละคำสั่ง SQL โดยไม่ต้องเรียกเมธอดการแปลงด้วยตนเอง
Room ประกอบด้วยสามองค์ประกอบหลัก: Entity, DAO และ Database แต่ละองค์ประกอบมีบทบาทที่กำหนดไว้อย่างชัดเจนและมีคำอธิบายประกอบที่เกี่ยวข้อง เมื่อรวมกันแล้วจะสร้างเลเยอร์การเข้าถึงข้อมูลที่สมบูรณ์ซึ่งแยกตรรกะทางธุรกิจของแอปพลิเคชันออกจากรายละเอียดการใช้งาน SQLite
Entity คือคลาสข้อมูลที่อธิบายโครงสร้างของหนึ่งตารางในฐานข้อมูล แต่ละฟิลด์ของคลาสสอดคล้องกับคอลัมน์ของตาราง และแต่ละแถวในฐานข้อมูลสอดคล้องกับหนึ่งอินสแตนซ์ของคลาส คำอธิบายประกอบ @Entity บอก Room ว่าคลาสนี้เป็นตาราง ฟิลด์ที่มีคำอธิบายประกอบ @PrimaryKey กำหนดคีย์หลัก ซึ่งสามารถเพิ่มอัตโนมัติหรือแบบผสมได้ @ForeignKey ใช้สำหรับความสัมพันธ์ระหว่างตาราง เพื่อรับประกันความสมบูรณ์ของข้อมูลในระดับฐานข้อมูล
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
@ColumnInfo(name = "full_name")
val name: String,
val age: Int,
val email: String
)
DAO (Data Access Object) คืออินเทอร์เฟซหรือคลาสนามธรรมที่ประกาศการดำเนินการสำหรับทำงานกับข้อมูล: การแทรก การอ่าน การอัปเดต และการลบ แต่ละการดำเนินการจะมีคำอธิบายประกอบ @Insert, @Query, @Update หรือ @Delete Room จะสร้างการใช้งานของอินเทอร์เฟซนี้โดยอัตโนมัติในเวลาคอมไพล์ คำอธิบายประกอบ @Query มีค่าอย่างยิ่ง — โดยรับคำสั่ง SQL เป็นสตริงและตรวจสอบความถูกต้องในเวลาคอมไพล์
@Dao
interface UserDao {
@Insert
suspend fun insert(user: User): Long
@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getUserById(userId: Int): User?
@Query("SELECT * FROM users")
fun getAllUsers(): Flow<List<User>>
@Delete
suspend fun delete(user: User)
}
Database คือคลาสนามธรรมที่สืบทอด RoomDatabase ทำหน้าที่เป็นจุดเริ่มต้นของฐานข้อมูล โดยมีรายการ Entities ทั้งหมดและให้เมธอดนามธรรมสำหรับรับ DAO คลาสนี้มีคำอธิบายประกอบ @Database ซึ่งระบุเวอร์ชันสกีมาและรายการเอนทิตี อินสแตนซ์ฐานข้อมูลถูกสร้างขึ้นผ่าน Room.databaseBuilder พร้อมกับบริบทของแอปพลิเคชัน ชื่อไฟล์ และคลาส Database
Room ไม่ได้แทนที่ SQLite แต่ทำงานเหนือมันในฐานะเลเยอร์นามธรรม สถาปัตยกรรมภายในประกอบด้วยตัวประมวลผลคำอธิบายประกอบ ตัวสร้างโค้ด และพูลการเชื่อมต่อ ในเวลาคอมไพล์ ตัวประมวลผลคำอธิบายประกอบจะวิเคราะห์คลาส Entity, DAO และ Database จากนั้นสร้างคลาสการใช้งานที่มีคำต่อท้าย _Impl คลาสที่สร้างขึ้นทั้งหมดจะถูกวางในแพ็คเกจคอมไพล์และไม่สามารถมองเห็นได้โดยตรงโดยนักพัฒนา
การสร้างโค้ดในเวลาคอมไพล์เป็นกลไกหลักของ Room สำหรับแต่ละอินเทอร์เฟซ DAO จะมีการสร้างคลาสที่มีการใช้งานครบถ้วนของเมธอดทั้งหมดที่มีคำอธิบายประกอบ คำสั่ง SQL จากคำอธิบายประกอบ @Query จะถูกตรวจสอบความถูกต้อง: ตัวประมวลผลจะจับคู่ชื่อคอลัมน์กับฟิลด์ Entity และตรวจสอบไวยากรณ์ SQL หากพบข้อผิดพลาด การคอมไพล์จะถูกขัดจังหวะด้วยข้อความที่ชัดเจน ซึ่งเป็นไปไม่ได้เมื่อใช้ SQLiteOpenHelper โดยตรง ซึ่งข้อผิดพลาดจะปรากฏในเวลารันไทม์เท่านั้น
กระบวนการสร้างประกอบด้วยสามขั้นตอน ขั้นแรก — การตรวจสอบสกีมา: ตัวประมวลผลตรวจสอบว่าคลาสทั้งหมดที่ระบุใน @Database เป็น Entities ที่ถูกต้อง ขั้นที่สอง — การสร้างเนื้อหา DAO: สำหรับแต่ละเมธอด จะมีการสร้างการใช้งานโดยใช้ออบเจ็กต์ภายใน RoomSQLiteQuery ที่ดำเนินการคำสั่งที่เตรียมไว้ ขั้นที่สาม — การสร้างคลาส Database_Impl ซึ่งจัดการการสร้างและการเปิดฐานข้อมูล รวมถึงการเริ่มต้นออบเจ็กต์ DAO ทั้งหมด
class UserDao_Impl(private val __db: RoomDatabase) : UserDao {
private val __insertionAdapter = __db
.createInsertionAdapter(User::class, 0)
override suspend fun insert(user: User): Long {
__db.assertNotSuspendingTransaction()
return __db.runInTransaction {
__insertionAdapter.insertAndReturnId(user)
}
}
}
Room ไม่ได้สร้างพูลเธรดแยกต่างหากสำหรับการดำเนินการฐานข้อมูล โดยค่าเริ่มต้น คำสั่งจะถูกดำเนินการในเธรดที่เรียกโดยมีข้อจำกัดหนึ่งข้อ: การอ่านและการเขียนจะบล็อกเธรด สำหรับการทำงานแบบอะซิงโครนัส Room ผสานรวมกับ Coroutines ของ Kotlin ผ่านฟังก์ชัน suspend กับ LiveData ผ่านค่าที่ส่งคืน และกับ Flow ผ่านตัวห่อรีแอกทีฟ ซึ่งช่วยให้นักพัฒนามีความยืดหยุ่นในการเลือกโซลูชันสถาปัตยกรรมสำหรับงานเฉพาะ
มาดูตัวอย่างการปฏิบัติจริงของการสร้างแอปพลิเคชันบันทึกข้อความโดยใช้ Room แอปพลิเคชันประกอบด้วยตาราง Note หนึ่งตารางที่มีฟิลด์ id, title, content และ timestamp ผู้ใช้สามารถเพิ่ม ดู และลบบันทึกข้อความได้ Coroutines ถูกใช้สำหรับการดำเนินการแบบอะซิงโครนัส
ในการผสานรวม Room เข้ากับโปรเจกต์ Android ให้เพิ่มการพึ่งพาในไฟล์ build.gradle ระดับโมดูล Room ต้องการสามองค์ประกอบ: ไลบรารีรันไทม์ ตัวประมวลผลคำอธิบายประกอบ kapt และการสนับสนุน Coroutines แบบเลือกได้ เวอร์ชันไลบรารีถูกระบุในตัวแปร room_version เพื่อความสะดวกในการอัปเดต ตั้งแต่ Room 2.4.0 เป็นต้นไป KSP ได้รับการสนับสนุนเป็นทางเลือกแทน kapt ด้วยความเร็วในการคอมไพล์ที่เร็วกว่า
dependencies {
def room_version = "2.6.1"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
// ตัวเลือก: การทดสอบ
testImplementation "androidx.room:room-testing:$room_version"
}
หลังจากตั้งค่าการพึ่งพาแล้ว ให้สร้างสามไฟล์: Entity Note, อินเทอร์เฟซ NoteDao และคลาส AppDatabase Entity Note มีฟิลด์พร้อมคำอธิบายประกอบ @PrimaryKey และ @ColumnInfo DAO ให้เมธอดสำหรับการแทรก การรับรายการ และการลบ Database เชื่อมโยง Entity และ DAO ผ่านคำอธิบายประกอบ @Database
@Entity(tableName = "notes")
data class Note(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val title: String,
val content: String,
@ColumnInfo(name = "created_at")
val timestamp: Long = System.currentTimeMillis()
)
@Dao
interface NoteDao {
@Insert
suspend fun insert(note: Note)
@Query("SELECT * FROM notes ORDER BY created_at DESC")
fun getAllNotes(): Flow<List<Note>>
@Delete
suspend fun delete(note: Note)
}
ไฟล์ AppDatabase ถูกประกาศเป็นคลาสนามธรรมที่สืบทอด RoomDatabase คำอธิบายประกอบ @Database ระบุ Entities ทั้งหมดสำหรับเวอร์ชันปัจจุบันและหมายเลขเวอร์ชันสกีมา ในการรับอินสแตนซ์ จะใช้รูปแบบซิงเกิลตันผ่านเมธอด build ของ Room.databaseBuilder พร้อมกับบริบทของแอปพลิเคชัน การแคชอินสแตนซ์ฐานข้อมูลป้องกันการสร้างหลายครั้งซึ่งอาจทำให้หน่วยความจำรั่วไหล
การย้ายฐานข้อมูล ใน Room เป็นกลไกสำหรับเปลี่ยนสกีมาฐานข้อมูลเมื่ออัปเดตแอปพลิเคชันโดยไม่สูญเสียข้อมูลที่มีอยู่ เมื่อผู้ใช้ติดตั้งเวอร์ชันใหม่ที่มี Entities ที่ถูกแก้ไข Room จะตรวจพบความไม่ตรงกันของเวอร์ชันและดำเนินการตามขั้นตอนการย้ายฐานข้อมูลที่ระบุ หากไม่มีการย้ายฐานข้อมูล ฐานข้อมูลจะถูกลบและสร้างใหม่ ส่งผลให้สูญเสียข้อมูลทั้งหมดที่ผู้ใช้บันทึกไว้
การย้ายฐานข้อมูลอธิบายโดยคลาส Migration ซึ่งรับเวอร์ชันเริ่มต้นและสิ้นสุดของฐานข้อมูล ภายในเมธอด migrate จะดำเนินการคำสั่ง SQL ALTER TABLE หรือ CREATE TABLE เพื่อเปลี่ยนสกีมา Room ไม่สามารถตรวจจับการเปลี่ยนแปลงสกีมาโดยอัตโนมัติ — นักพัฒนาต้องเขียนการย้ายฐานข้อมูลด้วยตนเองสำหรับการเปลี่ยนแปลง Entity แต่ละครั้ง ตั้งแต่ Room 2.4.0 เป็นต้นไป ฟังก์ชันทดลอง autoMigrations พร้อมใช้งานสำหรับการสร้างการย้ายฐานข้อมูลอัตโนมัติ
ฟังก์ชัน autoMigrations ช่วยให้ Room สร้างการย้ายฐานข้อมูลโดยอัตโนมัติตามความแตกต่างระหว่างเวอร์ชัน Entity ในการใช้งาน เพียงเพิ่มคำอธิบายประกอบ @AutoMigration ไปยัง @Database และเปิดใช้งานการส่งออกสกีมาเป็น JSON Room จะเปรียบเทียบสกีมาของเวอร์ชันที่อยู่ติดกันและสร้างคำสั่ง ALTER ที่จำเป็น อย่างไรก็ตาม autoMigrations รองรับเฉพาะการเปลี่ยนแปลงที่เข้ากันได้ย้อนหลังเท่านั้น: การเพิ่มคอลัมน์ การสร้างดัชนี และการเปลี่ยนประเภทด้วยการแปลงที่เข้ากันได้
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL(
"ALTER TABLE users ADD COLUMN phone TEXT"
)
}
}
val db = Room
.databaseBuilder(context, AppDatabase::class.java, "app-db")
.addMigrations(MIGRATION_1_2)
.build()
สำหรับการเปลี่ยนแปลงที่ซับซ้อน เช่น การเปลี่ยนชื่อคอลัมน์หรือการรวมตาราง จำเป็นต้องมีการย้ายฐานข้อมูลด้วยตนเองโดยใช้ตารางกลาง สถานการณ์ทั่วไป: สร้างตารางชั่วคราวด้วยสกีมาเก่า คัดลอกข้อมูลจากตารางเก่าไปยังตารางใหม่พร้อมการแปลง ลบตารางเก่าและเปลี่ยนชื่อตารางชั่วคราว Room รับประกันว่าการย้ายฐานข้อมูลทั้งหมดจะถูกดำเนินการในธุรกรรมเดียว และหากเกิดข้อผิดพลาด การเปลี่ยนแปลงจะถูกย้อนกลับทั้งหมด
คำถามที่พบบ่อย
Room ให้นามธรรม ORM พร้อมคำอธิบายประกอบและการตรวจสอบ SQL ในเวลาคอมไพล์ ในขณะที่ SQLiteOpenHelper ต้องการการเขียนคำสั่งทั้งหมดด้วยตนเองและการจัดการการเชื่อมต่อ Room สร้างโค้ดสำหรับการดำเนินการ CRUD โดยอัตโนมัติและผสานรวมกับองค์ประกอบสถาปัตยกรรม Android รวมถึง LiveData และ Flow
Room รองรับประเภทพื้นฐานทั้งหมดของ Java: Int, Long, Boolean, Float, Double รวมถึง String, ByteArray และ Date สำหรับประเภทที่ซับซ้อนเช่น List หรือ Enum จะใช้ TypeConverters — เมธอดการแปลงแบบสแตติกที่แปลงประเภทที่ไม่ได้มาตรฐานเป็นรูปแบบที่เข้ากันได้กับ SQLite
ได้ Room รองรับการเรียกแบบซิงโครนัสโดยไม่มี Coroutines แต่จะบล็อกเธรดที่ดำเนินการอยู่ สำหรับการทำงานแบบอะซิงโครนัส คุณสามารถใช้ LiveData หรือ RxJava แทน Coroutines Google แนะนำให้ใช้ Coroutines เป็นวิธีหลักสำหรับการเข้าถึงข้อมูลแบบอะซิงโครนัสในโปรเจกต์ใหม่
หาก Room ตรวจพบความไม่ตรงกันของเวอร์ชันฐานข้อมูลและไม่พบการย้ายฐานข้อมูลที่เหมาะสม โดยค่าเริ่มต้นจะเกิด IllegalStateException พร้อมคำอธิบายข้อผิดพลาด นักพัฒนาสามารถแทนที่พฤติกรรมนี้ด้วยเมธอด fallbackToDestructiveMigration ซึ่งจะลบฐานข้อมูลที่มีอยู่และสร้างใหม่ ทำให้สูญเสียข้อมูลทั้งหมด
Room รองรับความสัมพันธ์ผ่านออบเจ็กต์ที่ซ้อนกันด้วยคำอธิบายประกอบ @Embedded และผ่านคลาสความสัมพันธ์ด้วยคำอธิบายประกอบ @Relation สำหรับคำสั่งที่ซับซ้อนที่เกี่ยวข้องกับการ JOIN ตาราง จะใช้คลาส POJO ที่กำหนดเอง ซึ่งฟิลด์ของคลาสจะถูกเติมจากผลลัพธ์ของ @Query ด้วยคำสั่ง SQL JOIN
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม