Android 具有复杂对象的工作室类型转换器
Android Studio Type Converters with complex objects
在尝试使用 Room 向我的 AndroidStudio 应用添加持久性时,我遇到了这个问题:
error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
private User creator;
我已经尝试过所说的类型转换器,看起来就是这样
class Converters {
@TypeConverter
fun usuarioToString(user: User): String {
return user.getID().toString()
}
@TypeConverter
fun stringToUser(value: String?): UUID {
return UUID.fromString(value)
}
}
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract val appDAO: AppDAO
...
}
@Entity(tableName = "element_table")
abstract class Element (
private var IDClass: Int,
@TypeConverters(Converters::class)
private var creator: User
)
我想在 类 中维护复杂的对象,但我不明白为什么类型转换器不能正常工作。
PD:我已经制作了用户和元素 类
您有很多问题。
第 1 期 - private var ....
与 var ....
通常,在数据 Class 中只使用 var ....
。但是,如果创建 private
那么对于 room 你需要有 getter 和 setter 以便能够访问成员变量。
所以你可以使用 :-
@Entity(tableName = "element_table")
abstract class Element (
@PrimaryKey
private var IDClass: Int,
//@TypeConverters(Converters::class) // not needed here as defined at the @Database level
private var creator: User
) {
fun getIDClass(): Int {
return IDClass
}
fun setIDClass(id: Int) {
this.IDClass = id
}
fun getCreator(): User {
return creator
}
fun setCreator(user: User) {
this.creator = user
}
}
或者(在这种情况下不需要 getter 和 setter,因为值可以直接访问):-
@Entity(tableName = "element_table")
abstract class Element (
@PrimaryKey
var IDClass: Int,
//@TypeConverters(Converters::class) // not needed here as defined at the @Database level
var creator: User
)
TypeConverter 的第二个问题是您需要匹配对。也就是一个从Object转换,另一个转换成Object。
前者,fromObject必须获取Object并将其转换为room可以操作的类型:-
- 字符串,
- 整数类型,例如 Long 或 Int,
- 十进制类型(SQLite 术语中的 REAL),例如 Double、Float,
- 字节数组(QLite 术语中的 BLOB)例如 ByteArray
后者,toObject,必须从数据库中获取值(因此是 fromObject 类型转换器的结果类型)并将其转换为 Type。
所以你有:-
@TypeConverter
fun usuarioToString(user: User): String {
return user.getID().toString()
}
但是!!您没有匹配的转换器。必须向匹配的转换器传递一个字符串(fromObject 转换器 usuarioToString
的结果)和 return 一个用户。
因此而不是(returns 一个 UUID 对象):-
@TypeConverter
fun stringToUser(value: String?): UUID {
return UUID.fromString(value)
}
你可以:-
@TypeConverter
fun stringToUser(value: String?): User {
return User(????) /* construct the User based upon the String as an ID */
}
function/converter :-
@TypeConverter
fun stringToUser(value: String?): UUID {
return UUID.fromString(value)
}
将从字符串转换为 UUID 而不是用户,因此您提供的内容没有用。
但是,不需要将 Int 转换为 String 即可存储 Int。 但是 仅仅存储 id 就足够了,如果 User 有名字和/或其他信息,id 从 Int 转换为 String 就足以构建一个实际的 User(因此 User(????)
).
例如,用户 class 是:-
data class User(
val id: Int,
val userName: String
) {
fun getID(): Int {
return id
}
}
那么仅根据 id 生成用户名将很困难,但并非不可能。有两个基本选项可以存储 ID 和用户名(以及其他详细信息)。
您可以使用更复杂的 TypeConverter 将所有成员变量(id 和 userName)转换为字符串,或者您可以为 User 使用单独的 table 并在元素和用户。
在关系数据库术语中(SQLite,因此 Room 是一个关系数据库)后者被认为是更可取的。然而,通常情况是放弃数据库的关系方面并将关系数据存储在单个列中,并且通常 JSON 字符串用于存储此类数据。
将用户存储为 JSON 字符串的示例
首先,您需要为 GSON 添加依赖项,例如:-
implementation 'com.google.code.gson:gson:2.9.0'
那么您的 TypeConverter 可能是:-
class Converters {
@TypeConverter
fun usuarioToString(user: User): String {
return Gson().toJson(user)
//return user.getID().toString()
}
@TypeConverter
fun stringToUsuario(value: String): User {
return Gson().fromJson(value,User::class.java)
//return User(value.toInt(),"blah")
}
}
这将保存所有用户信息,而不仅仅是 ID,例如像 :-
- 可以看出,同一用户已被存储两次,这与规范化的不重复数据方面相矛盾。然而,它很方便(至少一开始是这样),但可能会显得笨拙,尤其是在需要根据用户信息搜索数据的情况下。
示例将用户存储在 table 中并利用元素和用户之间的关系
元素将存储相应用户的 ID,而不是使用字符串表示 JSON,因此元素 class 可以是:-
@Entity(
tableName = "element_table",
)
data class Element (
@PrimaryKey
val IDClass: Int,
@ColumnInfo(index = true) /* optional but negates warning */
val creator: Int
)
由于用户将是 table,用户 class 可能是:-
@Entity(tableName = "user_table")
data class User(
@PrimaryKey
val id: Int,
val userName: String
)
因为你想用用户检索一个元素,所以使用了一个额外的 POJO(即不是带注释的@Entity class),例如:-
data class ElementWithUser (
@Embedded
var element: Element,
@Relation(
entity = User::class,
parentColumn = "creator",
entityColumn = "id"
)
var user: User
)
- 这介绍了@Embedded 和@Relation 注释
@Embedded 基本上复制了嵌入的class
@Relation 用于告诉room如何获取相关item
- 实体是从中获取相关数据的class (table)
- parentColumn 是父 class (@Embedded) 中的列,maps/references 子 (ren) 在另一个 table
- childColumn 是另一个 table 中被引用的列(最常见的是主键)
要使用以上内容,您需要能够:-
- 将用户插入 user_table
- 将元素插入 element_table
- 有提取数据的方法
将其组合成一个带@Dao 注释的接口(或抽象 class):-
@Dao
interface ElementDao {
@Insert
fun insert(element: Element): Long
@Query("SELECT * FROM element_table")
fun getAllElements(): List<Element>
@Insert
fun insert(user: User): Long
@Query("SELECT * FROM user_table")
fun getAllUsers(): List<User>
@Transaction
@Query("SELECT * FROM element_table")
fun getAllElementsWithUser(): List<ElementWithUser>
}
对于 Room,您需要一个带注释的 @Database class 以及实际构建数据库的方法。在这个例子中:-
@Database(entities = [Element::class,User::class], version = 1, exportSchema = false)
//@TypeConverters( value = [DateTypeConverter::class, Converters::class]) //<<<<< not needed
abstract class TheDatabase: RoomDatabase() {
abstract fun getElementDao(): ElementDao
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
}
}
}
- 注意 .alowMainThreadQueries 用于简洁和方便nce
最后显然你需要代码来做任何事情,例如:-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: ElementDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getElementDao()
dao.insert(User(1,"Fred Bloggs"))
dao.insert(User(2,"Jane Doe"))
dao.insert(User(3,"Mary Smith"))
dao.insert(Element(1000,2 /* Jane Doe */))
dao.insert(Element(2000,1 /* Fred Bloggs */))
dao.insert(Element(3000,2 /* Jane Doe also in this Element */))
for (ewu: ElementWithUser in dao.getAllElementsWithUser()) {
Log.d("DBINFO","Element is ${ewu.element.IDClass} User is ${ewu.user.userName}")
}
}
}
结果
日志包括:-
D/DBINFO: Element is 1000 User is Jane Doe
D/DBINFO: Element is 2000 User is Fred Bloggs
D/DBINFO: Element is 3000 User is Jane Doe
数据库,通过App Inspection可以看出是:-
和 user_table :-
与将用户存储为 JSON 的版本相比,您可以看到用户的数据仅存储一次(即使 Jane Doe 被引用了两次)。
在尝试使用 Room 向我的 AndroidStudio 应用添加持久性时,我遇到了这个问题:
error: Cannot figure out how to save this field into database. You can consider adding a type converter for it. private User creator;
我已经尝试过所说的类型转换器,看起来就是这样
class Converters {
@TypeConverter
fun usuarioToString(user: User): String {
return user.getID().toString()
}
@TypeConverter
fun stringToUser(value: String?): UUID {
return UUID.fromString(value)
}
}
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract val appDAO: AppDAO
...
}
@Entity(tableName = "element_table")
abstract class Element (
private var IDClass: Int,
@TypeConverters(Converters::class)
private var creator: User
)
我想在 类 中维护复杂的对象,但我不明白为什么类型转换器不能正常工作。
PD:我已经制作了用户和元素 类
您有很多问题。
第 1 期 - private var ....
与 var ....
通常,在数据 Class 中只使用 var ....
。但是,如果创建 private
那么对于 room 你需要有 getter 和 setter 以便能够访问成员变量。
所以你可以使用 :-
@Entity(tableName = "element_table")
abstract class Element (
@PrimaryKey
private var IDClass: Int,
//@TypeConverters(Converters::class) // not needed here as defined at the @Database level
private var creator: User
) {
fun getIDClass(): Int {
return IDClass
}
fun setIDClass(id: Int) {
this.IDClass = id
}
fun getCreator(): User {
return creator
}
fun setCreator(user: User) {
this.creator = user
}
}
或者(在这种情况下不需要 getter 和 setter,因为值可以直接访问):-
@Entity(tableName = "element_table")
abstract class Element (
@PrimaryKey
var IDClass: Int,
//@TypeConverters(Converters::class) // not needed here as defined at the @Database level
var creator: User
)
TypeConverter 的第二个问题是您需要匹配对。也就是一个从Object转换,另一个转换成Object。
前者,fromObject必须获取Object并将其转换为room可以操作的类型:-
- 字符串,
- 整数类型,例如 Long 或 Int,
- 十进制类型(SQLite 术语中的 REAL),例如 Double、Float,
- 字节数组(QLite 术语中的 BLOB)例如 ByteArray
后者,toObject,必须从数据库中获取值(因此是 fromObject 类型转换器的结果类型)并将其转换为 Type。
所以你有:-
@TypeConverter
fun usuarioToString(user: User): String {
return user.getID().toString()
}
但是!!您没有匹配的转换器。必须向匹配的转换器传递一个字符串(fromObject 转换器 usuarioToString
的结果)和 return 一个用户。
因此而不是(returns 一个 UUID 对象):-
@TypeConverter
fun stringToUser(value: String?): UUID {
return UUID.fromString(value)
}
你可以:-
@TypeConverter
fun stringToUser(value: String?): User {
return User(????) /* construct the User based upon the String as an ID */
}
function/converter :-
@TypeConverter
fun stringToUser(value: String?): UUID {
return UUID.fromString(value)
}
将从字符串转换为 UUID 而不是用户,因此您提供的内容没有用。
但是,不需要将 Int 转换为 String 即可存储 Int。 但是 仅仅存储 id 就足够了,如果 User 有名字和/或其他信息,id 从 Int 转换为 String 就足以构建一个实际的 User(因此 User(????)
).
例如,用户 class 是:-
data class User(
val id: Int,
val userName: String
) {
fun getID(): Int {
return id
}
}
那么仅根据 id 生成用户名将很困难,但并非不可能。有两个基本选项可以存储 ID 和用户名(以及其他详细信息)。
您可以使用更复杂的 TypeConverter 将所有成员变量(id 和 userName)转换为字符串,或者您可以为 User 使用单独的 table 并在元素和用户。
在关系数据库术语中(SQLite,因此 Room 是一个关系数据库)后者被认为是更可取的。然而,通常情况是放弃数据库的关系方面并将关系数据存储在单个列中,并且通常 JSON 字符串用于存储此类数据。
将用户存储为 JSON 字符串的示例
首先,您需要为 GSON 添加依赖项,例如:-
implementation 'com.google.code.gson:gson:2.9.0'
那么您的 TypeConverter 可能是:-
class Converters {
@TypeConverter
fun usuarioToString(user: User): String {
return Gson().toJson(user)
//return user.getID().toString()
}
@TypeConverter
fun stringToUsuario(value: String): User {
return Gson().fromJson(value,User::class.java)
//return User(value.toInt(),"blah")
}
}
这将保存所有用户信息,而不仅仅是 ID,例如像 :-
- 可以看出,同一用户已被存储两次,这与规范化的不重复数据方面相矛盾。然而,它很方便(至少一开始是这样),但可能会显得笨拙,尤其是在需要根据用户信息搜索数据的情况下。
示例将用户存储在 table 中并利用元素和用户之间的关系
元素将存储相应用户的 ID,而不是使用字符串表示 JSON,因此元素 class 可以是:-
@Entity(
tableName = "element_table",
)
data class Element (
@PrimaryKey
val IDClass: Int,
@ColumnInfo(index = true) /* optional but negates warning */
val creator: Int
)
由于用户将是 table,用户 class 可能是:-
@Entity(tableName = "user_table")
data class User(
@PrimaryKey
val id: Int,
val userName: String
)
因为你想用用户检索一个元素,所以使用了一个额外的 POJO(即不是带注释的@Entity class),例如:-
data class ElementWithUser (
@Embedded
var element: Element,
@Relation(
entity = User::class,
parentColumn = "creator",
entityColumn = "id"
)
var user: User
)
- 这介绍了@Embedded 和@Relation 注释
@Embedded 基本上复制了嵌入的class
@Relation 用于告诉room如何获取相关item
- 实体是从中获取相关数据的class (table)
- parentColumn 是父 class (@Embedded) 中的列,maps/references 子 (ren) 在另一个 table
- childColumn 是另一个 table 中被引用的列(最常见的是主键)
要使用以上内容,您需要能够:-
- 将用户插入 user_table
- 将元素插入 element_table
- 有提取数据的方法
将其组合成一个带@Dao 注释的接口(或抽象 class):-
@Dao
interface ElementDao {
@Insert
fun insert(element: Element): Long
@Query("SELECT * FROM element_table")
fun getAllElements(): List<Element>
@Insert
fun insert(user: User): Long
@Query("SELECT * FROM user_table")
fun getAllUsers(): List<User>
@Transaction
@Query("SELECT * FROM element_table")
fun getAllElementsWithUser(): List<ElementWithUser>
}
对于 Room,您需要一个带注释的 @Database class 以及实际构建数据库的方法。在这个例子中:-
@Database(entities = [Element::class,User::class], version = 1, exportSchema = false)
//@TypeConverters( value = [DateTypeConverter::class, Converters::class]) //<<<<< not needed
abstract class TheDatabase: RoomDatabase() {
abstract fun getElementDao(): ElementDao
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
}
}
}
- 注意 .alowMainThreadQueries 用于简洁和方便nce
最后显然你需要代码来做任何事情,例如:-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: ElementDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getElementDao()
dao.insert(User(1,"Fred Bloggs"))
dao.insert(User(2,"Jane Doe"))
dao.insert(User(3,"Mary Smith"))
dao.insert(Element(1000,2 /* Jane Doe */))
dao.insert(Element(2000,1 /* Fred Bloggs */))
dao.insert(Element(3000,2 /* Jane Doe also in this Element */))
for (ewu: ElementWithUser in dao.getAllElementsWithUser()) {
Log.d("DBINFO","Element is ${ewu.element.IDClass} User is ${ewu.user.userName}")
}
}
}
结果
日志包括:-
D/DBINFO: Element is 1000 User is Jane Doe
D/DBINFO: Element is 2000 User is Fred Bloggs
D/DBINFO: Element is 3000 User is Jane Doe
数据库,通过App Inspection可以看出是:-
和 user_table :-
与将用户存储为 JSON 的版本相比,您可以看到用户的数据仅存储一次(即使 Jane Doe 被引用了两次)。