रूम के डीएओ इस्तेमाल करके, डेटा को ऐक्सेस करना

ऐप्लिकेशन का डेटा सेव करने के लिए, Room परसिस्टेंट लाइब्रेरी का इस्तेमाल किया जाता है. सेव किए गए डेटा के साथ इंटरैक्ट करने के लिए, डेटा ऐक्सेस ऑब्जेक्ट या डीएओ तय किए जाते हैं. हर डीएओ में ऐसे फ़ंक्शन शामिल होते हैं जो आपके ऐप्लिकेशन के डेटाबेस को ऐब्स्ट्रैक्ट ऐक्सेस देते हैं. कंपाइल के समय, Room आपके तय किए गए डीएओ के लिए अपने-आप लागू होने वाले कोड जनरेट करता है.

क्वेरी बिल्डर या सीधे तौर पर क्वेरी करने के बजाय, अपने ऐप्लिकेशन के डेटाबेस को ऐक्सेस करने के लिए डीएओ का इस्तेमाल करके, जिम्मेदारियों को अलग-अलग रखा जा सकता है. यह आर्किटेक्चर का एक अहम सिद्धांत है. डीएओ, ऐप्लिकेशन की जांच करते समय डेटाबेस का मॉक ऐक्सेस करने की सुविधा भी देते हैं.

डीएओ की बनावट

हर डीएओ को इंटरफ़ेस या ऐब्सट्रैक्ट क्लास के तौर पर तय किया जा सकता है. सामान्य इस्तेमाल के लिए, इंटरफ़ेस का इस्तेमाल किया जाता है. दोनों ही मामलों में, आपको अपने DAO को हमेशा @Dao से एनोटेट करना होगा. DAO में प्रॉपर्टी नहीं होती हैं. हालांकि, ये आपके ऐप्लिकेशन के डेटाबेस में मौजूद डेटा के साथ इंटरैक्ट करने के लिए, एक या उससे ज़्यादा फ़ंक्शन तय करते हैं.

यहां दिए गए कोड में, डीएओ का एक उदाहरण दिखाया गया है. इसमें Room डेटाबेस में User ऑब्जेक्ट डालने, मिटाने, और चुनने के लिए फ़ंक्शन तय किए गए हैं:

@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>
}

डेटाबेस इंटरैक्शन के बारे में बताने वाले डीएओ फ़ंक्शन दो तरह के होते हैं:

  • ये ऐसे फ़ंक्शन हैं जिनकी मदद से, एसक्यूएल कोड लिखे बिना ही डेटाबेस में लाइनें जोड़ी, अपडेट की, और मिटाई जा सकती हैं.
  • क्वेरी फ़ंक्शन, जिनकी मदद से डेटाबेस के साथ इंटरैक्ट करने के लिए, अपनी एसक्यूएल क्वेरी लिखी जा सकती है.

यहां दिए गए सेक्शन में, दोनों तरह के डीएओ फ़ंक्शन इस्तेमाल करने का तरीका बताया गया है. इससे यह तय किया जा सकता है कि आपके ऐप्लिकेशन को डेटाबेस के साथ किस तरह के इंटरैक्शन की ज़रूरत है.

सुविधा देने वाले फ़ंक्शन

Room, ऐसे फ़ंक्शन तय करने के लिए सुविधाजनक एनोटेशन उपलब्ध कराता है जो SQL स्टेटमेंट लिखे बिना, डेटाबेस में डेटा डालने, अपडेट करने, और मिटाने का काम करते हैं.

अगर आपको डेटा में ज़्यादा जटिल बदलाव करने हैं या डेटा मिटाना है या डेटाबेस में मौजूद डेटा के बारे में क्वेरी करनी है, तो क्वेरी फ़ंक्शन का इस्तेमाल करें.

शामिल करें

@Insert एनोटेशन की मदद से, ऐसे फ़ंक्शन तय किए जा सकते हैं जो अपने पैरामीटर को डेटाबेस की सही टेबल में डालते हैं. नीचे दिए गए कोड में, मान्य @Insert फ़ंक्शन के उदाहरण दिखाए गए हैं. ये फ़ंक्शन, डेटाबेस में एक या उससे ज़्यादा User ऑब्जेक्ट डालते हैं:

@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 फ़ंक्शन के हर पैरामीटर के लिए, यह ज़रूरी है कि वह @Entity एनोटेशन वाली Room डेटा इकाई क्लास का इंस्टेंस हो या डेटा इकाई क्लास के इंस्टेंस का कलेक्शन हो. @Insert फ़ंक्शन को कॉल करने पर, Room पास किए गए हर इकाई इंस्टेंस को उससे जुड़ी डेटाबेस टेबल में डालता है.

