Room DAO'larını kullanarak verilere erişme

Uygulamanızın verilerini depolamak için Room kalıcılık kitaplığını kullandığınızda veri erişim nesneleri (DAO'lar) tanımlayarak depolanan verilerle etkileşimde bulunursunuz. Her DAO, uygulamanızın veritabanına soyut erişim sunan işlevler içerir. Derleme sırasında Room, tanımladığınız DAO'ların uygulamalarını otomatik olarak oluşturur.

Uygulamanızın veritabanına erişmek için sorgu oluşturucular veya doğrudan sorgular yerine DAO'ları kullanarak önemli bir mimari ilke olan ilgi alanlarının ayrılmasını sağlayabilirsiniz. DAO'lar, uygulamanızı test ederken veritabanı erişimini taklit etmenize de olanak tanır.

DAO'nun anatomisi

Her DAO'yu arayüz veya soyut sınıf olarak tanımlayabilirsiniz. Temel kullanım alanlarında genellikle bir arayüz kullanırsınız. Her iki durumda da DAO'larınızı her zaman @Dao ile açıklama eklemeniz gerekir. DAO'ların özellikleri yoktur ancak uygulamanızın veritabanındaki verilerle etkileşim kurmak için bir veya daha fazla işlev tanımlar.

Aşağıdaki kod, Room veritabanında User nesnelerini ekleme, silme ve seçme işlevlerini tanımlayan bir DAO örneğidir:

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

Veritabanı etkileşimlerini tanımlayan iki tür DAO işlevi vardır:

  • SQL kodu yazmadan veritabanınıza satır eklemenize, satırları güncellemenize ve silmenize olanak tanıyan kolaylık işlevleri.
  • Veritabanıyla etkileşim kurmak için kendi SQL sorgunuzu yazmanıza olanak tanıyan sorgu işlevleri.

Aşağıdaki bölümlerde, uygulamanızın ihtiyaç duyduğu veritabanı etkileşimlerini tanımlamak için her iki tür DAO işlevinin nasıl kullanılacağı gösterilmektedir.

Kolaylık işlevleri

Room, SQL ifadesi yazmanızı gerektirmeden ekleme, güncelleme ve silme işlemleri gerçekleştiren işlevleri tanımlamak için kolaylık sağlayan ek açıklamalar sunar.

Daha karmaşık eklemeler, güncellemeler veya silmeler tanımlamanız ya da veritabanındaki verileri sorgulamanız gerekiyorsa bunun yerine sorgu işlevi kullanın.

Ekle

@Insert ek açıklaması, parametrelerini veritabanındaki uygun tabloya ekleyen işlevler tanımlamanıza olanak tanır. Aşağıdaki kodda, veritabanına bir veya daha fazla User nesnesi ekleyen geçerli @Insert işlevlerine ilişkin örnekler gösterilmektedir:

@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 işlevinin her parametresi, @Entity ile ek açıklama eklenmiş bir Room veri varlığı sınıfı örneği veya veri varlığı sınıfı örnekleri koleksiyonu olmalıdır. Bir @Insert işlevi çağrıldığında Room, iletilen her varlık örneğini ilgili veritabanı tablosuna ekler.

@Insert işlevi tek bir parametre alırsa eklenen öğenin yeni rowId olan bir Long değeri döndürebilir. Parametre bir dizi veya koleksiyon ise bunun yerine Long değerlerinden oluşan bir dizi ya da koleksiyon döndürmelidir. Her değer, eklenen öğelerden birinin rowId değeridir. rowId değerlerini döndürme hakkında daha fazla bilgi edinmek için @Insert ek açıklamasıyla ilgili referans belgelerine ve SQLite belgelerindeki rowid tablolarına bakın.

Güncelleme

@Update ek açıklaması, bir veritabanı tablosundaki belirli satırları güncelleyen işlevler tanımlamanıza olanak tanır. @Insert işlevleri gibi @Update işlevleri de parametre olarak veri varlığı örneklerini kabul eder. Aşağıdaki kodda, veritabanındaki bir veya daha fazla User nesnesini güncellemeye çalışan bir @Update işlevi örneği gösterilmektedir:

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

Room, bağımsız değişkenlerdeki öğe örneklerini veritabanındaki satırlarla eşleştirmek için birincil anahtarı kullanır. Aynı birincil anahtara sahip bir satır yoksa Room herhangi bir değişiklik yapmaz.

Bir @Update işlevi, isteğe bağlı olarak başarıyla güncellenen satır sayısını belirten bir Int değeri döndürebilir.

Sil

@Delete notu, bir veritabanı tablosundan belirli satırları silen işlevler tanımlamanıza olanak tanır. @Insert işlevleri gibi, @Delete işlevleri de parametre olarak veri varlığı örneklerini kabul eder. Aşağıdaki kodda, veritabanından bir veya daha fazla User nesnesini silmeye çalışan bir @Delete işlevi örneği gösterilmektedir:

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

Room, bağımsız değişkenlerdeki öğe örneklerini veritabanındaki satırlarla eşleştirmek için birincil anahtarı kullanır. Aynı birincil anahtara sahip bir satır yoksa Room herhangi bir değişiklik yapmaz.

Bir @Delete işlevi isteğe bağlı olarak başarıyla silinen satır sayısını belirten bir Int değeri döndürebilir.

Upsert

@Upsert ek açıklaması, eşleşen satır olmadığında varlık örnekleri ekleyen işlevler tanımlamanıza veya aynı birincil anahtara sahip bir satır zaten varsa bunları güncellemenize olanak tanır.

@Insert ve @Update işlevleri gibi, @Upsert işlevleri de parametre olarak veri varlığı örneklerini kabul eder. Aşağıdaki kodda, veritabanına bir veya daha fazla User nesnesi eklemeye veya eklenmiş olanları güncellemeye çalışan bir @Upsert işlevi örneği gösterilmektedir:

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

@Upsert işlevi tek bir parametre alırsa Long değeri döndürebilir. Yeni bir satırın eklenmesiyle sonuçlanırsa yeni eklenen satırın rowId değerini döndürür. Mevcut bir satırın güncellenmesiyle sonuçlanırsa -1 değerini döndürür. Parametre bir dizi veya koleksiyon ise bunun yerine Long değerlerinden oluşan bir dizi ya da koleksiyon döndürmelidir.

Sorgu işlevleri

@Query ek açıklaması, SQL ifadeleri yazmanıza ve bunları DAO işlevleri olarak kullanmanıza olanak tanır. Uygulamanızın veritabanındaki verileri sorgulamak veya daha karmaşık ekleme, güncelleme ve silme işlemleri yapmanız gerektiğinde bu sorgu işlevlerini kullanın.

Room, SQL sorgularını derleme zamanında doğrular. Bu, sorgunuzda bir sorun varsa çalışma zamanı hatası yerine derleme hatası oluşacağı anlamına gelir.

Basit sorgular

Aşağıdaki kod, veritabanındaki tüm User nesnelerini döndürmek için SELECT sorgusunu kullanan bir işlevi tanımlar:

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

Aşağıdaki bölümlerde, bu örneğin tipik kullanım alanlarına göre nasıl değiştirileceği gösterilmektedir.

Tablonun sütunlarının bir alt kümesini döndürme

Çoğu zaman, sorguladığınız tablodaki sütunların yalnızca bir alt kümesini döndürmeniz gerekir. Örneğin, kullanıcı arayüzünüzde kullanıcıyla ilgili her ayrıntı yerine yalnızca kullanıcının adı ve soyadı gösterilebilir. Kaynakları kaydetmek ve sorgunuzun yürütülmesini kolaylaştırmak için yalnızca ihtiyacınız olan özelliklere sorgu gönderin.

Room, sonuç sütunları kümesini döndürülen nesneye eşleyebildiğiniz sürece sorgularınızdan bir veri nesnesi döndürmenize olanak tanır. Örneğin, kullanıcının adını ve soyadını tutmak için aşağıdaki nesneyi tanımlayabilirsiniz:

data class NameTuple(
    @ColumnInfo(name = "first_name") val firstName: String,
    @ColumnInfo(name = "last_name") val lastName: String
)

Ardından, sorgu işlevinizden bu veri nesnesini döndürebilirsiniz:

@Query("SELECT first_name, last_name FROM user")
suspend fun loadFullName(): List<NameTuple>

Sorgu, first_name ve last_name sütunları için değerler döndürdüğünden, Room bu değerleri NameTuple sınıfındaki özelliklerle eşler. Sorgu, döndürülen nesnedeki bir özellikle eşlenmeyen bir sütun döndürürse Room uyarı gösterir.

Önceki örnekte sütunların bir alt kümesini almak için özel bir veri sınıfı kullanılsa da sorgu tam olarak iki veya üç sütun döndürdüğünde kolaylık sağlamak için Room, kotlin.Pair ve kotlin.Triple döndürmeyi de destekler. Bu türler kullanılırken sütunlar, sorgu ifadesinde tanımlandıkları sıraya göre eşlenir. Bu nedenle, SELECT ifadesindeki sütunların sırası, Pair veya Triple içindeki türlerin sırasıyla eşleşmelidir.

Sorguya basit parametreler iletme

Çoğu zaman, DAO işlevlerinizin filtreleme işlemleri gerçekleştirebilmesi için parametreleri kabul etmesi gerekir. Room, sorgularınızda işlev parametrelerini bağlama parametreleri olarak kullanmayı destekler.

Örneğin, aşağıdaki kod, belirli bir yaşın üzerindeki tüm kullanıcıları döndüren bir işlevi tanımlar:

@Query("SELECT * FROM user WHERE age > :minAge")
suspend fun loadAllUsersOlderThan(minAge: Int): Array<User>

Ayrıca, aşağıdaki kodda gösterildiği gibi bir sorguda birden fazla parametre iletebilir veya aynı parametreye birden çok kez başvurabilirsiniz:

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

Sorguya parametre koleksiyonu iletme

Bazı DAO işlevleriniz, çalışma zamanına kadar bilinmeyen değişken sayıda parametre iletmenizi gerektirebilir. Bir parametre koleksiyonu temsil ediyorsa çalışma zamanında değer sayısına göre otomatik olarak genişletilir.

Örneğin, aşağıdaki kod, bir bölge alt kümesindeki tüm kullanıcılarla ilgili bilgileri döndüren bir işlev tanımlar:

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

Birden çok tabloyu sorgulama

Sorgularınızdan bazılarında sonucu hesaplamak için birden fazla tabloya erişilmesi gerekebilir. Birden fazla tabloya referans vermek için SQL sorgularınızda JOIN ifadelerini kullanabilirsiniz.

Aşağıdaki kod, şu anda belirli bir kullanıcıya ödünç verilmiş olan kitapları döndürmek için üç tabloyu birleştiren bir işlev tanımlar:

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

Ayrıca, birleştirilmiş birden fazla tablodan sütunların bir alt kümesini döndürmek için veri nesneleri de tanımlayabilirsiniz. Daha fazla bilgi için Tablonun sütunlarının bir alt kümesini döndürme başlıklı makaleyi inceleyin. Aşağıdaki kod, kullanıcıların adlarını ve ödünç aldıkları kitapların adlarını döndüren bir işlevi olan bir DAO tanımlar:

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)

