Android Room数据库:如何在实体中处理Arraylist?


84

我刚刚实现了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;
    }

}

因此,基本上我想将ArrayList保存在数据库中,但找不到任何相关的东西。您可以指导我如何使用Room保存阵列吗?

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

提前致谢。

Answers:


78

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

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


好的,谢谢您的快速回答。我将首先尝试第二个选项(尚不完全清楚第一个选项是tbh ..:E),然后再找您。
Tushar Gogna'7

1
ArrayList-> String(使用Json),反之亦然。顺便说一句,您还能详细说明第一种选择吗?只想知道替代方案。不管怎么说,还是要谢谢你。:)
Tushar Gogna

@TusharGogna:关系包含在会议室文档中,“实体不直接引用其他实体”位也包含在会议室文档中
CommonsWare

1
只是一个注释。例如,如果要保留Int列表,则需要将其序列化为选项2的字符串。这会使查询更加复杂。我宁愿选择选项1,因为对“类型”的依赖性较小。
axierjhtjz

5
将来的某个时候,您可能需要查询商品,所以我通常会选择选项#1
Jeffrey

107

类型转换器是专门为此而设计的。在您的情况下,可以使用下面给出的代码片段将数据存储在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})

更多信息在这里


2
谁能帮我在List的Kotlin中做同样的事情。在Java中,它工作正常。但是,当我将其转换为Kolin时,它无法正常工作
-Ozeetee

2
您如何从该数组列表中查询?
Sanjog Shrestha

@SanjogShrestha我不明白你的意思。你只需检索ArrayList和查询使用get方法
阿米特·班达里

@AmitBhandari让我们以上述情况为例。我想搜索myListItems包含(例如a,b,c)且userId为abc的表(MainActivityData)。现在我们如何编写这种情况的查询?
Sanjog Shrestha

1
@bompf感谢您的建议。尽管此示例仅是示例。通常,我们总是在应用程序级别上保留一个gson实例。
阿米特·班达里

51

类型转换器的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
}

2
我认为与其反序列化为数组然后转换为List更好,不如使用这样的List类型:val listType = object:TypeToken <List <JobWorkHistory >>(){} .type如下面的答案中提到的Amit。
Sohayb Hassoun '18

3
另外,您可能想Gson从应用程序中的某处获取缓存的实例。Gson在每个调用上初始化新实例可能会很昂贵。
Apsaliya

14

更好的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 = ",")
    }
}

6
注意不要使用“,”作为分隔符,因为有时您的字符串可能具有相同的字符,并且可能会一团糟。
emarshah

9

这就是我处理列表转换的方式

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 <Integer>,List <Long>),因为它比基于gson的解决方案轻。
朱利安·克罗内格

2
此解决方案错过了不愉快的流程(例如,空字符串和空字符串,空列表)。
朱利安·克罗内格

是的,我犯了复制粘贴错误,并且在创建单个逗号的元素的单个元素列表上损失了至少一个小时。我已经提交并针对此问题做出了答复(在科特林)
丹尼尔·威尔逊,

6

发生与上述相同的错误消息。我想添加:如果在@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();
    }
}

1
我尝试在Query注释上方添加@TypeConverters,但是仍然遇到相同的错误
zulkarnain shah 18-10-18,10

2

此答案使用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
        }
    }

2

就我而言,问题是基于此答案的通用类型

https://stackoverflow.com/a/48480257/3675925 使用列表而不是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() {

2

我个人建议不要使用@TypeConverters/ serialization,因为它们会破坏数据库的常规格式合规性。

对于这种特殊情况,可能值得使用@Relation批注定义一个关系,该批注允许将嵌套的实体查询到单个对象中,而无需增加声明a和手动编写所有SQL查询的复杂性:@ForeignKey

@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和NM关系。



0

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提供嵌入式


0

这是将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;

0

当我们使用TypaConverters时,数据类型应为TypeConverter方法的返回类型。示例TypeConverter方法返回字符串,然后添加表栏应为字符串

 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;");
    }
};

0
 @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());
 }

0

以上所有答案都是针对字符串列表的。但是下面可以帮助您找到用于对象列表的转换器。

代替“ 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);
        }

0

以上所有答案均正确。是的,如果您确实需要将某些东西存储到一个SQLite字段中,TypeConverter是一种解决方案。

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

但是不要这样做!!!

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

否则,您下次在该数组内选择具有键的对象的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是否存储了在第一个api调用中可用但在下一个api调用中不可用的ID列表,则一定要将这些ID存储在某个地方,然后使用它们来查询api,将其存储到带有联结表的表中,这同时使用了两种解决方案,我确实同意您的观点,因为很多原因,这可能被认为是一种简单的解决方法,并且效果
并不理想

0

使用Kotlin的序列化组件– kotlinx.serialization的原生Kotlin版本

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

dependencies {
   ...
   implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1"
}
  1. 将类型转换器添加到您的Converter类;
@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() {...}

大功告成!

额外资源:


-2

使用来自房间的官方解决方案@Embedded注解:

@Embedded(prefix = "mylist_array") private ArrayList<MyListItems> myListItems
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.