据我所知,SQLite无法处理列表。因此,我尝试了一种方法,即使用类型转换器,但是,由于我对GSON不熟悉,我很难将其实现到我的代码中。
1). 将Gson库添加到您的项目中,例如在build.gradle(模块)中:
implementation 'com.google.code.gson:gson:2.9.0'
2). 添加一个数据类,例如 ClothingList:
data class ClothingList(
val clothingList: List<Clothing>
)
3). 修改 Outfit 类,使用 ClothingList 替代 List,并移除 @Ignore 注解,例如:
@Entity
data class Outfit (
@PrimaryKey(autoGenerate = true) val id: Int,
val outfitName: String,
val ClothingItems: ClothingList
)
- 自动生成的列更正确地是Long而不是Int,因为理论上存储的值可以高达64位有符号。
4). 添加一个新类用于类型转换器,例如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 注释的类(具有最高作用域),使其具有 @TypeConverters 注释,例如:
@TypeConverters(value = [MyTypeConverters::class])
@Database(entities = [Clothing::class,Outfit::class], version = 1, exportSchema = false)
abstract class TheDatabase: RoomDatabase() {
....
}
您可以将服装列表放入一个装备中。但是,从关系数据库的角度来看,这并不是理想的方式,因为整个衣服列表是单个存储值,会引入复杂性。
您的第二个尝试(似乎是)将一件衣服与一个装备关联起来,因此如果您的“蓝色牛仔裤”在多个装备中使用,则必须重复。
建议的解决方案是实现多对多的关系,因此服装可以使用任意数量的服装,并且服装可以被任何数量的装备使用。因此,“蓝色牛仔裤”只需一行即可。
要利用多对多的关系,您需要一个中间表格,它是装备和服装之间的交叉引用。即一个用于装备ID的列和一个用于服装ID的列。然后就不需要类型转换器或者存储列表了。
考虑以下工作示例:-
装备类
@Entity
data class Outfit(
@PrimaryKey
@ColumnInfo(name = "outfitId")
val id: Long?=null,
val outfitName: String
)
和 服装 类
@Entity
data class Clothing (
@PrimaryKey
@ColumnInfo(name = "clothingId")
val id: Long?=null,
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类 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>
)
一个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>
)
一个带有TypeConverters的类,用于Date(将日期存储为整数即Long):-
class TheTypeConverters {
@TypeConverter
fun fromDateToLong(date: Date): Long {
return date.time
}
@TypeConverter
fun fromLongToDate(date: Long): Date {
return Date(date)
}
}
一个(为了简洁/方便)用 @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
@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 注释的类(为了简洁和方便使用 .allowMainThreadQueries)
@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
}
}
}
- 在数据库级别(最高级别)定义的TypeConverters
最后,展示插入Outfits、Clothing和映射以及提取所有Outfits和其包含的Clothing列表以及所有Clothing和使用该衣物的Outfits列表的activity代码。
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"))
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即数据库中存储的数据。
![enter image description here](https://istack.dev59.com/7KM75.webp)
![enter image description here](https://istack.dev59.com/Nscti.webp)
和映射表
![enter image description here](https://istack.dev59.com/FiRXi.webp)
关于@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>
@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(因此id列使用了@ColumnInfo(name = ??)以使它们不会产生歧义。
如果列名不明确且您使用tablename.column名称,则提取的列名将具有相同的名称,并且Room将仅选择最后一个,因此outfit.id将与clothing.id具有相同的值,这可以通过使用唯一的列名来避免。
因此,tablename.column仅用于展示其用途。
为了演示,活动可以包括:
for (c in dao.searchDatabase("Top")) {
Log.d("SRCHINFO1","Clothing is ${c.description} ....")
}
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} ....")
}
}
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} ....")
}
}
输出结果将是(第一次运行):
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查询找到了使用Singlet的2个Outfits,并列出所有衣服
- 最后一个查询找到了使用Singlet的2个Outfits,但只列出了Singlet而不是所有其他衣服(如所需)
@Relation
的工作原理(并使一些人感到困惑)。 - MikeT