अगर @Insert फ़ंक्शन को एक पैरामीटर मिलता है, तो यह Long वैल्यू दिखा सकता है. यह वैल्यू, जोड़े गए आइटम के लिए नया rowId होता है. अगर पैरामीटर कोई कलेक्शन या कलेक्शन है, तो उसे Long वैल्यू का कलेक्शन या कलेक्शन दिखाना चाहिए. इसमें हर वैल्यू, डाले गए किसी एक आइटम के लिए rowId के तौर पर काम करती है. rowId वैल्यू के बारे में ज़्यादा जानने के लिए, @Insert एनोटेशन का रेफ़रंस दस्तावेज़ और rowid टेबल के लिए SQLite दस्तावेज़ देखें.

अपडेट करें

@Update एनोटेशन की मदद से, ऐसे फ़ंक्शन तय किए जा सकते हैं जो डेटाबेस टेबल की कुछ खास लाइनों को अपडेट करते हैं. @Insert फ़ंक्शन की तरह, @Update फ़ंक्शन भी डेटा इकाई के इंस्टेंस को पैरामीटर के तौर पर स्वीकार करते हैं. नीचे दिए गए कोड में, @Update फ़ंक्शन का एक उदाहरण दिखाया गया है. यह फ़ंक्शन, डेटाबेस में मौजूद एक या उससे ज़्यादा User ऑब्जेक्ट को अपडेट करने की कोशिश करता है:

@Dao
interface UserDao {
    @Update
    suspend fun updateUsers(vararg users: User)
}

रूम, प्राइमरी कुंजी का इस्तेमाल करके, आर्ग्युमेंट में मौजूद इकाई के इंस्टेंस को डेटाबेस की लाइनों से मैच करता है. अगर एक जैसी प्राइमरी कुंजी वाली कोई पंक्ति नहीं है, तो Room कोई बदलाव नहीं करता.

@Update फ़ंक्शन, Int वैल्यू को वैकल्पिक तौर पर दिखा सकता है. इससे अपडेट की गई लाइनों की संख्या का पता चलता है.

मिटाएं

@Delete एनोटेशन की मदद से, ऐसे फ़ंक्शन तय किए जा सकते हैं जो डेटाबेस टेबल से कुछ खास लाइनें मिटाते हैं. @Insert फ़ंक्शन की तरह, @Delete फ़ंक्शन भी डेटा इकाई के इंस्टेंस को पैरामीटर के तौर पर स्वीकार करते हैं. नीचे दिए गए कोड में, @Delete फ़ंक्शन का एक उदाहरण दिखाया गया है. यह फ़ंक्शन, डेटाबेस से एक या उससे ज़्यादा User ऑब्जेक्ट मिटाने की कोशिश करता है:

@Dao
interface UserDao {
    @Delete
    suspend fun deleteUsers(vararg users: User)
}

रूम, प्राइमरी कुंजी का इस्तेमाल करके, आर्ग्युमेंट में मौजूद इकाई के इंस्टेंस को डेटाबेस की लाइनों से मैच करता है. अगर एक जैसी प्राइमरी कुंजी वाली कोई पंक्ति नहीं है, तो Room कोई बदलाव नहीं करता.

@Delete फ़ंक्शन, Int वैल्यू को वैकल्पिक तौर पर दिखा सकता है. यह वैल्यू, उन लाइनों की संख्या दिखाती है जिन्हें मिटा दिया गया है.

अपसर्ट

@Upsert एनोटेशन की मदद से, ऐसे फ़ंक्शन तय किए जा सकते हैं जो मैच करने वाली कोई लाइन न होने पर, इकाई के इंस्टेंस डालते हैं. इसके अलावा, अगर एक ही प्राइमरी कुंजी वाली कोई लाइन पहले से मौजूद है, तो उसे अपडेट भी किया जा सकता है.

@Insert और @Update फ़ंक्शन की तरह, @Upsert फ़ंक्शन भी डेटा इकाई के इंस्टेंस को पैरामीटर के तौर पर स्वीकार करते हैं. नीचे दिए गए कोड में, @Upsert फ़ंक्शन का एक उदाहरण दिखाया गया है. यह फ़ंक्शन, डेटाबेस में एक या उससे ज़्यादा User ऑब्जेक्ट को अपसर्ट करने की कोशिश करता है:

