处理 json 到房间数据库实体
Posted
技术标签:
【中文标题】处理 json 到房间数据库实体【英文标题】:Handle the json to Room Database Entity 【发布时间】:2021-12-18 09:05:38 【问题描述】:[
"name" : "phase1",
"type" : "purchase",
"submenu": [
"name": "insert",
"route": "insert"
,
"name": "send",
"route": "send"
,
"name": "delete",
"route": "delete"
]
,
"name" : "phase2",
"type" : "refund",
"submenu": [
"name": "insert",
"route": "insert"
,
"name": "send",
"route": "send"
,
"name": "delete",
"route": "delete"
]
]
我将读取带有以下组件的 json 并将其放入 Room Database。所以我尝试创建并插入 MenuEntity 和 SubMenu,
菜单实体
@Entity(tableName = "menus", primaryKeys = ["name", "type"])
data class MenuEntity(
@ColumnInfo(name = "name")
val name: String,
@ColumnInfo(name = "type")
val type: String,
@Embedded
val submenus: List<Submenu>,
)
子菜单
data class Submenu(
@ColumnInfo(name = "submenu_name")
val name: String,
@ColumnInfo(name = "route")
val route: String
)
菜单
data class Menu(
val name: String,
val type: String,
val submenus: List<Submenu>,
)
但出现以下错误。
Entities and POJOs must have a usable public constructor. You can have an empty constructor or a constructor whose parameters match the fields (by name and type). - java.util.List
我想要 读取json文件->插入数据库->从数据库中读取
想请教一下实体的制作方法。
【问题讨论】:
【参考方案1】:您的问题是您不能将列表/数组类型作为列。列必须是一组有限的类型 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)
请注意,Submenu 类已嵌入
这基本上是一个带有 2 个附加列的子菜单
submenu_id 列,如前所述,它是 rowid 的别名,并且 用于存储父菜单的 menu_id 的列,即关系为了完整起见,添加了外键约束。这减少了子菜单没有父项并因此成为孤儿的机会。根据 cmets,不需要约束。
要通过关系使用 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 显示:-
和子菜单表:-
【讨论】:
感谢您的友好评论和示例。多亏了这一点,我对问题以及如何进行有了清晰的认识。再次感谢您。 我能再问你一个问题吗?如何将 MenuWithSubmenus 更改为 List @PolarisNation 从房间的角度来看,你不能直接。请注意,当您检索 List避免此错误的最简单方法是为您的实体属性添加默认值。
data class Menu(
val name: String = "",
val type: String = "",
val submenus: List<Submenu> = listOf<Submenu>()
)
【讨论】:
以上是关于处理 json 到房间数据库实体的主要内容,如果未能解决你的问题,请参考以下文章
将 PostgreSQL JSON 列映射到 Hibernate 实体属性