您的问题是您不能将列表/数组类型作为列。列必须是一组有限的类型 String、Integer、Long、Double、Float、ByteArray 是最常见的可接受类型。
所以
@Embedded
val submenus: List<Submenu>
不起作用,我相信这是您收到错误的原因(Room 没有如何处理列表)。
你可以走两条路:-
- 利用关系,从而为子菜单列表使用第二个表(实体)。
- 创建和存储子菜单的表示形式,可能是字符串 (json)。在这种情况下,您将提取子菜单的 json 并将其存储。
这是 1 的一个工作示例
似乎最接近的一个选项是利用具有一对多关系的两个表。也就是说,一个 Menu 可以有 0-n 个子菜单。为了建立这种关系,您在子菜单中有一个额外的列,用于唯一标识子菜单所属的菜单。
Menu 和 Submenu 类保持不变。
MenuEntity 是:-
@Entity(tableName = "menus", indices = [Index("name", "type", unique = true)])
data class MenuEntity(
@PrimaryKey
@ColumnInfo(name = "menu_id")
val menuId: Long? = null,
@ColumnInfo(name = "name")
val name: String,
@ColumnInfo(name = "type")
val type: String
)
- 子菜单列表已被删除
- 添加了一个额外的(但不是真的)列
- 因为列定义将是
menu_id INTEGER PRIMARY KEY,所以它是通常隐藏的rowid 列的别名,它存在于所有表中(Room 当前不允许的 WITHOUT ROWID 表除外)。
- 因此添加此列没有开销。但是,rowid 的处理效率更高,最高可达两倍,因此可能会提高效率。
- 额外的列简化了 Menu 和 Submenu 之间的关系,使用的是简单键而不是复合键。
子菜单表的新类SubmenuEntity
@Entity(tableName = "submenus",
/* Optional but useful as referential integrity is enforced */
foreignKeys = [
ForeignKey(
entity = MenuEntity::class,
parentColumns = ["menu_id"],
childColumns = ["menu_id_map"],
/* Optional within A Foreign Key */
onDelete = CASCADE,
onUpdate = CASCADE
)
]
)
data class SubmenuEntity(
@PrimaryKey
@ColumnInfo(name = "submenu_id")
val id: Long? = null,
@Embedded
val submenu: Submenu,
@ColumnInfo(name = "menu_id_map")
val menuMapId: Long
) {
// Bonus function that will get a Submenu from a SubmenuEntity
fun getSubmenuFromSubmenuEntity(submenuEntity: SubmenuEntity): Submenu {
return Submenu(name = submenuEntity.submenu.name, route = submenuEntity.submenu.route)
}
}
要通过关系使用 SubMenus 检索所有菜单,那么您有一个 POJO,它嵌入了带有 @Embedded 注释的父级(菜单),并有一个带有 @Relation 注释的子级列表,例如:-
data class MenuWithSubmenus (
@Embedded
val menuEntity: MenuEntity,
@Relation(
entity = SubmenuEntity::class,
parentColumn = "menu_id",
entityColumn = "menu_id_map")
val submenuList: List<SubmenuEntity>
)
然后你想要访问数据库的方法,这些是 Dao 的,你有一个接口或一个带有 @Dao 注释的抽象类,例如
@Dao
abstract class AllDao {
@Insert
abstract fun insert(menuEntity: MenuEntity): Long
@Insert
abstract fun insert(submenu: SubmenuEntity): Long
@Transaction
@Query("SELECT * FROM menus")
abstract fun getALlMenusWithSubmenus(): List<MenuWithSubmenus>
}
所有这些都通过一个带有@Database 注释的抽象类组合在一起,其中定义了实体并定义了一个抽象函数来获取带有@Dao 注释的类。通常会包含一个方法来满足获取 Database 类的单个实例。所以你可以:-
@Database(entities = [MenuEntity::class, SubmenuEntity::class],version = 1)
abstract class TheDatabase: RoomDatabase() {
abstract fun getAllDao(): AllDao
companion object {
private var instance: TheDatabase? = null
fun getInstance(context: Context) : TheDatabase {
if (instance == null) {
instance = Room.databaseBuilder(
context,
TheDatabase::class.java,
"menu_database.db"
)
.allowMainThreadQueries() // Used for convenience/brevity
.build()
}
return instance as TheDatabase
}
}
}
-
注意 为简洁和方便起见,
.allowMainThreadQueries 已用于允许演示在主线程上运行,因此没有所有额外的代码来处理从主线程运行的所有额外代码。后者推荐用于要分发的应用程序。
最后一个 Activity MainActivity 来演示上述内容。
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();
// Get the JSON and add the Menu's and Submenu's to the database.
Log.d("DBINFO",getTheJsonInputString()) // Write the JSON to the log
val menuList = Gson().fromJson(getTheJsonInputString(), Array<Menu>::class.java)
for (m: Menu in menuList) {
var menuId = dao.insert(MenuEntity(name = m.name, type = m.type))
for (sm: Submenu in m.submenus) {
dao.insert(SubmenuEntity(submenu = Submenu(sm.name,sm.route),menuMapId = menuId))
}
}
// extract the data from the database as MenuWithSubmenus objects
for (mws: MenuWithSubmenus in dao.getALlMenusWithSubmenus()) {
Log.d("DBINFO"," Menu is ${mws.menuEntity.name} Type is ${mws.menuEntity.type} ID is ${mws.menuEntity.menuId}")
for(sm: SubmenuEntity in mws.submenuList) {
var currentSubmenu = sm.getSubmenuFromSubmenuEntity(sm) // Check the function works
Log.d("DBINFO","\tSubmenu is ${sm.submenu.name} Route is ${sm.submenu.route} ID is ${sm.id} Parent Menu is ${sm.menuMapId}")
Log.d("DBINFO", "\tSubmenu extract from SubmenuEntity is ${currentSubmenu.name} Route is ${currentSubmenu.route}")
}
}
}
/**
* generate some test data
*/
private fun getTheJsonInputString(): String {
val menulist: List<Menu> = listOf(
Menu("menu1","type1",
listOf(
Submenu(name = "SM1", route = "route1"),
Submenu(name = "SM2", route = "route2"),
Submenu(name = "SM3", route = "route3")
)
),
Menu( name = "menu2", type = "type2",
listOf(
Submenu(name = "SM4", route = "route4"),
Submenu(name = "SM5", route = "route5")
)
)
)
return Gson().toJson(menulist)
}
}
上面的设计只运行一次。运行时将以下内容输出到日志:-
D/DBINFO: [{"name":"menu1","submenus":[{"name":"SM1","route":"route1"},{"name":"SM2","route":"route2"},{"name":"SM3","route":"route3"}],"type":"type1"},{"name":"menu2","submenus":[{"name":"SM4","route":"route4"},{"name":"SM5","route":"route5"}],"type":"type2"}]
D/DBINFO: Menu is menu1 Type is type1 ID is 1
D/DBINFO: Submenu is SM1 Route is route1 ID is 1 Parent Menu is 1
D/DBINFO: Submenu extract from SubmenuEntity is SM1 Route is route1
D/DBINFO: Submenu is SM2 Route is route2 ID is 2 Parent Menu is 1
D/DBINFO: Submenu extract from SubmenuEntity is SM2 Route is route2
D/DBINFO: Submenu is SM3 Route is route3 ID is 3 Parent Menu is 1
D/DBINFO: Submenu extract from SubmenuEntity is SM3 Route is route3
D/DBINFO: Menu is menu2 Type is type2 ID is 2
D/DBINFO: Submenu is SM4 Route is route4 ID is 4 Parent Menu is 2
D/DBINFO: Submenu extract from SubmenuEntity is SM4 Route is route4
D/DBINFO: Submenu is SM5 Route is route5 ID is 5 Parent Menu is 2
D/DBINFO: Submenu extract from SubmenuEntity is SM5 Route is route5
菜单表,通过 App Inspection aka Database Inspector 显示:-
和子菜单表:-