@Dao
interface UserDao {
    @Upsert
    suspend fun upsertUsers(vararg users: User)
}

अगर @Upsert फ़ंक्शन को एक पैरामीटर मिलता है, तो यह Long वैल्यू दिखा सकता है. अगर इससे कोई नई लाइन जुड़ती है, तो यह नई लाइन का rowId दिखाता है. अगर इससे किसी मौजूदा लाइन को अपडेट किया जाता है, तो यह -1 दिखाता है. अगर पैरामीटर एक कलेक्शन या कैटगरी है, तो इसे Long वैल्यू का कलेक्शन या कैटगरी दिखाना चाहिए.

क्वेरी फ़ंक्शन

@Query एनोटेशन की मदद से, एसक्यूएल स्टेटमेंट लिखे जा सकते हैं और उन्हें DAO फ़ंक्शन के तौर पर दिखाया जा सकता है. अपने ऐप्लिकेशन के डेटाबेस से डेटा के बारे में क्वेरी करने के लिए, इन क्वेरी फ़ंक्शन का इस्तेमाल करें. इसके अलावा, इनका इस्तेमाल तब भी किया जा सकता है, जब आपको डेटाबेस में ज़्यादा जटिल तरीके से डेटा डालना, अपडेट करना, और मिटाना हो.

Room, कंपाइल के समय पर SQL क्वेरी की पुष्टि करता है. इसका मतलब है कि अगर आपकी क्वेरी में कोई समस्या है, तो रनटाइम में गड़बड़ी होने के बजाय कंपाइलेशन की गड़बड़ी होती है.

सिंपल क्वेरी

नीचे दिए गए कोड में, एक ऐसे फ़ंक्शन को तय किया गया है जो डेटाबेस में मौजूद सभी SELECT ऑब्जेक्ट को वापस लाने के लिए, SELECT क्वेरी का इस्तेमाल करता है:User

@Query("SELECT * FROM user")
suspend fun loadAllUsers(): List<User>

यहां दिए गए सेक्शन में, सामान्य इस्तेमाल के उदाहरणों के लिए इस उदाहरण में बदलाव करने का तरीका बताया गया है.

टेबल के कॉलम का सबसेट दिखाना

ज़्यादातर मामलों में, आपको क्वेरी की जा रही टेबल से सिर्फ़ कुछ कॉलम वापस लाने होते हैं. उदाहरण के लिए, ऐसा हो सकता है कि आपके यूज़र इंटरफ़ेस (यूआई) पर किसी उपयोगकर्ता की पूरी जानकारी के बजाय, सिर्फ़ उसका पहला और आखिरी नाम दिखे. संसाधन सेव करने और क्वेरी को आसानी से लागू करने के लिए, सिर्फ़ उन प्रॉपर्टी के लिए क्वेरी करें जिनकी आपको ज़रूरत है.

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 को भी वापस लाने की सुविधा देता है. इन टाइप का इस्तेमाल करते समय, कॉलम को क्वेरी स्टेटमेंट में तय किए गए क्रम के हिसाब से मैप किया जाता है. इसलिए, 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>

क्वेरी में पैरामीटर का कलेक्शन पास करना

ऐसा हो सकता है कि आपके कुछ डीएओ फ़ंक्शन के लिए, आपको अलग-अलग पैरामीटर पास करने पड़ें. इनकी जानकारी रनटाइम तक नहीं मिलती. अगर कोई पैरामीटर किसी कलेक्शन को दिखाता है, तो रनटाइम के दौरान वह अपने-आप बड़ा हो जाता है. ऐसा वैल्यू की संख्या के आधार पर होता है.

उदाहरण के लिए, यहां दिए गए कोड में एक फ़ंक्शन के बारे में बताया गया है. यह फ़ंक्शन, कुछ देशों/इलाकों के सभी उपयोगकर्ताओं के बारे में जानकारी देता है:

@Query("SELECT * FROM user WHERE region IN (:regions)")
suspend fun loadUsersFromRegions(regions: List<String>): List<User>

एक से ज़्यादा टेबल पर क्वेरी करना

आपकी कुछ क्वेरी के नतीजे का हिसाब लगाने के लिए, एक से ज़्यादा टेबल का ऐक्सेस ज़रूरी हो सकता है. एक से ज़्यादा टेबल का रेफ़रंस देने के लिए, अपनी SQL क्वेरी में JOIN क्लॉज़ का इस्तेमाल किया जा सकता है.

