簡體   English   中英

使用 Kotlin 在 Android 房間數據庫中插入對象列表

[英]Inserting a List of Objects in Android Room Database with Kotlin

Android 的初學者正在努力解決房間數據庫的這一限制。 我正在處理兩張桌子,服裝和服裝。 用戶可以通過插入呈現給他們的值來創建 Outfit。 然后在一個單獨的頁面上,用戶可以插入一個 Outfit,其中包含他們之前在 Clothing.kt 中創建的服裝。 為了應用程序,關系將只是一對多的,這意味着我只需要使用許多 Clothing Items 創建一個 Outfit。 到目前為止,這是我的代碼:

服裝.kt

@Parcelize
@Entity(foreignKeys = [
    ForeignKey(entity = Outfit::class,
        parentColumns = ["id"],
        childColumns = ["outfitRefFK"]
        )
    ]
)
data class Clothing (
    //Sets all attributes and primary key
    @PrimaryKey(autoGenerate = true) val id: Int,
    val type: String,
    val color: String,
    val style: String,
    val description: String,
    val dateAdded: Date = Date(),
    val brand: String,
    val theme: String,
    val image: String,
    @Nullable val outfitRefFK: Int
    ): Parcelable

服裝.kt

@Parcelize
@Entity
data class Outfit (
    @PrimaryKey(autoGenerate = true) val id: Int,
    val outfitName: String,
    @Ignore
    val ClothingItems: List<Clothing>

):Parcelable

我查看了許多 Android 開發人員文檔,它們都提到了如何使用相同的服裝列表查詢服裝,但沒有提到如何使用 List 對象插入新服裝。

據我所知,SQLite 無法處理列表。 因此,我嘗試的一種方法是使用類型轉換器,但是,我很難將其實現到我的代碼中,主要是因為我是 GSON 的新手。

我一直在嘗試實現的來自 Google Android Docs 的示例對我來說不太有意義,但似乎可以在 POJO 之后插入對象列表:

谷歌插入示例:

@Dao
public interface MusicDao {
  @Insert(onConflict = OnConflictStrategy.REPLACE)
  public fun insertSongs(varargs songs: Song)

  @Insert
  public fun insertBoth(song1: Song, song2: Song)

  @Insert
  public fun insertAlbumWithSongs(album: Album, songs: List<Song>);
}

我假設我的目標是用類似的方法復制它,從 List 創建 Outfit。 據我所知,Google 文檔使用 3 個表(音樂、專輯和歌曲),因此我一直在努力尋找可以修改數據庫的位置。 我應該創建第三個表嗎? 有沒有人得出與Kotlin類似的結論? 如果你們中的任何人已經解決了這個問題或接近解決了這個問題,我們將不勝感激任何建議。

對於這里的其他來源是我的 Dao 的表格,還沒有完成,因為我無法找到存儲服裝項目的方法。

服裝.道

@Dao
interface ClothingDao {

    //Ignores when the exact same data is put in
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun addClothing(clothing: Clothing)

    @Update
    suspend fun updateClothing(clothing: Clothing)

    @Delete
    suspend fun deleteClothing(clothing: Clothing)

    @Query("DELETE FROM Clothing")
    suspend fun deleteAllClothing()

    @Query("SELECT * FROM Clothing ORDER BY id ASC")
    fun readAllData(): LiveData<List<Clothing>>

    @Query("SELECT * FROM Clothing WHERE type='Top' ORDER BY id ASC")
    fun selectClothingTops(): LiveData<List<Clothing>>

    //Called in ListFragment Searchbar. Queries Clothing Type or Clothing Color.
    @Query("SELECT * FROM Clothing WHERE type LIKE :searchQuery OR color LIKE :searchQuery")
    fun searchDatabase(searchQuery: String): LiveData<List<Clothing>>

}

OutfitDao.kt

@Dao
interface OutfitDao {

    // Grabs data from Outfit Table, necessary for each other Query to read
    // from in the Outfit Repository class

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun addOutfit(outfit: Outfit)


    @Query("SELECT * FROM Outfit ORDER BY id ASC")
    fun readAllData(): LiveData<List<Outfit>>


}

據我所知,SQLite 無法處理列表。 因此,我嘗試的一種方法是使用類型轉換器,但是,我很難將其實現到我的代碼中,主要是因為我是 GSON 的新手。

1). 將 Gson 庫添加到您的項目中,例如在您的 build.gradle(模塊)中:-

 implementation 'com.google.code.gson:gson:2.9.0'

