Android Room 数据库: 如何在实体中处理数组列表?

我刚刚为离线数据保存实现了 Room,但是在 Entity 类中,我得到了以下错误:

Error:(27, 30) error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.

课程如下:

@Entity(tableName = "firstPageData")
public class MainActivityData {


@PrimaryKey
private String userId;


@ColumnInfo(name = "item1_id")
private String itemOneId;


@ColumnInfo(name = "item2_id")
private String itemTwoId;


// THIS IS CAUSING THE ERROR... BASICALLY IT ISN'T READING ARRAYS
@ColumnInfo(name = "mylist_array")
private ArrayList<MyListItems> myListItems;


public String getUserId() {
return userId;
}


public void setUserId(String userId) {
this.userId = userId;
}


public ArrayList<MyListItems> getMyListItems() {
return myListItems;
}


public void setCheckListItems(ArrayList<MyListItems> myListItems) {
this.myListItems = myListItems;
}


}

所以基本上我想保存数组列表在数据库中,但我无法找到任何相关的东西。你能指导我如何使用房间来保存一个数组吗?

注意: MyListItems Pojo 类包含2个字符串(截至目前)

先谢谢你。

131798 次浏览

选项1: 让 MyListItems成为 @Entity,就像 MainActivityData一样。MyListItems会建立一个 @ForeignKey回到 MainActivityData。但是在这种情况下,MainActivityData不能有 private ArrayList<MyListItems> myListItems,因为在 Room 中,实体不引用其他实体。但是,视图模型或类似的 POJO 构造可以有一个 MainActivityData及其相关的 ArrayList<MyListItems>

选项 # 2: 设置一对 @TypeConverter方法来将 ArrayList<MyListItems>与一些基本类型进行转换(例如,String,比如使用 JSON 作为存储格式)。现在,MainActivityData可以直接拥有它的 ArrayList<MyListItems>。但是,对于 MyListItems没有单独的表,因此不能很好地查询 MyListItems

类型转换器 是专门为此设计的。在您的情况下,您可以使用下面给出的代码片段在 DB 中存储数据。

public class Converters {
@TypeConverter
public static ArrayList<String> fromString(String value) {
Type listType = new TypeToken<ArrayList<String>>() {}.getType();
return new Gson().fromJson(value, listType);
}


@TypeConverter
public static String fromArrayList(ArrayList<String> list) {
Gson gson = new Gson();
String json = gson.toJson(list);
return json;
}
}

像这样在你的数据库里提到这个课程

@Database (entities = {MainActivityData.class},version = 1)
@TypeConverters({Converters.class})

更多信息 给你

具有与上述相同的错误消息。 我想添加: 如果在@Query 中得到这个错误消息,应该在@Query 注释上添加@TypeConverters。

例如:

@TypeConverters(DateConverter.class)
@Query("update myTable set myDate=:myDate  where id = :myId")
void updateStats(int myId, Date myDate);

....

public class DateConverter {


@TypeConverter
public static Date toDate(Long timestamp) {
return timestamp == null ? null : new Date(timestamp);
}


@TypeConverter
public static Long toTimestamp(Date date) {
return date == null ? null : date.getTime();
}
}

类型转换器的 Kotlin 版本:

 class Converters {


@TypeConverter
fun listToJson(value: List<JobWorkHistory>?) = Gson().toJson(value)


@TypeConverter
fun jsonToList(value: String) = Gson().fromJson(value, Array<JobWorkHistory>::class.java).toList()
}

我使用 JobWorkHistory对象为我的目的,使用自己的对象

@Database(entities = arrayOf(JobDetailFile::class, JobResponse::class), version = 1)
@TypeConverters(Converters::class)
abstract class MyRoomDataBase : RoomDatabase() {
abstract fun attachmentsDao(): AttachmentsDao
}

这就是我处理 List 转换的方法

public class GenreConverter {
@TypeConverter
public List<Integer> gettingListFromString(String genreIds) {
List<Integer> list = new ArrayList<>();


String[] array = genreIds.split(",");


for (String s : array) {
if (!s.isEmpty()) {
list.add(Integer.parseInt(s));
}
}
return list;
}


@TypeConverter
public String writingStringFromList(List<Integer> list) {
String genreIds = "";
for (int i : list) {
genreIds += "," + i;
}
return genreIds;
}}

然后在数据库上我做如下所示

@Database(entities = {MovieEntry.class}, version = 1)
@TypeConverters(GenreConverter.class)

下面是一个同样的 kotlin 实现;

class GenreConverter {
@TypeConverter
fun gettingListFromString(genreIds: String): List<Int> {
val list = mutableListOf<Int>()


val array = genreIds.split(",".toRegex()).dropLastWhile {
it.isEmpty()
}.toTypedArray()


for (s in array) {
if (s.isNotEmpty()) {
list.add(s.toInt())
}
}
return list
}


@TypeConverter
fun writingStringFromList(list: List<Int>): String {
var genreIds=""
for (i in list) genreIds += ",$i"
return genreIds
}}

