Android Room Database:如何处理实体中的 Arraylist?
Posted
技术标签:
【中文标题】Android Room Database:如何处理实体中的 Arraylist?【英文标题】:Android Room Database: How to handle Arraylist in an Entity? 【发布时间】:2017-12-12 16:30:00 【问题描述】:我刚刚实现了 Room 用于离线数据保存。但是在实体类中,我收到以下错误:
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;
所以基本上我想将 ArrayList 保存在数据库中,但我找不到与它相关的任何内容。你能指导我如何使用 Room 保存数组吗?
注意:MyListItems Pojo 类包含 2 个字符串(截至目前)
提前致谢。
【问题讨论】:
【参考方案1】:Type Converter 是专门为此制作的。在您的情况下,您可以使用下面给出的代码 sn-p 将数据存储在 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;
并像这样在你的 Room DB 中提及这个类
@Database (entities = MainActivityData.class,version = 1)
@TypeConverters(Converters.class)
更多信息here
【讨论】:
谁能帮我在 Kotlin 中用 List 做同样的事情。在 Java 中它运行良好。但是当我在 Kolin 中转换它时它不起作用 如何从该数组列表中查询? @SanjogShrestha 我不明白你的意思。您只需使用 get 方法检索数组列表和查询 @AmitBhandari 让我们以上述场景为例。我想搜索 myListItems 包含的表(MainActivityData)(例如 a、b、c )并且 userId 是 abc。现在我们如何为这种情况编写查询? @bompf 感谢您的建议。虽然这里的这个例子只是说明。通常,我们总是在应用程序级别保留一个 gson 实例。【参考方案2】:选项#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
。
【讨论】:
@TusharGogna:the Room documentation 涵盖了关系,the Room documentation 也涵盖了“实体不直接引用其他实体”位。 @CommonsWare 是Relation
这种情况下的选项#3? Relation documentation
@FeleMed:不是真的。 @Relation
仅用于从数据库中取出内容。它与将东西放入数据库无关。
就像一个注释。例如,如果要保留 Int 列表,则需要将其序列化为选项 2 的字符串。这会使查询更加复杂。我宁愿选择选项 1,因为它对“类型”的依赖性较小。
将来您可能需要查询您的项目,所以我通常会选择选项#1【参考方案3】:
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,不如使用这样的 List 类型: val listType = object : TypeToken>() .type就像下面答案中提到的阿米特一样。 另外,您可能希望从应用中的某处获取缓存的Gson
实例。在每次调用时初始化新的 Gson
实例可能会很昂贵。【参考方案4】:
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 = ",")
【讨论】:
小心使用“,”作为分隔符,因为有时您的字符串可能具有相同的字符,并且可能会很混乱。【参考方案5】:原生 Kotlin 版本使用 Kotlin 的序列化组件 - kotlinx.serialization。
-
将 Kotlin 序列化 Gradle 插件和依赖项添加到您的
build.gradle
:
apply plugin: 'kotlinx-serialization'
dependencies
...
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1"
-
将类型转换器添加到您的 Converter 类中;
class Converters
@TypeConverter
fun fromList(value : List<String>) = Json.encodeToString(value)
@TypeConverter
fun toList(value: String) = Json.decodeFromString<List<String>>(value)
-
将您的 Converter 类添加到您的数据库类中:
@TypeConverters(Converters::class)
abstract class YourDatabase: RoomDatabase() ...
你就完成了!
额外资源:
Type Converters Kotlin Serialization【讨论】:
如果自动导入不起作用添加:import kotlinx.serialization.json.Json import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString 你可能还需要classpath("org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion")
【参考方案6】:
这就是我处理列表转换的方式
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
【讨论】:
我将此解决方案用于简单类型(例如 List我个人建议不要使用@TypeConverters
/serializations,因为它们破坏了数据库的正常表单合规性。
对于这种特殊情况,defining a relationship 可能值得使用 @Relation 注释,它允许将嵌套实体查询到单个对象中,而不会增加声明 @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 关系。
【讨论】:
这里唯一理智的答案!不要违反第一范式! 我喜欢这种使用 1 对 N 关系的解决方案。但是有一个问题,如果你有一个包含一些数据的 JSON 文件并且你想将它存储在你的数据库中,与使用 Gson 的 ArrayList 方法不同,你可以轻松地创建保存数据的对象的实例,你如何处理这些数据结构? @EmmanuelMurairi 恐怕你不能。为了在运行时实例化任意对象,Gson 使用 reflection - 你也可以使用它 - 但是,由于 Room 在关系数据库 (SQLite) 之上工作,它将数据构造成具有预定义列的表,所以你需要了解数据的结构并提前声明实体类。当您使用 Gson 时,您只是将一个巨大的字符串转储到一个列中,并在每次阅读时在运行时解析它。这是一个很好的解决方法,但我会尽量避免它。 有时你应该,有时你不应该,取决于你是否需要操纵它+在查询中使用它 @EpicPandaForce 当然,反规范化有时可以带来更好的性能,许多分布式系统都利用了它。但是,应该记住,应用程序的要求可能会随着新版本的变化而变化(重新规范化非规范化的模式可能会很痛苦),并且类型转换本身就是一种操作,当不是绝对必要的。仅当您知道自己在做什么时才去规范化。【参考方案8】:Kotlin 答案
你需要做三件事:
-
创建转换器类。
在数据库上添加转换器类。
只需定义要在 Entity 类中使用的内容。
逐步使用示例:
第 1 步:
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()
第 2 步:
@Database(entities = [YourEntity::class], version = 1)
@TypeConverters(Converters::class)
abstract class YourDatabase : RoomDatabase()
abstract fun yourDao(): YourDao
第 3 步:
注意:您确实不需要需要调用 Converter 的 listToJsonString() 和 jsonStringToList() 函数。他们正在由 Room 在后台使用。
@Entity(tableName = "example_database_table")
data class YourEntity(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
@ColumnInfo(name = "your_model_list") var yourModelList: List<YourModel>,
)
【讨论】:
这对我有用。谢谢@Caner【参考方案9】:出现与上述相同的错误消息。 我想补充一点:如果您在@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();
【讨论】:
我尝试在 Query 注释上方添加 @TypeConverters,但我仍然遇到同样的错误【参考方案10】:此答案使用 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
【讨论】:
【参考方案11】:在我的情况下,问题是泛型类型 基于这个答案
https://***.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)
在dao类和Entity类中查询不需要添加@TypeConverters(IntArrayListConverter::class) 只需将 @TypeConverters(IntArrayListConverter::class) 添加到数据库类
@Database(entities = [MyEntity::class], version = 1, exportSchema = false)
@TypeConverters(IntArrayListConverter::class)
abstract class MyDatabase : RoomDatabase()
【讨论】:
【参考方案12】:当我们使用 TypaConverters 时,Datatype 应该是 TypeConverter 方法的返回类型 .示例 TypeConverter 方法返回字符串然后添加表 COloum 应该是字符串
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;");
;
【讨论】:
【参考方案13】:以上所有答案都是针对字符串列表的。但下面帮助您编写对象列表的转换器。
在“YourClassName”处添加您的 Object 类。
@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);
【讨论】:
【参考方案14】:以转换器类作为参数添加@TypeConverters
到数据库和 Dao 类,使我的查询工作
【讨论】:
你能详细说明你的答案吗??【参考方案15】: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提供Embedded
【讨论】:
【参考方案16】:这是将 customObject 类型添加到 Room DB 表的示例。 https://mobikul.com/insert-custom-list-and-get-that-list-in-room-database-using-typeconverter/
添加一个类型转换器很简单,我只需要一个可以将对象列表转换为字符串的方法,以及一个可以反过来的方法。我为此使用了 gson。
public class Converters
@TypeConverter
public static String MyListItemListToString(List<MyListitem> list)
Gson gson = new Gson();
return gson.toJson(list);
@TypeConverter
public static List<Integer> stringToMyListItemList(@Nullable String data)
if (data == null)
return Collections.emptyList();
Type listType = new TypeToken<List<MyListItem>>() .getType();
Gson gson = new Gson();
return gson.fromJson(data, listType);
然后我在实体中的字段中添加了一个注释:
@TypeConverters(Converters.class)
public final ArrayList<MyListItem> myListItems;
【讨论】:
【参考方案17】: @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());
【讨论】:
【参考方案18】:以上所有答案都正确。是的,如果你真的需要将一些东西存储到一个 SQLite 字段中,TypeConverter 是一种解决方案。
我在我的项目中使用了公认的答案。
但不要这样做!!!
如果您在 90% 的情况下需要在 Entity 中存储数组,则需要创建一对多或多对多关系。
否则,您在此数组中选择带有键的内容的下一个 SQL 查询将绝对是地狱......
例子:
对象 foo 以 json 形式出现:[id: 1, name: "abs", id:2, name: "cde"
对象栏:[id, 1, foos: [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)
并将您的 foos:[] 作为此表中的记录。
【讨论】:
不要假设 yopu 存储的 ID 列表在第一次 api 调用中可用但在下一次调用中不可用,那么一定要将这些 id 存储在某处,然后使用它们来查询 api 存储将其放入带有联结表的表中,这使用了两种解决方案,我同意您的观点,这可以被视为一种简单的出路,并且出于很多原因并不是很好【参考方案19】:使用房间的官方解决方案,@Embedded 注解:
@Embedded(prefix = "mylist_array") private ArrayList<MyListItems> myListItems
【讨论】:
以上是关于Android Room Database:如何处理实体中的 Arraylist?的主要内容,如果未能解决你的问题,请参考以下文章
Android Room Database:如何处理实体中的 Arraylist?
获取 android 房间数据库 java.lang.IllegalArgumentException 的以下异常:@androidx.room.Database 未定义元素视图()
Android Room Database,检索输入的最新记录的特定值