2). 添加數據 class 例如 ClothingList:-

data class ClothingList(
    val clothingList: List<Clothing>
)

3). 修改Outfit class 以使用 ClothingList 而不是 List 並刪除 @Ignore 注釋,例如:-

@Entity
data class Outfit (
    @PrimaryKey(autoGenerate = true) val id: Int, /* more correct to use Long */
    val outfitName: String,
    //@Ignore
    val ClothingItems: ClothingList
)
  • 自動生成的列更正確地是 Long 的而不是 Int 的,因為理論上存儲的值最多可以有 64 位簽名。

4). 為 TypeConverters 添加一個新的 class 例如MyTypeConverters :-

class MyTypeConverters {

    @TypeConverter
    fun fromDateToLong(date: Date): Long {
        return date.time
    }
    @TypeConverter
    fun fromLongToDate(date: Long): Date {
        return Date(date)
    }
    @TypeConverter
    fun fromClothingToJSON(clothinglist: ClothingList): String {
        return Gson().toJson(clothinglist)
    }
    @TypeConverter
    fun fromJSONToClothing(json: String): ClothingList {
        return Gson().fromJson(json,ClothingList::class.java)
    }
}

5). 修改 @Database 注釋 class (具有最高范圍)以具有 @TypeConverters 注釋例如

@TypeConverters(value = [MyTypeConverters::class])
@Database(entities = [Clothing::class,Outfit::class], version = 1, exportSchema = false)
abstract class TheDatabase: RoomDatabase() {
....
}

您可以為他們提供一套服裝的清單。 然而,從關系數據庫的角度來看,這並不是真正理想的方式,因為整個衣服列表是一個單一的存儲值,這會帶來復雜性。

你的第二次嘗試(看起來是)將一件衣服與一件衣服聯系在一起,所以你的“藍色牛仔褲”如果用於多件衣服就必須重復。

建議的解決方案

我建議更好的解決方案是多對多的關系,這樣一件衣服可以使用任意數量的衣服,而一件衣服可以被任意數量的衣服使用。 因此,您的“藍色牛仔褲”將是一排。

要利用多對多關系,您有一個中間表,它是服裝和服裝項目之間的交叉引用。 即一列用於服裝的 ID 和一列用於服裝項目的 ID。 然后就不需要類型轉換器或存儲列表

工作示例

考慮以下工作示例:-

套裝class

@Entity
data class Outfit(
    @PrimaryKey
    @ColumnInfo(name = "outfitId")
    val id: Long?=null,
    val outfitName: String
)

服裝Class

@Entity
data class Clothing (
    //Sets all attributes and primary key
    @PrimaryKey/*(autoGenerate = true) inefficient not needed*/
    @ColumnInfo(name = "clothingId") /* suggest to have unique column names */
    val id: Long?=null, /* Long rather than Int */
    val type: String,
    val color: String,
    val style: String,
    val description: String,
    val dateAdded: Date = Date(),
    val brand: String,
    val theme: String,
    val image: String
)

多對多關系的中間表(映射、關聯、引用和其他名稱)

@Entity(
    primaryKeys = ["outfitIdRef","clothingIdRef"],
    foreignKeys = [
        ForeignKey(
            entity = Outfit::class,
            parentColumns = ["outfitId"],
            childColumns = ["outfitIdRef"],
            onUpdate = ForeignKey.CASCADE,
            onDelete = ForeignKey.CASCADE
        ),
        ForeignKey(
            entity = Clothing::class,
            parentColumns = ["clothingId"],
            childColumns = ["clothingIdRef"],
            onUpdate = ForeignKey.CASCADE,
            onDelete = ForeignKey.CASCADE
        )
    ]
)
data class OutFitClothingMappingTable (
    val outfitIdRef: Long,
    @ColumnInfo(index = true)
    val clothingIdRef: Long
)

一個 POJO class OutFitWithClothingList用於獲取 Outfit 及其相關的服裝列表。

data class OutFitWithClothingList(
    @Embedded
    val outfit: Outfit,
    @Relation(
        entity = Clothing::class,
        parentColumn = "outfitId",
        entityColumn = "clothingId",
        associateBy = Junction(
            value = OutFitClothingMappingTable::class,
            parentColumn = "outfitIdRef",
            entityColumn = "clothingIdRef"
        )
    )
    val clothingList: List<Clothing>
)

一個 POJO,與使用它的服裝相反