使用房间里的官方解决方案,@嵌入式注释:

@Embedded(prefix = "mylist_array") private ArrayList<MyListItems> myListItems

使用转换器类作为参数添加 @TypeConverters

到数据库 & 到刀类,使我的查询工作

这个答案使用 Kotin 以逗号分割并构造逗号划分的字符串。除了最后一个元素之外,逗号必须放在所有元素的末尾,因此这也将处理单个元素列表。

object StringListConverter {
@TypeConverter
@JvmStatic
fun toList(strings: String): List<String> {
val list = mutableListOf<String>()
val array = strings.split(",")
for (s in array) {
list.add(s)
}
return list
}


@TypeConverter
@JvmStatic
fun toString(strings: List<String>): String {
var result = ""
strings.forEachIndexed { index, element ->
result += element
if(index != (strings.size-1)){
result += ","
}
}
return result
}
}

就内存分配而言,Json 转换不能很好地伸缩。我宁愿使用类似于上面的带有一些空性的响应。

class Converters {
@TypeConverter
fun stringAsStringList(strings: String?): List<String> {
val list = mutableListOf<String>()
strings
?.split(",")
?.forEach {
list.add(it)
}


return list
}


@TypeConverter
fun stringListAsString(strings: List<String>?): String {
var result = ""
strings?.forEach { element ->
result += "$element,"
}
return result.removeSuffix(",")
}
}

对于简单数据类型,可以使用上述方法,对于复杂数据类型,Room 提供 嵌入式

List<String>转换器的更好版本

class StringListConverter {
@TypeConverter
fun fromString(stringListString: String): List<String> {
return stringListString.split(",").map { it }
}


@TypeConverter
fun toString(stringList: List<String>): String {
return stringList.joinToString(separator = ",")
}
}

在我的情况下,问题是泛型类型 基于这个答案

Https://stackoverflow.com/a/48480257/3675925 使用 List 而不是 ArrayList

 import androidx.room.TypeConverter
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
class IntArrayListConverter {
@TypeConverter
fun fromString(value: String): List<Int> {
val type = object: TypeToken<List<Int>>() {}.type
return Gson().fromJson(value, type)
}


@TypeConverter
fun fromArrayList(list: List<Int>): String {
val type = object: TypeToken<List<Int>>() {}.type
return Gson().toJson(list, type)
}
}

它不需要添加@TypeConverters (IntArrayListConverter: : class)来在 Dao 类中进行查询,也不需要在 Entity 类中添加字段 并将@TypeConverters (IntArrayListConverter: : class)添加到数据库类中

@Database(entities = [MyEntity::class], version = 1, exportSchema = false)
@TypeConverters(IntArrayListConverter::class)
abstract class MyDatabase : RoomDatabase() {

当我们使用 TypeConverters 时,数据类型应该是 TypeConverter 方法的返回类型。

例如: TypeConverter 方法返回一个字符串,然后添加表列应该是 string 类型的。

private static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
// Since we didn't alter the table, there's nothing else to do here.
database.execSQL("ALTER TABLE "+  Collection.TABLE_STATUS  + " ADD COLUMN deviceType TEXT;");
database.execSQL("ALTER TABLE "+  Collection.TABLE_STATUS  + " ADD COLUMN inboxType TEXT;");
}
};
 @Query("SELECT * FROM business_table")
abstract List<DatabaseModels.Business> getBusinessInternal();




@Transaction @Query("SELECT * FROM business_table")
public ArrayList<DatabaseModels.Business> getBusiness(){
return new ArrayList<>(getBusinessInternal());
}

以上所有答案都是字符串列表,但下面的答案可以帮助您编写对象列表的转换器。

只是在“ 你的班名”的地方,添加您的对象类。

 @TypeConverter
public String fromValuesToList(ArrayList<**YourClassName**> value) {
if (value== null) {
return (null);
}
Gson gson = new Gson();
Type type = new TypeToken<ArrayList<**YourClassName**>>() {}.getType();
return gson.toJson(value, type);
}
    

@TypeConverter
public ArrayList<**YourClassName**> toOptionValuesList(String value) {
if (value== null) {
return (null);
}
Gson gson = new Gson();
Type type = new TypeToken<List<**YourClassName**>>() {
}.getType();
return gson.fromJson(value, type);
}

以上答案全部正确。是的,如果您真的需要存储数组的东西到一个 SQLite 字段 TypeConverter 是一个解决方案。

我在我的项目中使用了公认的答案。

但是不要这么做! ! !

如果在90% 的情况下需要在 Entity 中存储数组,则需要创建一对多或多对多关系。

