База данных Android Room: как работать со списком Arraylist в сущности?


90

Я только что реализовал 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 строки (на данный момент)

Заранее спасибо.

Ответы:


82

Вариант № 1: Have 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.


Хорошо, спасибо за быстрый ответ. Сначала я попробую 2-й вариант (1-й вариант не совсем ясен как tbh ..: E) и вернусь к вам.
Tushar Gogna 08

1
ArrayList -> String (с использованием Json) и наоборот работали нормально. Кстати, не могли бы вы подробнее проработать 1-й вариант? Просто хочу знать альтернативу. Спасибо, в любом случае. :)
Тушар Гогна 08

@TusharGogna: Взаимосвязи описаны в документации по комнатам , а бит «сущности не относятся напрямую к другим сущностям» также рассматривается в документации по комнатам .
CommonsWare 08

1
Просто в качестве примечания. Если вы, например, собираетесь сохранить список Int, вам необходимо сериализовать его как строку для варианта 2. Это усложняет запросы. Я бы предпочел вариант 1, поскольку он меньше зависит от типа.
axierjhtjz 06

5
Когда-нибудь в будущем вам может потребоваться запросить ваши предметы, поэтому я обычно
Джеффри

111

Конвертер типов создан специально для этого. В вашем случае вы можете использовать приведенный ниже фрагмент кода для хранения данных в БД.

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

Больше информации здесь


2
Может ли кто-нибудь помочь мне сделать то же самое в Kotlin с помощью List. На Java все работало нормально. Но когда я переоборудовал его в Колине, он не работал
Ozeetee

2
Как вы спрашиваете у этого Arraylist?
Санджог Шреста

@SanjogShrestha Я не понимаю, о чем вы. Вы просто извлекаете arrayylist и запрашиваете, используя метод get
Амит Бхандари

@AmitBhandari Давайте рассмотрим приведенный выше сценарий в качестве примера. Я хочу выполнить поиск в таблице (MainActivityData), где myListItems содержит (например, a, b, c), а userId - abc. Как теперь написать запрос для такого случая?
Санджог Шреста

1
@bompf спасибо за предложение. Хотя этот пример здесь всего лишь иллюстрация. Обычно мы всегда храним один экземпляр gson на уровне приложения.
Амит Бхандари,

55

Версия 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
Я думаю, что вместо десериализации в массив и последующего преобразования в список лучше использовать тип списка вроде этого: val listType = object: TypeToken <List <JobWorkHistory>> () {} .type, как Амит, упомянутый в ответе ниже.
Sohayb Hassoun 01

3
Кроме того, вы можете получить кешированный Gsonэкземпляр откуда-нибудь в своем приложении. Инициализация нового Gsonэкземпляра при каждом вызове может быть дорогостоящей.
Апсалия

18

Лучшая версия 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 07

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)

А ниже - реализация того же котлина;

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, вы должны добавить @TypeConverters над аннотацией @Query.

Пример:

@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
Я попытался добавить @TypeConverters над аннотацией запроса, но все равно получаю ту же ошибку
zulkarnain shah

5

Я бы лично посоветовал не использовать @TypeConverters/ сериализации, так как они нарушают соответствие нормальным формам базы данных.

В этом конкретном случае, возможно, стоит определить отношения с помощью аннотации @Relation , которая позволяет запрашивать вложенные сущности в один объект без дополнительной сложности объявления a @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 и NM.


4

Собственная версия Kotlin, использующая компонент сериализации Kotlin - kotlinx.serialization .

  1. Добавьте плагин Kotlin serialization 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() {...}

И вы сделали!

Дополнительные ресурсы:


3

В этом ответе 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 используйте 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() {

0

Добавление @TypeConvertersс классом преобразователя в качестве параметров

в базу данных и класс Дао, заставил мои запросы работать


1
Вы можете уточнить свой ответ ??
K Pradeep Kumar Reddy

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 предоставляет Embedded


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

Затем я добавил аннотацию к полю в Entity:

@TypeConverters(Converters.class)

public final ArrayList<MyListItem> myListItems;

0

Когда мы используем 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;");
    }
};

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 - это решение.

И я использовал принятый ответ в своих проектах.

Но не делай этого !!!

Если вам нужен массив хранения в Entity, в 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, сохраните его в таблице с таблицей соединений , здесь используются оба решения, я согласен с вами, что это можно рассматривать как простой выход и не очень хорошо по многим причинам
martinseal1987

-2

Используйте официальное решение из комнаты, аннотацию @Embedded:

@Embedded(prefix = "mylist_array") private ArrayList<MyListItems> myListItems
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.