使用 Kotlin 在 Android Room 数据库中插入对象列表

Kyl*_*ken 3 android kotlin android-room

Android 初学者正在努力克服 Room Database 的这一限制。我正在使用两张桌子,“服装”和“服装”。用户可以通过插入提供给他们的值来创建服装。然后,在单独的页面上,用户可以插入带有他们已在 Clothing.kt 中创建的先前服装的服装。出于应用程序的考虑,关系只是一对多,这意味着我只需要使用许多服装项目创建一套服装。到目前为止,这是我的代码:

服装.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
Run Code Online (Sandbox Code Playgroud)

服装.kt

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

):Parcelable
Run Code Online (Sandbox Code Playgroud)

我查看了许多 Android 开发人员文档,它们都提到了如何使用相同的服装列表查询服装,但没有提到如何使用列表对象插入新服装。

据我所知,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>);
}
Run Code Online (Sandbox Code Playgroud)

我假设我的目标是用类似的方法复制它,从列表中创建服装。据我所知,Google Docs 使用 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>>

}
Run Code Online (Sandbox Code Playgroud)

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


}
Run Code Online (Sandbox Code Playgroud)

Mik*_*keT 8

据我所知,SQLite 无法处理列表。因此,我尝试的一种方法是使用类型转换器,但是,我很难将其实现到我的代码中,主要是因为我是 GSON 的新手。

1)。将 Gson 库添加到您的项目中,例如在您的 build.gradle (模块)中:-

 implementation 'com.google.code.gson:gson:2.9.0'
Run Code Online (Sandbox Code Playgroud)

2)。添加数据类,例如 ClothingList :-

data class ClothingList(
    val clothingList: List<Clothing>
)
Run Code Online (Sandbox Code Playgroud)

3)。修改Outfit类以使用 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
)
Run Code Online (Sandbox Code Playgroud)
  • 自动生成的列更正确的是 Long 列而不是 Int 列,因为理论上存储的值最多可以是 64 位有符号的。

4).为 TypeConverters 添加一个新类,例如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)
    }
}
Run Code Online (Sandbox Code Playgroud)

5)。修改@Database注释的类(具有最高范围)以具有@TypeConverters注释,例如

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

您可以为他们提供一套服装中的服装清单。然而,从关系数据库方面来看,这并不是真正理想的方式,因为由于整个衣服列表是单个存储值,这会带来复杂性。

您的第二次尝试(似乎是)将一件衣服与一件衣服绑在一起,因此如果在多件衣服中使用您的“蓝色牛仔裤”,则必须重复。

建议的解决方案

我建议更好的解决方案是多对多关系,因此一套服装可以使用任意数量的服装项目,并且一件服装项目可以由任意数量的服装使用。因此,您的“蓝色牛仔裤”将是单行。

为了利用多对多关系,您需要一个中间表,它是服装和服装之间的交叉引用。即服装 ID 列和服装 ID 列。这样就不需要类型转换器或存储列表

工作示例

考虑以下工作示例:-

服装类

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

还有服装

@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
)
Run Code Online (Sandbox Code Playgroud)

多对多关系的中间表(映射、关联、引用和其他名称)

@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
)
Run Code Online (Sandbox Code Playgroud)

POJO 类OutFitWithClothingList,用于获取服装及其相关服装列表。

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>
)
Run Code Online (Sandbox Code Playgroud)

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>
)
Run Code Online (Sandbox Code Playgroud)

具有日期类型转换器的类(将日期存储为整数,即长整型):-

class TheTypeConverters {
    @TypeConverter
    fun fromDateToLong(date: Date): Long {
        return date.time
    }
    @TypeConverter
    fun fromLongToDate(date: Long): Date {
        return Date(date)
    }
}
Run Code Online (Sandbox Code Playgroud)

单个(为了简洁/方便)@Dao 注释类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>
    
}
Run Code Online (Sandbox Code Playgroud)

@Database 注解的类(为了简洁和方便使用请注意.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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
  • 在数据库级别定义的类型转换器(最高范围)

最后的活动代码演示插入服装、服装和映射,以及提取包含服装列表的所有服装以及使用服装项目的服装列表的所有服装。

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

    }
}
Run Code Online (Sandbox Code Playgroud)

结果(输出到日志)

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
Run Code Online (Sandbox Code Playgroud)

通过AppInspection即存储在数据库中的数据

在此输入图像描述

在此输入图像描述

和映射表

在此输入图像描述

额外的重新分级@Relation

当您使用 @Relation 时,无论对象如何,都会检索所有子级,并且它们将按照适合查询优化器的任何顺序。如果您指定了 ORDER 或 WHERE 子句,这可能会令人沮丧/困惑。

以下是一些示例查询,演示了

  • a) 如果您在创建服装时只想选择上衣,那么您的查询没问题

  • b) 一个查询,您只想查找具有上衣的服装并列出所有衣服(通过 @Relation) -c) 一个查询,您希望查找具有上衣的服装,但只列出属于上衣的服装(演示如何绕过 @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
}
Run Code Online (Sandbox Code Playgroud)
  • 请注意,tablename.column 已被使用,但并非普遍使用,仅当列名称不明确时才需要 tablename.column (因此为什么 @ColumnInfo(name = ??) 用于 id 列,这样它们就不会产生歧义。
    • 如果列名称不明确并且您使用 tablename.column 名称,则提取的列名称将具有相同的名称,并且 Room 将仅选择最后一个,因此outlet.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} ....")
        }
    }
Run Code Online (Sandbox Code Playgroud)

输出将是(第一次运行):-

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 ....
Run Code Online (Sandbox Code Playgroud)
  • 您的查询找到 Singlet
  • @Relation 查询找到 2 个使用 Singlet 的服装并列出所有服装
  • 最后一个查询找到 2 个使用单衣的 OutFits,但仅列出单衣,而不列出所有其他服装(根据需要)