否则,您的下一个 SQL 查询将选择此数组中包含键的内容,这将是绝对糟糕的..。

例如:

对象 foo 来自 json: [{ id: 1,name: “ abs”} ,{ id: 2,name: “ cde”}

对象栏: [{ id,1,fos: [1,2] ,{ ... }]

所以不要创建这样的实体:

@Entity....
data class bar(
...
val foos: ArrayList<Int>)

下一个:

@Entity(tablename="bar_foo", primaryKeys=["fooId", "barId"])
data class barFoo(val barId: Int, val fooId: Int)

使你们的脚酸痛,如同这桌上的记录。

我个人建议不要使用 @TypeConverters/序列化,因为它们会破坏数据库的正常表单遵从性。

对于这种特殊情况,使用 @ 关系注释值得 定义一段关系使用,它允许将嵌套实体查询到单个对象中,而不需要声明 @ForeignKey和手动编写所有 SQL 查询:

@Entity
public class MainActivityData {
@PrimaryKey
private String userId;
private String itemOneId;
private String itemTwoId;
}


@Entity
public class MyListItem {
@PrimaryKey
public int id;
public String ownerUserId;
public String text;
}


/* This is the class we use to define our relationship,
which will also be used to return our query results.
Note that it is not defined as an @Entity */
public class DataWithItems {
@Embedded public MainActivityData data;
@Relation(
parentColumn = "userId"
entityColumn = "ownerUserId"
)
public List<MyListItem> myListItems;
}


/* This is the DAO interface where we define the queries.
Even though it looks like a single SELECT, Room performs
two, therefore the @Transaction annotation is required */
@Dao
public interface ListItemsDao {
@Transaction
@Query("SELECT * FROM MainActivityData")
public List<DataWithItems> getAllData();
}

除了这个1-N 示例之外,还可以定义1-1和 N-M 关系。

使用 Kotlin 的序列化组件 Kotlinx 序列化的 NativeKotlin 版本

  1. build.gradle中添加 Kotlin 序列化 Gradle 插件和依赖项:
apply plugin: 'kotlinx-serialization'


dependencies {
...
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1"
}
  1. 将类型转换器添加到您的转换器类;
class Converters {
@TypeConverter
fun fromList(value : List<String>) = Json.encodeToString(value)


@TypeConverter
fun toList(value: String) = Json.decodeFromString<List<String>>(value)
}
  1. 将 Converter 类添加到数据库类:
@TypeConverters(Converters::class)
abstract class YourDatabase: RoomDatabase() {...}

你完蛋了!

额外资源:

Kotlin 答案

你需要做三件事:

  1. 创建转换器类。
  2. 在数据库上添加转换器类。
  3. 只需定义要在 Entity 类中使用的内容。

用法示例:

第一步:

 class Converters {


@TypeConverter
fun listToJsonString(value: List<YourModel>?): String = Gson().toJson(value)


@TypeConverter
fun jsonStringToList(value: String) = Gson().fromJson(value, Array<YourModel>::class.java).toList()
}

第二步:

@Database(entities = [YourEntity::class], version = 1)
@TypeConverters(Converters::class)
abstract class YourDatabase : RoomDatabase() {
abstract fun yourDao(): YourDao
}

第三步:

注意: 你做 没有需要调用转换器的函数是 < strong > listToJsonString () < strong > jsonStringToList () 。他们正在使用的背景由 房间

@Entity(tableName = "example_database_table")
data class YourEntity(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
@ColumnInfo(name = "your_model_list") var yourModelList: List<YourModel>,
)

我想在 ROOM 数据库中存储一个包含照片 URI 的 List。由于特殊的字符,我将这个错误本地化在转换器类 TypeConverters.class 中:

com.google.gson.stream.MalformedJsonException: Unterminated array at line 1 column 8 path $[1]

在我用于 arrayList of String 的简单转换器中存在一个问题,实际上是:

public static List<String> fromString(String value) {
Type listType = new TypeToken<ArrayList<String>>() {
}.getType();
return new Gson().fromJson(value, listType);
}


@TypeConverter
public static String fromArrayList(List<String> list) {
Gson gson = new Gson();
return gson.toJson(list);
}

通过把@Derrick Njeru 的注释拿回来,我修改了它,因为它可以把一个字符串看作“ https://images.app.goo.gl/jwhkhzhzvwrceqv67" ,像这样:

    @TypeConverter
public List<String> gettingListFromString(String genreIds) {
List<String> list = new ArrayList<>();


String[] array = genreIds.split(",");
    

for (String s : array) {
if (!s.isEmpty()) {
list.add(s);
}
}
return list;
}


@TypeConverter
public String writingStringFromList(List<String> list) {
String genreIds = "";
for (String i : list) {
genreIds += "," + i;
}
return genreIds;
}