data class ClothingWithOutFitsList(
    @Embedded
    val clothing: Clothing,
    @Relation(
        entity = Outfit::class,
        parentColumn = "clothingId",
        entityColumn = "outfitId",
        associateBy = Junction(
            value = OutFitClothingMappingTable::class,
            parentColumn = "clothingIdRef",
            entityColumn = "outfitIdRef"
        )
    )
    val outfitList: List<Outfit>
)

帶有日期類型轉換器的 class(將日期存儲為 integer,即 Long):-

class TheTypeConverters {
    @TypeConverter
    fun fromDateToLong(date: Date): Long {
        return date.time
    }
    @TypeConverter
    fun fromLongToDate(date: Long): Date {
        return Date(date)
    }
}

一個(為了簡潔/方便)@Dao 注釋為 class Alldao包括查詢以獲取所有服裝及其服裝列表,以及獲取所有服裝項目及其使用的服裝,當然還有插入到表中。

@Dao
interface AllDao {

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    fun addOutfit(outfit: Outfit): Long
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    fun addClothing(clothing: Clothing): Long
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    fun addOutfitClothingMap(outFitClothingMappingTable: OutFitClothingMappingTable): Long /* value not of much use other than if 1 or greater insert, if -1 not inserted */

    @Query("SELECT * FROM clothing")
    fun getAllClothing(): List<Clothing>
    @Query("SELECT * FROM outfit")
    fun getAllOutfits(): List<Outfit>


    @Query("SELECT * FROM outfit")
    fun getAllOutfitsWithClothingList(): List<OutFitWithClothingList>

    @Query("SELECT * FROM clothing")
    fun getAllClothingWithOutfitList(): List<ClothingWithOutFitsList>
    
}

@Database 注釋為 class(為了簡潔和方便,請注意使用.allowMainThreadQuesries

@TypeConverters(value = [TheTypeConverters::class])
@Database(entities = [Outfit::class,Clothing::class,OutFitClothingMappingTable::class], version = 1, exportSchema = false)
abstract class TheDatabase: RoomDatabase() {
    abstract fun getAllDao(): AllDao

    companion object {
        @Volatile
        var instance: TheDatabase? = null
        fun getInstance(context: Context): TheDatabase {
            if (instance == null) {
                instance = Room.databaseBuilder(context,TheDatabase::class.java,"the_database.db")
                    .allowMainThreadQueries()
                    .build()
            }
            return instance as TheDatabase
        }
    }
}
  • 在數據庫級別(最高范圍)定義的類型轉換器

最后的活動代碼演示了插入服裝、服裝和映射以及使用服裝列表提取所有服裝和使用服裝項目的服裝列表提取所有服裝。

class MainActivity : AppCompatActivity() {
    lateinit var db: TheDatabase
    lateinit var dao: AllDao
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        db = TheDatabase.getInstance(this)
        dao = db.getAllDao()

        val outfit1 = dao.addOutfit(Outfit(outfitName = "Outfit1"))
        val outfit2 = dao.addOutfit(Outfit(outfitName = "Outfit2"))

        val clothing1 = dao.addClothing(Clothing(type = "Top", color = "Red", description = "Singlet",brand = "Fred's Clothing Inc", theme = "whatever", image = "image001", style = "style1"))
        val clothing2 = dao.addClothing(Clothing(type = "Bottom", color = "Blue", description = "Shorts",brand = "AC", theme = "whatever", image = "image002", style = "style2"))
        val clothing3 = dao.addClothing(Clothing(type = "Bottom", color = "White", description = "Skirt",brand = "AC", theme = "whatever", image = "image003", style = "style3"))
        val clothing4 = dao.addClothing(Clothing(type = "Hat", color = "Brown", description = "Hat with feather",brand = "AC", theme = "whatever", image = "image003", style = "style4"))
        // etc

        dao.addOutfitClothingMap(OutFitClothingMappingTable(outfit1,clothing1))
        dao.addOutfitClothingMap(OutFitClothingMappingTable(outfit1,clothing2))
        dao.addOutfitClothingMap(OutFitClothingMappingTable(outfit2,clothing1))
        dao.addOutfitClothingMap(OutFitClothingMappingTable(outfit2,clothing3))
        dao.addOutfitClothingMap(OutFitClothingMappingTable(outfit2,clothing4))


        for (owc in dao.getAllOutfitsWithClothingList()) {
            Log.d("DBINFO","Outfit is ${owc.outfit.outfitName} ID is ${owc.outfit.id}, it has ${owc.clothingList.size} Items of Clothing, they are:-")
            for (c in owc.clothingList) {
                Log.d("DBINFO","\tClothing Item desc is ${c.description} Date is ${c.dateAdded} Brand is ${c.brand} type is ${c.type} etc")
            }
        }


        for (cwo in dao.getAllClothingWithOutfitList()) {
            Log.d("DBINFO","Clothing is ${cwo.clothing.description} color is ${cwo.clothing.color} it is used by ${cwo.outfitList.size } Outfits, they are:-")
            for(o in cwo.outfitList) {
                Log.d("DBINFO","\tOutfit is ${o.outfitName} it's ID is ${o.id}")
            }
        }

    }
}