यहां दिए गए कोड में, एक ऐसा फ़ंक्शन तय किया गया है जो तीन टेबल को एक साथ जोड़ता है. इससे, उन किताबों की जानकारी मिलती है जो फ़िलहाल किसी उपयोगकर्ता को उधार दी गई हैं:

@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)

मल्टीमैप वापस पाना

जॉइन करने की कार्रवाइयों के लिए, एक से ज़्यादा टेबल से कॉलम के बारे में क्वेरी भी की जा सकती है. इसके लिए, आपको कोई अतिरिक्त डेटा क्लास तय करने की ज़रूरत नहीं है. इसके लिए, ऐसे क्वेरी फ़ंक्शन लिखें जो मल्टीमैप लौटाते हैं.

कई टेबल के लिए क्वेरी करना में दिया गया उदाहरण देखें. User और Book इंस्टेंस की पेयरिंग रखने वाली कस्टम डेटा क्लास के इंस्टेंस की सूची दिखाने के बजाय, क्वेरी फ़ंक्शन से सीधे User और Book की मैपिंग दिखाई जा सकती है:

@Query(
    """
    SELECT * FROM user
    JOIN book ON user.id = book.user_id
    """
)
suspend fun loadUserAndBookNames(): Map<User, List<Book>>

जब क्वेरी फ़ंक्शन, मल्टीमैप दिखाता है, तब GROUP BY क्लॉज़ का इस्तेमाल करने वाली क्वेरी लिखी जा सकती हैं. इससे आपको बेहतर कैलकुलेशन और फ़िल्टर करने के लिए, SQL की सुविधाओं का फ़ायदा मिलता है. उदाहरण के लिए, loadUserAndBookNames फ़ंक्शन में बदलाव करके, सिर्फ़ उन उपयोगकर्ताओं की जानकारी दिखाई जा सकती है जिन्होंने तीन या इससे ज़्यादा किताबें चेक आउट की हैं:

@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, अन्य एपीआई लाइब्रेरी के साथ इंटिग्रेट करने के लिए, कुछ खास तरह के रिटर्न टाइप उपलब्ध कराता है.

पेजिंग लाइब्रेरी की मदद से पेज नंबर वाली क्वेरी

Room, Paging library के साथ इंटिग्रेट करके, पेज के हिसाब से क्वेरी करने की सुविधा देता है. Paging 3 के रिटर्न टाइप इस्तेमाल करने के लिए, आपको अपने डेटाबेस या डीएओ में Paging रिटर्न टाइप कन्वर्टर रजिस्टर करने होंगे:

  1. अपने बिल्ड कॉन्फ़िगरेशन में androidx.room3:room3-paging आर्टफ़ैक्ट शामिल करें.
  2. @DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class) का इस्तेमाल करके, @Database या @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 के लिए टाइप पैरामीटर चुनने के बारे में ज़्यादा जानने के लिए, कुंजी और वैल्यू के टाइप चुनना लेख पढ़ें.

डेटाबेस से सीधे कनेक्ट करने का ऐक्सेस

अगर आपके ऐप्लिकेशन के लॉजिक के लिए, डेटाबेस कनेक्शन को सीधे तौर पर कम लेवल पर ऐक्सेस करना ज़रूरी है, तो Room के कनेक्शन एपीआई का इस्तेमाल किया जा सकता है. RoomDatabase इंस्टेंस पर सिर्फ़ पढ़ने के लिए useReaderConnection का इस्तेमाल करके या लिखने के लिए useWriterConnection का इस्तेमाल करके कनेक्शन बनाया जा सकता है. साथ ही, स्टेटमेंट को लागू करने के लिए 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))
                }
            }
        }
    }

अगर आपको कनेक्शन पर सीधे तौर पर डेटाबेस के लेन-देन करने हैं, तो useWriterConnection ब्लॉक में मौजूद Transactor इंस्टेंस पर, immediateTransaction, deferredTransaction या exclusiveTransaction हेल्पर फ़ंक्शन का इस्तेमाल किया जा सकता है:

roomDatabase.useWriterConnection { transactor ->
    transactor.immediateTransaction {
        // Perform transactional database operations using transactor
    }
}

इसके अलावा, अगर आपको किसी लेन-देन में सिर्फ़ टॉप-लेवल के DAO ऑपरेशन करने हैं, तो अपने RoomDatabase इंस्टेंस पर withReadTransaction या withWriteTransaction हेल्पर एक्सटेंशन फ़ंक्शन का इस्तेमाल करें:

// 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)
}