Çoklu harita döndürme

Birleştirme işlemleri için, çoklu harita döndüren sorgu işlevleri yazarak ek bir veri sınıfı tanımlamadan birden fazla tablodaki sütunları da sorgulayabilirsiniz.

Birden çok tabloyu sorgulama bölümündeki örneği inceleyin. User ve Book örneklerinin eşleşmelerini içeren özel bir veri sınıfının örneklerinin listesini döndürmek yerine, User ve Book eşlemesini doğrudan sorgu işlevinizden döndürebilirsiniz:

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

Sorgu işleviniz çoklu harita döndürdüğünde GROUP BY ifadeleri kullanan sorgular yazabilirsiniz. Bu sayede, SQL'in gelişmiş hesaplama ve filtreleme özelliklerinden yararlanabilirsiniz. Örneğin, loadUserAndBookNames işlevinizi yalnızca üç veya daha fazla kitap ödünç almış kullanıcıları döndürecek şekilde değiştirebilirsiniz:

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

Nesnelerin tamamını eşlemeniz gerekmiyorsa sorgunuzdaki belirli sütunlar arasındaki eşlemeleri döndürmek için dönüş türünün genel parametrelerinde @MapColumn ek açıklamasını da kullanabilirsiniz.

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

Özel iade türleri

Room, diğer API kitaplıklarıyla entegrasyon için bazı özel dönüş türleri sağlar.

