【问题标题】:Android Room Database: How to handle Arraylist in an Entity?Android Room Database:如何处理实体中的 Arraylist?
【发布时间】: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 个字符串(截至目前)

提前致谢。

【问题讨论】:

    标签: java android android-room


    【解决方案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

    1. 将 Kotlin 序列化 Gradle 插件和依赖项添加到您的 build.gradle
    apply plugin: 'kotlinx-serialization'
    
    dependencies {
       ...
       implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1"
    }
    
    1. 将类型转换器添加到您的 Converter 类中;
    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() {...}
    

    你就完成了!

    额外资源:

    【讨论】:

    • 如果自动导入不起作用添加: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 答案

    你需要做三件事:

    1. 创建转换器类。
    2. 在数据库上添加转换器类。
    3. 只需定义要在 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://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)
           } 
      }
      

      在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
                  

                  【讨论】:

                    猜你喜欢
                    • 2019-01-22
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2018-03-04
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多