結果(輸出到日志)

2022-05-01 08:55:15.287 D/DBINFO: Outfit is Outfit1 ID is 1, it has 2 Items of Clothing, they are:-
2022-05-01 08:55:15.294 D/DBINFO:   Clothing Item desc is Singlet Date is Sun May 01 08:55:15 GMT+10:00 2022 Brand is Fred's Clothing Inc type is Top etc
2022-05-01 08:55:15.294 D/DBINFO:   Clothing Item desc is Shorts Date is Sun May 01 08:55:15 GMT+10:00 2022 Brand is AC type is Bottom etc
2022-05-01 08:55:15.294 D/DBINFO: Outfit is Outfit2 ID is 2, it has 3 Items of Clothing, they are:-
2022-05-01 08:55:15.294 D/DBINFO:   Clothing Item desc is Singlet Date is Sun May 01 08:55:15 GMT+10:00 2022 Brand is Fred's Clothing Inc type is Top etc
2022-05-01 08:55:15.294 D/DBINFO:   Clothing Item desc is Skirt Date is Sun May 01 08:55:15 GMT+10:00 2022 Brand is AC type is Bottom etc
2022-05-01 08:55:15.295 D/DBINFO:   Clothing Item desc is Hat with feather Date is Sun May 01 08:55:15 GMT+10:00 2022 Brand is AC type is Hat etc


2022-05-01 08:55:15.298 D/DBINFO: Clothing is Singlet color is Red it is used by 2 Outfits, they are:-
2022-05-01 08:55:15.298 D/DBINFO:   Outfit is Outfit1 it's ID is 1
2022-05-01 08:55:15.298 D/DBINFO:   Outfit is Outfit2 it's ID is 2
2022-05-01 08:55:15.298 D/DBINFO: Clothing is Shorts color is Blue it is used by 1 Outfits, they are:-
2022-05-01 08:55:15.298 D/DBINFO:   Outfit is Outfit1 it's ID is 1
2022-05-01 08:55:15.298 D/DBINFO: Clothing is Skirt color is White it is used by 1 Outfits, they are:-
2022-05-01 08:55:15.298 D/DBINFO:   Outfit is Outfit2 it's ID is 2
2022-05-01 08:55:15.298 D/DBINFO: Clothing is Hat with feather color is Brown it is used by 1 Outfits, they are:-
2022-05-01 08:55:15.298 D/DBINFO:   Outfit is Outfit2 it's ID is 2

通過AppInspection即數據庫中存儲的數據

在此處輸入圖像描述

在此處輸入圖像描述

和映射表

在此處輸入圖像描述

額外重新分級@Relation

當您使用@Relation 時,無論對象如何,都會檢索所有子項,並且它們將按照適合查詢優化器的順序排列。 如果您指定了 ORDER 或 WHERE 子句,這可能會令人沮喪/困惑。

下面是一些示例查詢,展示了

  • a) 你的查詢很好,如果說在創建服裝時你只想 select 上衣

  • b) 一個查詢,你只想找到有上衣的服裝並列出所有衣服(通過@Relation)繞過@Relation 得到所有的孩子,只得到一些孩子)

  • 除了額外的 @Dao 函數和用於演示它們的活動代碼外,沒有其他更改

所以額外的@Dao 函數是

@Transaction
@Query("SELECT * FROM outfit " +
        " JOIN outfitclothingmappingtable ON outfit.outfitId = outfitclothingmappingtable.outfitIdRef " +
        " JOIN clothing ON clothingIdRef = clothingId " +
        "WHERE clothing.type LIKE :searchQuery OR color LIKE :searchQuery")
fun getOutfitsWithClothingSearchingClothing(searchQuery: String): List<OutFitWithClothingList>
/* NOTE */
/* As this uses @Relation the outfits returned will contain ALL related clothing items */


