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 将设置一个@ForeignKeyMainActivityData。但是,在这种情况下,MainActivityData 不能有 private ArrayList&lt;MyListItems&gt; myListItems,就像在 Room 中一样,实体不引用其他实体。不过,视图模型或类似的 POJO 构造可能具有 MainActivityData 及其关联的 ArrayList&lt;MyListItems&gt;

选项#2:设置一对@TypeConverter 方法将ArrayList&lt;MyListItems&gt; 转换为某种基本类型(例如String,例如使用JSON 作为存储格式)。现在,MainActivityData 可以直接拥有它的ArrayList&lt;MyListItems&gt;。但是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&lt;String&gt;转换器的更好版本

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、List),因为它比基于 gson 的解决方案更轻。 这个解决方案错过了不愉快的流程(例如null和空String,null List)。 是的,我犯了复制粘贴的错误,并且浪费了至少一个小时来创建带有单个逗号的元素的单个元素列表。我已经提交并回答了这个问题(在 Kotlin 中)【参考方案7】:

我个人建议不要使用@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 学习

Android Room Database:如何处理实体中的 Arraylist?

获取 android 房间数据库 java.lang.IllegalArgumentException 的以下异常:@androidx.room.Database 未定义元素视图()

Android Room Database,检索输入的最新记录的特定值

Android Room Database 执行批量更新的正确语法是啥?

Android Room Database 应用程序崩溃但未显示任何错误