SQLite和Room只能直接存储特定类型的值:
- 空值(NULL),
- 整型值(整型),
- 十进制类型的值(实数),
- 字符串类型值(TEXT)和
- 字节流类型值(BLOB)
其他值需要转换为上述值之一.显然,NULL是没有用的,因为该信息不能用于重构底层值(您的List).INTEGER值需要一些复杂的方法来重建基础值(List),小数也是如此.因此,您需要将该值转换为List的TEXTual或ByteStream表示,以便存储List.
Hence the suggestion that a TypeConverter is used.
通常使用JSON表示,这是底层对象(部件列表)的字符串表示.
However,因为Parts本身就是一个表,所以不将存储在该表中的数据复制到另一个表中,而是利用作为relational数据库的SQLite的relationship功能似乎是明智的.
典型的relationship是通过在另一个表中存储唯一标识相关行的单个值来反映的(您已经定义了这样的值,即id字段,因为它必须是作为主键的唯一值).
单个Maintenance对象极有可能与另一个Maintenance具有相同的相关部分,因此单个部分可能与多个Maintenance相关.这是一种多对多类型的关系.
通常,多对多关系是通过使用另一个表来提供的,该表存储两个值,即对每个相关行(维护和相关部分)的引用.这样的表有许多术语,如关联表、参考表、映射表等.
通常,主键是两个值的组合(Room需要定义主键).
因为访问可能是通过部件(例如,获取使用特定部件的所有维护)或通过维护(例如,获取维护使用的所有部件,即显然是您想要的).因此,根据第二个值创建索引可能会更有效(如果不存在这样的索引,Room将发出警告).
因此,建议您修改Maintenance类以不包括List字段,而是为关系表引入一个新的带@Entity
注释的类.
所以你可以:-
@Entity
data class Maintenance(
@PrimaryKey(autoGenerate = true)
val id: Int,
var description: String,
/*var listParts: List<Parts> defunct id using a many-many relationship*/
)
@Entity
data class Parts (
@PrimaryKey(autoGenerate = true)
val id: Int,
var serialNumber: Long,
var description: String,
var model: String,
)
@Entity(
primaryKeys = ["maintenanceId","partsId"]
)
data class MaintenancePartsRelationship(
val maintenanceId: Int,
@ColumnInfo(index = true)
val partsId: Int
)
要对其部件进行维护,您可以使用POJO,例如:-
data class MaintenanceWithParts(
@Embedded
val maintenance: Maintenance,
@Relation(
entity = Parts::class, parentColumn = "id", entityColumn = "id",
associateBy = Junction(MaintenancePartsRelationship::class,"maintenanceId","partsId")
)
val partsList: List<Parts>
)
如果您曾经想通过使用部件的维护来获取部件,那么您可以拥有如下POJO:
data class PartsWithMaintenances(
@Embedded
val parts: Parts,
@Relation(
entity = Maintenance::class, parentColumn = "id", entityColumn = "id",
associateBy = Junction(MaintenancePartsRelationship::class, parentColumn = "partsId", entityColumn = "maintenanceId")
)
val maintenanceList: List<Maintenance>
)
- 使用这些POJO,AssociateBy的Junction定义了关系/引用/映射/associatave表和引用父项/子项的列.
Working Demo个
为了演示上述内容,随后引入了以下附加代码:
@Dao
interface AllDAOs {
@Insert( onConflict = OnConflictStrategy.IGNORE)
fun insert(maintenance: Maintenance): Long
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(parts: Parts): Long
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(maintenancePartsRelationship: MaintenancePartsRelationship): Long
@Query("SELECT * FROM maintenance")
fun getALlMaintenancesWithTheirParts(): List<MaintenanceWithParts>
@Query("SELECT * FROM parts")
fun getAllPartsWithTheirMaintenances(): List<PartsWithMaintenances>
}
@Database(entities = [Maintenance::class,Parts::class,MaintenancePartsRelationship::class], version = 1, exportSchema = false)
abstract class TheDatabase: RoomDatabase() {
abstract fun getAllDAOs(): AllDAOs
companion object {
private 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: AllDAOs
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getAllDAOs()
val p1Id = dao.insert(Parts(id= 0, serialNumber = 100000000001, description = "Parts 1", model = "P1Model"))
val p2Id = dao.insert(Parts(id= 0, serialNumber = 100000000002, description = "Parts 2", model = "P2Model"))
val p3Id = dao.insert(Parts(id= 0, serialNumber = 100000000003, description = "Parts 3", model = "P3Model"))
val m1Id = dao.insert(Maintenance(id = 0, description = "M1"))
val m2Id = dao.insert(Maintenance(id = 0, description = "M2"))
val m3Id = dao.insert(Maintenance(id = 0, description = "M3"))
val p4Id = dao.insert(Parts(id= 0, serialNumber = 100000000004, description = "Parts 4", model = "P4Model"))
val m4Id = dao.insert(Maintenance(id = 0, description = "M4"))
dao.insert(MaintenancePartsRelationship(maintenanceId = m1Id.toInt(), partsId = p1Id.toInt()))
dao.insert(MaintenancePartsRelationship(maintenanceId = m1Id.toInt(), partsId = p3Id.toInt()))
dao.insert(MaintenancePartsRelationship(maintenanceId = m2Id.toInt(), partsId = p2Id.toInt()))
dao.insert(MaintenancePartsRelationship(maintenanceId = m2Id.toInt(), partsId = p4Id.toInt()))
dao.insert(MaintenancePartsRelationship(maintenanceId = m3Id.toInt(), partsId = p4Id.toInt()))
dao.insert(MaintenancePartsRelationship(maintenanceId = m3Id.toInt(), partsId = p1Id.toInt()))
dao.insert(MaintenancePartsRelationship(maintenanceId = m3Id.toInt(), partsId = p3Id.toInt()))
dao.insert(MaintenancePartsRelationship(maintenanceId = m3Id.toInt(), partsId = p2Id.toInt()))
dao.insert(MaintenancePartsRelationship(maintenanceId = m3Id.toInt(), partsId = p2Id.toInt()))
for (mwp in dao.getALlMaintenancesWithTheirParts()) {
val sb = StringBuilder()
for (p in mwp.partsList) {
sb.append("\n\tParts ID is ${p.id} Desc is ${p.description} Model is ${p.model} Serial is ${p.serialNumber}")
}
Log.d("DBINFO","Maintenance ID is ${mwp.maintenance.id} Desc is ${mwp.maintenance.description}. It has ${mwp.partsList.size} Parts. They are:-$sb")
}
for (pwm in dao.getAllPartsWithTheirMaintenances()) {
val sb = StringBuilder()
for (m in pwm.maintenanceList) {
sb.append("\n\tMaintenance ID is ${m.id} Desc is ${m.description}")
}
Log.d("DBINFO","Parts ID is ${pwm.parts.id} Desc is ${pwm.parts.description} Model is ${pwm.parts.model} Serial is ${pwm.parts.serialNumber}. It has ${pwm.maintenanceList.size} Maintenances. They are:-$sb")
}
}
}
- 为简洁起见,请在主线程上运行注释
- the above first gets an instance of the Database 然后 the DAO from the database.
- 然后添加多个部件和维护(顺序无关紧要,因为在此阶段它们彼此独立).
- 然后添加MaintenanceWithParts行,形成各种关系
- M1有第1部分和第3部分
- M2分为第2部分和第4部分
- M3全部4个部分
- M4没有部件
- 相反,所有部件都由两次维护使用
- 最后,提取所有维护及其部件并将其写入日志(log).同样,所有部件及其维护都写入日志(log).
Result个
运行时,日志(log)包括:-
2023-12-30 06:34:58.747 D/DBINFO: Maintenance ID is 1 Desc is M1. It has 2 Parts. They are:-
Parts ID is 1 Desc is Parts 1 Model is P1Model Serial is 100000000001
Parts ID is 3 Desc is Parts 3 Model is P3Model Serial is 100000000003
2023-12-30 06:34:58.747 D/DBINFO: Maintenance ID is 2 Desc is M2. It has 2 Parts. They are:-
Parts ID is 2 Desc is Parts 2 Model is P2Model Serial is 100000000002
Parts ID is 4 Desc is Parts 4 Model is P4Model Serial is 100000000004
2023-12-30 06:34:58.747 D/DBINFO: Maintenance ID is 3 Desc is M3. It has 4 Parts. They are:-
Parts ID is 1 Desc is Parts 1 Model is P1Model Serial is 100000000001
Parts ID is 2 Desc is Parts 2 Model is P2Model Serial is 100000000002
Parts ID is 3 Desc is Parts 3 Model is P3Model Serial is 100000000003
Parts ID is 4 Desc is Parts 4 Model is P4Model Serial is 100000000004
2023-12-30 06:34:58.747 D/DBINFO: Maintenance ID is 4 Desc is M4. It has 0 Parts. They are:-
2023-12-30 06:34:58.750 D/DBINFO: Parts ID is 1 Desc is Parts 1 Model is P1Model Serial is 100000000001. It has 2 Maintenances. They are:-
Maintenance ID is 1 Desc is M1
Maintenance ID is 3 Desc is M3
2023-12-30 06:34:58.750 D/DBINFO: Parts ID is 2 Desc is Parts 2 Model is P2Model Serial is 100000000002. It has 2 Maintenances. They are:-
Maintenance ID is 2 Desc is M2
Maintenance ID is 3 Desc is M3
2023-12-30 06:34:58.750 D/DBINFO: Parts ID is 3 Desc is Parts 3 Model is P3Model Serial is 100000000003. It has 2 Maintenances. They are:-
Maintenance ID is 1 Desc is M1
Maintenance ID is 3 Desc is M3
2023-12-30 06:34:58.750 D/DBINFO: Parts ID is 4 Desc is Parts 4 Model is P4Model Serial is 100000000004. It has 2 Maintenances. They are:-
Maintenance ID is 2 Desc is M2
Maintenance ID is 3 Desc is M3
Additional (Referential Integrity)个
尽管上述方法奏效,但并没有强制执行所谓的参照整体法.例如,您可以包括以下内容:
dao.insert(MaintenancePartsRelationship(9999,6666))
这将导致引用表中的行引用不存在的维护和不存在的部件,这可能会导致问题,但至少是浪费.这些引用并不具有它们与任何东西相关的完整性.
通常情况下,您会希望不允许这样做.SQLite通过外键约束的方式满足了不允许这种浪费的需要.Room通过允许指定外键来满足这一要求.
您可能希望考虑通过包含ForeignKey定义来修改上述(MaintenancePartsRelationship表)以不允许此类差异.
因此,您可以考虑使用:
@Entity(
primaryKeys = ["maintenanceId","partsId"],
foreignKeys = [
ForeignKey(
entity = Maintenance::class,
parentColumns = ["id"],
childColumns = ["maintenanceId"],
/* Optional but helps maintain referential Integrity */
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
),
ForeignKey(
entity = Parts::class,
parentColumns = ["id"],
childColumns = ["partsId"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
)
]
)
这就是说:
- maintenanceId值MUST是Maintenance表中的现有id值,并且
- partsId值,MUST是Parts表中现有的id值.
使用CASCADE时,onDelete和onUpdate会自动维护参照完整性,因为:
- 对于onDelete,如果父项被删除,则引用该父项的子项将自动删除.
- 对于onUpdate,如果更改了参考值(id),则会对子对象进行该更改.
- 这通常是比较罕见的情况.
因此,onDelete
和onUpdate
的可选方面.
- 一个问题是,如果引用完整性受到损害,则会发生异常,这将引入复杂的编码来处理.例如,如果try 上面的插入,则异常将如下所示
:-
2023-12-30 07:21:15.449 2921-2921/a.a.so77733410kotlinroomstoringlist E/AndroidRuntime: FATAL EXCEPTION: main
Process: a.a.so77733410kotlinroomstoringlist, PID: 2921
java.lang.RuntimeException: Unable to start activity ComponentInfo{a.a.so77733410kotlinroomstoringlist/a.a.so77733410kotlinroomstoringlist.MainActivity}: android.database.sqlite.SQLiteConstraintException: FOREIGN KEY constraint failed (code 787 SQLITE_CONSTRAINT_FOREIGNKEY)
- 然而,处理上面的内容可能会优先于遇到可能更模糊的异常,因为引用了不存在的父对象.
Using Type Converters个
Consider the following that demonstrates using Type Converters:-
@Entity
data class OriginalMaintenance(
@PrimaryKey(autoGenerate = true)
val id: Int,
var description: String,
var listParts: PartsList
)
data class PartsList(
var partsList: List<Parts>
)
class TheTypeConverters {
@TypeConverter
fun convertFromPartsListToJSOnString(lp: PartsList): String = Gson().toJson(lp)
@TypeConverter
fun convertFromJSONStringToPartsList(js: String): PartsList = Gson().fromJson(js,PartsList::class.java)
}
- 原始维护以反映原始维护(但也保留建议)
- 添加了PartsList以适应com.google.code.gson库(添加到项目中).
- TypeConverters用于在存储数据时将部件列表转换为JSON字符串,并在检索数据时将存储的字符串转换为部件列表.
To inform Room where the TypeConverters can be found then the following is added (in this case at the @Database
level (fullest scope)):-
@TypeConverters(TheTypeConverters::class)
The entities parameter of the @Database
annotation is changed to include the new OriginalMaintenance class:-
@Database(entities = [Maintenance::class,Parts::class,MaintenancePartsRelationship::class,OriginalMaintenance::class], version = 1, exportSchema = false)
The AllDAOs interface has the following added:-
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(originalMaintenance: OriginalMaintenance): Long
@Query("SELECT * FROM originalmaintenance")
fun getAllOriginalMaintenance(): List<OriginalMaintenance>
Finally the activity code has the following added:-
val pl1 = PartsList(
listOf(
Parts(0,100000000001, description = "Parts 1", model = "P1Model"),
Parts(0, serialNumber = 100000000003, description = "Parts 3", model = "P3Model")
)
)
val pl2 = PartsList(
listOf(
Parts(id=0, serialNumber = 100000000002, description = "Parts 2", model = "P2Model"),
Parts(id=0, serialNumber = 100000000004, description = "Parts 4", model = "P4Model")
)
)
val pl3 = PartsList(
listOf(
Parts(0,100000000001, description = "Parts 1", model = "P1Model"),
Parts(id=0, serialNumber = 100000000002, description = "Parts 2", model = "P2Model"),
Parts(0, serialNumber = 100000000003, description = "Parts 3", model = "P3Model"),
Parts(id=0, serialNumber = 100000000004, description = "Parts 4", model = "P4Model")
)
)
dao.insert(OriginalMaintenance(0,"OM1",pl1))
dao.insert(OriginalMaintenance(0,"OM2",pl2))
dao.insert( OriginalMaintenance(0,"OM3",pl3))
dao.insert(OriginalMaintenance(0,"OM4", PartsList(listOf())))
for (om in dao.getAllOriginalMaintenance()) {
val sb = StringBuilder()
for (p in om.listParts.partsList) {
sb.append("\n\tParts ID is ${p.id} Desc is ${p.description} Model is ${p.model} Serial is ${p.serialNumber}")
}
Log.d("DBINFO","Maintenance ID is ${om.id} Desc is ${om.description}. It has ${om.listParts.partsList.size} Parts. They are:-$sb")
}
- This code will add the equivalent of the initial demo 然后 extract the data and output it to the log, as per
:-
2023-12-30 08:22:11.060 D/DBINFO: Maintenance ID is 1 Desc is OM1. It has 2 Parts. They are:-
Parts ID is 0 Desc is Parts 1 Model is P1Model Serial is 100000000001
Parts ID is 0 Desc is Parts 3 Model is P3Model Serial is 100000000003
2023-12-30 08:22:11.060 D/DBINFO: Maintenance ID is 2 Desc is OM2. It has 2 Parts. They are:-
Parts ID is 0 Desc is Parts 2 Model is P2Model Serial is 100000000002
Parts ID is 0 Desc is Parts 4 Model is P4Model Serial is 100000000004
2023-12-30 08:22:11.061 D/DBINFO: Maintenance ID is 3 Desc is OM3. It has 4 Parts. They are:-
Parts ID is 0 Desc is Parts 1 Model is P1Model Serial is 100000000001
Parts ID is 0 Desc is Parts 2 Model is P2Model Serial is 100000000002
Parts ID is 0 Desc is Parts 3 Model is P3Model Serial is 100000000003
Parts ID is 0 Desc is Parts 4 Model is P4Model Serial is 100000000004
2023-12-30 08:22:11.061 D/DBINFO: Maintenance ID is 4 Desc is OM4. It has 0 Parts. They are:-
一切正常.However基础数据讲述了一个不同的故事.
首先,添加OM时,部件列表可能包含也可能不包含部件表中存在的部件.每个这样的列表都以任何形式存储,无论它是否存在于零件表中.
然后使用应用判断来查看数据库(其中包含两种技术的数据).
比较维护表和原始维护表.
然后
- 如容易看到的,后者在partsList列中包含更多的数据,并且该数据被标识符和分隔符inflating ,并且如果一个部分被使用不止一次,则该部分被重复.
The Parts table effectively stores exactly the same data but is, in comparison:-
- 当然,如果使用原始的,如果添加了部件,这些数据就会存在,尽管那时它几乎没有什么用处,很可能会导致混乱.
Of course using the relational aspect and the many-many table has to be considered this table:-