База даних Android Room: Як обробляти Arraylist в сутності?


84

Я щойно реалізував Кімната для збереження даних у режимі офлайн. Але в класі 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 у базі даних, але мені не вдалося знайти нічого, що стосується цього. Чи можете ви порадити мені, як зберегти масив за допомогою Кімната?

ПРИМІТКА: Клас MyListItems Pojo містить 2 рядки (на даний момент)

Заздалегідь спасибі.

Відповіді:


78

Варіант № 1: Have MyListItemsбути @Entity, так як MainActivityDataє. MyListItemsвстановив би @ForeignKeyназад до MainActivityData. У цьому випадку, однак, MainActivityDataне може бути private ArrayList<MyListItems> myListItems, як у кімнаті, сутності не посилаються на інші сутності. Хоча модель подання або подібна конструкція POJO може мати a MainActivityDataта пов'язані з нею ArrayList<MyListItems>.

Варіант №2: Налаштуйте пару @TypeConverterметодів для перетворення ArrayList<MyListItems>на базовий тип та з нього (наприклад, a String, наприклад, використовуючи JSON як формат зберігання). Тепер MainActivityDataможе мати його ArrayList<MyListItems>безпосередньо. Однак окремої таблиці для цього не буде MyListItems, тому ви не можете MyListItemsдуже добре запитувати .


Добре, дякую за швидку відповідь. Я спробую спочатку другий варіант (перший варіант не зовсім зрозумілий, щоб бути tbh ..: E) і зв’яжусь з вами.
Tushar Gogna

1
ArrayList -> String (за допомогою Json) і навпаки працювали гарно. До речі, чи можете ви також детальніше розробити 1-й варіант? Просто хочу знати альтернативу. Все одно, дякую. :)
Тушар Гогна

@TusharGogna: Відносини висвітлюються в документації Кімнати , а біт "сутності не посилаються безпосередньо на інші сутності" також міститься в документації Кімнати .
CommonsWare

1
Просто як примітка. Якщо ви збираєтеся зберегти список Int, наприклад, вам потрібно серіалізувати його як рядок для варіанту 2. Це робить запити більш складними. Я б скоріше вибрав варіант 1, оскільки він менш залежний від "типу".
axierjhtjz

5
Десь у майбутньому вам може знадобитися запитати свої товари, тому я зазвичай
Джеффрі

108

Перетворювачі типів зроблені спеціально для цього. У вашому випадку ви можете використовувати наведений нижче фрагмент коду для зберігання даних у БД.

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
Хто-небудь може допомогти мені зробити те саме в Котліні зі списком. У Java це працювало нормально. Але коли я перетворив його в Коліні, він не працює
Озіті

2
Як ви запитуєте у цього списку записів?
Sanjog Shrestha,

@SanjogShrestha Я не розумію, що ти маєш на увазі. Ви просто отримуєте список записів і запит за допомогою методу get
Аміт Бхандарі,

@AmitBhandari Давайте візьмемо наведений вище сценарій як приклад. Я хочу здійснити пошук у таблиці (MainActivityData), де міститься myListItems (наприклад, a, b, c), а userId - abc. Тепер, як нам написати запит для такого випадку?
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, згаданий у відповіді нижче.
Сохайб Хассун

3
Крім того, вам може знадобитися отримати кешований Gsonекземпляр звідкись у вашому додатку. Ініціалізація нового Gsonекземпляра при кожному дзвінку може коштувати дорого.
Апсалія

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.
Julien Kronegg

2
Це рішення пропускає нещасний потік (наприклад, нульовий і порожній рядок, нульовий список).
Julien Kronegg

Так, я помилився, скопіювавши це, і втратив принаймні годину для списків окремих елементів, створюючи елементи з одинарними комами. Я подав і відповів із виправленням цього (у Котліні)
Даніель Вілсон,

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

2

Ця відповідь використовує Котіна для розділення комами та побудови рядка, виділеного комами. Кома повинна йти в кінці всіх, крім останнього елемента, тому це також буде обробляти списки окремих елементів.

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/ не проводити серіалізації, оскільки вони порушують відповідність нормальним формам бази даних.

У цьому конкретному випадку може бути варто визначити взаємозв'язок за допомогою анотації @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.


0

Додавання @TypeConvertersза допомогою класу конвертора як параметрів

до бази даних і до класу Dao, змусив мої запити працювати


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 до таблиці 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 Повернення рядка, а потім додавання таблиці 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)

І болять ваші ноги: [] як записи в цій таблиці.


не робіть припущень, якщо yopu зберігав список ідентифікаторів, який був доступний у першому виклику api, але не в наступному, тоді неодмінно зберігайте де-небудь ці ідентифікатори, а потім використовуйте їх для запиту, щоб api зберігав його в таблиці з таблицею з'єднань , тут використовуються обидва рішення, я погоджуюся з вами, що це можна розглядати як простий вихід і не чудово з багатьох причин
martinseal1987,

0

Власна версія Kotlin з використанням компонента серіалізації Kotlin - kotlinx.serialization .

  1. Додайте плагін Kotle для серіалізації Kotle до своїх 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.