Sayfalandırma kitaplığıyla sayfalandırılmış sorgular

Room, Paging kitaplığı ile entegrasyon sayesinde sayfalandırılmış sorguları destekler. Paging 3 dönüş türlerini kullanmak için Paging dönüş türü dönüştürücülerini veritabanınıza veya DAO'nuza kaydetmeniz gerekir:

  1. Derleme yapılandırmanıza androidx.room3:room3-paging yapıtını ekleyin.
  2. @Database veya @Dao beyanınıza @DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class) ile açıklama ekleyin.

Kaydettikten sonra DAO'larınız, PagingSource nesnelerini Paging 3 ile kullanılmak üzere döndürebilir:

@Dao
@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class)
interface UserDao {
    @Query("SELECT * FROM users WHERE label LIKE :query")
    fun pagingSource(query: String): PagingSource<Int, User>
}

PagingSource için tür parametreleri seçme hakkında daha fazla bilgi edinmek için Anahtar ve değer türlerini seçme başlıklı makaleyi inceleyin.

Doğrudan veritabanı bağlantısı erişimi

Uygulamanızın mantığı, veritabanı bağlantısına doğrudan ve düşük düzeyde erişim gerektiriyorsa bunun yerine Room'un bağlantı API'lerini kullanabilirsiniz. Salt okuma işlemleri için useReaderConnection, RoomDatabase örneğinizde yazma işlemleri için useWriterConnection kullanarak bağlantı elde edebilir ve ifadeleri yürütmek için usePrepared kullanabilirsiniz:

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

Doğrudan bağlantı üzerinde düşük seviyeli veritabanı işlemleri yapmanız gerekiyorsa useWriterConnection bloğunun içindeki Transactor örneğinde immediateTransaction, deferredTransaction veya exclusiveTransaction yardımcı işlevlerini kullanabilirsiniz:

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

Alternatif olarak, bir işlemde yalnızca üst düzey DAO işlemleri yapmanız gerekiyorsa RoomDatabase örneğinizde withReadTransaction veya withWriteTransaction yardımcı uzantı işlevlerini kullanın:

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