/* Things can get a little complicated though due to @Relation */
/* Say you wanted a List of the Outfits that include  specific clothing and to only list those clothing items not ALL */
/* Then 2 queries and a final function that invokes the 2 queries is easiest */
/* However the first query (the actual SQL) has all the data but would need a loop to select apply the clothing to the outfits */
@Query("SELECT * FROM outfit " +
        " JOIN outfitclothingmappingtable ON outfit.outfitId = outfitclothingmappingtable.outfitIdRef " +
        " JOIN clothing ON clothingIdRef = clothingId " +
        "WHERE clothing.type LIKE :searchQuery OR color LIKE :searchQuery")
fun getOutfitsOnlySearchingClothing(searchQuery: String): List<Outfit>
@Query("SELECT * FROM outfitclothingmappingtable JOIN clothing ON clothingIdRef = clothingId WHERE (type LIKE :searchQuery OR color LIKE :searchQuery) AND outfitIdRef=:outfitId")
fun getClothingThatMatchesSearchForAnOutfit(searchQuery: String, outfitId: Long): List<Clothing>

@Transaction
@Query("")
fun getOutfitsWithOnlyClothingsThatMatchSearch(searchQuery: String): List<OutFitWithClothingList> {
    val rv = mutableListOf<OutFitWithClothingList>()
    val outfits = getOutfitsOnlySearchingClothing(searchQuery)
    for (o in outfits) {
        rv.addAll(listOf(OutFitWithClothingList(o,getClothingThatMatchesSearchForAnOutfit(searchQuery,o.id!!))))
    }
    return rv
}
  • 請注意,tablename.column 已被使用但並非普遍使用,僅當列名不明確時才需要 tablename.column(因此為什么 @ColumnInfo(name =??) 用於 id 列,因此它們不是模棱兩可的。
    • 如果列名不明確並且您使用 tablename.column name,則提取的列名將具有相同的名稱並且 Room 將是 select 只有最后一個所以 outfit.id 將與 clothing.id 具有相同的值,再次通過使用唯一列來避免名字。
  • 所以 tablename.column 僅用於顯示它的用途。

演示的活動可以包括:-

    /* Your Query */
    for (c in dao.searchDatabase("Top")) {
        Log.d("SRCHINFO1","Clothing is ${c.description} ....")
    }

    /* @Relation Limited Search  complete outfit (all clothing) that has type of Top */
    for(owc in dao.getOutfitsWithClothingSearchingClothing("Top")) {
        Log.d("SRCHINFO2","Outfit is ${owc.outfit.outfitName}")
        for (c in owc.clothingList) {
            Log.d("SRCHINFO2c","Clothing is ${c.description} ....")
        }
    }

    /* Only the Outfits that match the search with the clothing that fits the search NOT ALL CLothing*/
    for(owc in dao.getOutfitsWithOnlyClothingsThatMatchSearch("Top")) {
        Log.d("SRCHINFO3","Outfit is ${owc.outfit.outfitName}")
        for (c in owc.clothingList) {
            Log.d("SRCHINFO3c","Clothing is ${c.description} ....")
        }
    }

output 將是(第一次運行):-

2022-05-01 13:31:52.485 D/SRCHINFO1: Clothing is Singlet ....


2022-05-01 13:31:52.488 D/SRCHINFO2: Outfit is Outfit1
2022-05-01 13:31:52.488 D/SRCHINFO2c: Clothing is Singlet ....
2022-05-01 13:31:52.488 D/SRCHINFO2c: Clothing is Shorts ....

2022-05-01 13:31:52.489 D/SRCHINFO2: Outfit is Outfit2
2022-05-01 13:31:52.489 D/SRCHINFO2c: Clothing is Singlet ....
2022-05-01 13:31:52.489 D/SRCHINFO2c: Clothing is Skirt ....
2022-05-01 13:31:52.489 D/SRCHINFO2c: Clothing is Hat with feather ....


2022-05-01 13:31:52.494 D/SRCHINFO3: Outfit is Outfit1
2022-05-01 13:31:52.494 D/SRCHINFO3c: Clothing is Singlet ....

2022-05-01 13:31:52.494 D/SRCHINFO3: Outfit is Outfit2
2022-05-01 13:31:52.494 D/SRCHINFO3c: Clothing is Singlet ....
  • 您的查詢找到 Singlet
  • @Relation 查詢找到 2 件使用 Singlet 的服裝並列出所有服裝
  • 最后一個查詢找到了 2 件使用 Singlet 的 OutFits,但只列出了 Singlet 而不是所有其他服裝(根據需要)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM