【问题标题】:Handle the json to Room Database Entity处理 json 到房间数据库实体
【发布时间】: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文件->插入数据库->从数据库中读取

想请教一下实体的制作方法。

【问题讨论】:

    标签: android json android-room


    【解决方案1】:

    您的问题是您不能将列表/数组类型作为列。列必须是一组有限的类型 String、Integer、Long、Double、Float、ByteArray 是最常见的可接受类型。

    所以

    @Embedded
    val submenus: List<Submenu>
    

    不起作用,我相信这是您收到错误的原因(Room 没有如何处理列表)。

    你可以走两条路:-

    1. 利用关系,从而为子菜单列表使用第二个表(实体)。
    2. 创建和存储子菜单的表示形式,可能是字符串 (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 时,您基本上拥有它,所以有一个 List。但是,如果 Menu 有一个父级,例如 Window,那么您可以拥有 WindowWithMenuWithSubmenus,其中 Window 是 @Embedded 并且 List@Relation,然后通过层次结构获取 @Relation 子菜单。说不确定您要达到的目标。也许再问一个问题。
    • 想想你说的话,你是对的。我误解了。谢谢你的回答。
    【解决方案2】:

    避免此错误的最简单方法是为您的实体属性添加默认值。

    data class Menu(
        val name: String = "",
        val type: String = "",
        val submenus: List<Submenu> = listOf<Submenu>()
    )
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多