Cơ sở dữ liệu Android Room: Làm thế nào để xử lý Arraylist trong một thực thể?


84

Tôi vừa triển khai Room để tiết kiệm dữ liệu ngoại tuyến. Nhưng trong một lớp Thực thể, tôi gặp lỗi sau:

Error:(27, 30) error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.

Và lớp học như sau:

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

}

Vì vậy, về cơ bản tôi muốn lưu ArrayList trong cơ sở dữ liệu nhưng tôi không thể tìm thấy bất kỳ thứ gì liên quan đến nó. Bạn có thể hướng dẫn tôi cách lưu Array bằng Room không?

LƯU Ý: Lớp MyListItems Pojo chứa 2 Chuỗi (tính đến thời điểm hiện tại)

Cảm ơn trước.

Câu trả lời:


78

Lựa chọn # 1: Có MyListItemsthể là một @Entity, như MainActivityDatalà. MyListItemssẽ thiết lập một sự @ForeignKeytrở lại MainActivityData. Trong trường hợp này, tuy nhiên, MainActivityDatakhông thể có private ArrayList<MyListItems> myListItems, như trong Room, các thực thể không tham chiếu đến các thực thể khác. Tuy nhiên, một mô hình khung nhìn hoặc cấu trúc POJO tương tự có thể có một MainActivityDatavà liên kết với nó ArrayList<MyListItems>.

Tùy chọn # 2: Thiết lập một cặp @TypeConverterphương thức để chuyển đổi ArrayList<MyListItems>sang và chuyển từ một số kiểu cơ bản (ví dụ: a String, chẳng hạn như bằng cách sử dụng JSON làm định dạng lưu trữ). Bây giờ, MainActivityDatacó thể có nó ArrayList<MyListItems>trực tiếp. Tuy nhiên, sẽ không có bảng riêng biệt cho MyListItemsvà vì vậy bạn không thể truy vấn trên MyListItemsrất tốt.


Được rồi, cảm ơn vì câu trả lời nhanh chóng. Tôi sẽ thử lựa chọn thứ 2 trước (lựa chọn thứ nhất không hoàn toàn rõ ràng là tbh ..: E) và liên hệ lại với bạn.
Tushar Gogna

1
ArrayList -> String (sử dụng Json) và ngược lại hoạt động tốt. Btw, bạn cũng có thể giải thích thêm về lựa chọn đầu tiên được không? Chỉ muốn biết giải pháp thay thế. Dẫu sao cũng xin cảm ơn. :)
Tushar Gogna

@TusharGogna: Các mối quan hệ được đề cập trong tài liệu Phòng và bit "thực thể không tham chiếu trực tiếp đến các thực thể khác" cũng được đề cập trong tài liệu Phòng .
CommonsWare

1
Chỉ như một lưu ý. Nếu bạn định tồn tại một danh sách Int chẳng hạn, thì bạn cần phải tuần tự hóa nó thành chuỗi cho tùy chọn 2. Điều này làm cho các truy vấn phức tạp hơn. Tôi muốn đi cho tùy chọn 1 vì ít phụ thuộc "loại" hơn.
axierjhtjz

5
Đôi khi trong tương lai, bạn có thể cần phải truy vấn Mục của mình, vì vậy, tôi thường chọn tùy chọn số 1
Jeffrey

107

Bộ chuyển đổi loại được thực hiện đặc biệt cho điều đó. Trong trường hợp của bạn, bạn có thể sử dụng đoạn mã được cung cấp bên dưới để lưu trữ dữ liệu trong 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;
    }
}

Và đề cập đến lớp này trong Phòng DB của bạn như thế này

@Database (entities = {MainActivityData.class},version = 1)
@TypeConverters({Converters.class})

Thông tin thêm tại đây


2
Bất cứ ai có thể giúp tôi làm điều tương tự trong Kotlin với Danh sách. Trong Java, nó hoạt động tốt. Nhưng khi tôi chuyển đổi nó trong Kolin nó không làm việc
Ozeetee

2
Làm thế nào để bạn truy vấn từ danh sách mảng đó?
Sanjog Shrestha

@SanjogShrestha Tôi không hiểu bạn muốn nói gì. Bạn chỉ cần truy xuất danh sách mảng và truy vấn bằng cách sử dụng phương thức get
Amit Bhandari

@AmitBhandari Hãy lấy trường hợp trên làm ví dụ. Tôi muốn tìm kiếm bảng (MainActivityData) nơi chứa myListItems (ví dụ: a, b, c) và userId là abc. Bây giờ chúng ta viết câu truy vấn cho trường hợp này như thế nào?
Sanjog Shrestha

1
@bompf cảm ơn vì gợi ý. Mặc dù ví dụ này ở đây chỉ là minh họa. Nói chung, chúng tôi luôn giữ một phiên bản gson ở cấp ứng dụng.
Amit Bhandari

51

Phiên bản Kotlin cho bộ chuyển đổi loại:

 class Converters {

    @TypeConverter
    fun listToJson(value: List<JobWorkHistory>?) = Gson().toJson(value)

    @TypeConverter
    fun jsonToList(value: String) = Gson().fromJson(value, Array<JobWorkHistory>::class.java).toList()
}

Tôi đã sử dụng JobWorkHistoryđối tượng cho mục đích của tôi, sử dụng đối tượng của riêng bạn

@Database(entities = arrayOf(JobDetailFile::class, JobResponse::class), version = 1)
@TypeConverters(Converters::class)
abstract class MyRoomDataBase : RoomDatabase() {
     abstract fun attachmentsDao(): AttachmentsDao
}

2
Tôi nghĩ thay vì giải kích thước thành một mảng và sau đó chuyển đổi thành Danh sách, tốt hơn nên sử dụng kiểu Danh sách như thế này: val listType = object: TypeToken <List <JobWorkHistory>> () {}. Kiểu như Amit đã đề cập trong câu trả lời bên dưới.
Sohayb Hassoun

3
Ngoài ra, bạn có thể muốn tìm nạp phiên bản đã lưu trong bộ nhớ cache Gsontừ một nơi nào đó trong ứng dụng của mình. Khởi tạo phiên bản mới Gsontrên mỗi cuộc gọi có thể tốn kém.
Apsaliya

14

Phiên bản tốt hơn của trình List<String>chuyển đổi

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
Hãy sử dụng "," làm dấu phân tách vì đôi khi chuỗi của bạn có thể có cùng một ký tự và nó có thể gây lộn xộn.
emarshah

9

Đây là cách tôi xử lý chuyển đổi Danh sách

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

Và sau đó trên cơ sở dữ liệu tôi làm như hình dưới đây

@Database(entities = {MovieEntry.class}, version = 1)
@TypeConverters(GenreConverter.class)

Và dưới đây là một kotlin triển khai tương tự;

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

Tôi sử dụng giải pháp này cho các loại đơn giản (ví dụ: List <Integer>, List <Long>) vì nó nhẹ hơn các giải pháp dựa trên gson.
Julien Kronegg 21/09/18

2
Giải pháp này bỏ lỡ luồng không vui (ví dụ: null và rỗng String, null List).
Julien Kronegg 21/09/18

Vâng, tôi đã mắc sai lầm khi sao chép, dán cái này và mất ít nhất một giờ để liệt kê các phần tử đơn lẻ tạo ra các phần tử có dấu phẩy. Tôi đã gửi và trả lời với một bản sửa lỗi cho điều đó (ở Kotlin)
Daniel Wilson

6

Có thông báo lỗi tương tự như mô tả ở trên. Tôi muốn nói thêm: nếu bạn nhận được thông báo lỗi này trong @Query, bạn nên thêm @TypeConverters phía trên chú thích @Query.

Thí dụ:

@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
Tôi đã cố gắng thêm @TypeConverters phía trên chú thích truy vấn, nhưng tôi vẫn nhận được lỗi tương tự
shah zulkarnain

2

Câu trả lời này sử dụng Kotin để chia theo dấu phẩy và xây dựng chuỗi được phân định bằng dấu phẩy. Dấu phẩy cần phải ở cuối tất cả trừ phần tử cuối cùng, vì vậy điều này cũng sẽ xử lý các danh sách phần tử đơn lẻ.

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

trong trường hợp của tôi, vấn đề là loại chung chung dựa trên câu trả lời này

https://stackoverflow.com/a/48480257/3675925 sử dụng Danh sách thay vì 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)
     } 
}

nó không cần thêm @TypeConverters (IntArrayListConverter :: class) để truy vấn trong lớp dao cũng như các trường trong lớp Entity và chỉ cần thêm @TypeConverters (IntArrayListConverter :: class) vào lớp cơ sở dữ liệu

@Database(entities = [MyEntity::class], version = 1, exportSchema = false)
@TypeConverters(IntArrayListConverter::class)
abstract class MyDatabase : RoomDatabase() {

2

Cá nhân tôi sẽ khuyên chống lại @TypeConverters/ tuần tự hóa, vì chúng phá vỡ sự tuân thủ các biểu mẫu thông thường của cơ sở dữ liệu.

Đối với trường hợp cụ thể này, có thể đáng để xác định mối quan hệ bằng cách sử dụng chú thích @Relation , cho phép truy vấn các thực thể lồng nhau vào một đối tượng duy nhất mà không cần khai báo thêm một cách phức tạp @ForeignKeyvà viết tất cả các truy vấn SQL theo cách thủ công:

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

Ngoài ví dụ 1-N này, có thể xác định các mối quan hệ 1-1 và NM.


0

Thêm @TypeConvertersvới lớp chuyển đổi dưới dạng tham số

đến Cơ sở dữ liệu & tới lớp Dao, đã làm cho các truy vấn của tôi hoạt động


1
bạn có thể giải thích câu trả lời của bạn không ??
K Pradeep Kumar Reddy

0

Chuyển đổi Json không mở rộng quy mô tốt về mặt phân bổ bộ nhớ. Tôi muốn tìm kiếm thứ gì đó tương tự như các phản hồi ở trên với một số khả năng vô hiệu.

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(",")
    }
}

Đối với các kiểu dữ liệu đơn giản, có thể sử dụng các kiểu dữ liệu trên, nếu không, đối với các kiểu dữ liệu phức tạp, Room cung cấp Embedded


0

Đây là ví dụ để thêm các loại customObject vào bảng Room DB. https://mobikul.com/insert-custom-list-and-get-that-list-in-room-database-using-typeconverter/

Thêm một trình chuyển đổi kiểu rất dễ dàng, tôi chỉ cần một phương thức có thể biến danh sách các đối tượng thành một chuỗi và một phương thức có thể làm ngược lại. Tôi đã sử dụng gson cho việc này.

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

Sau đó, tôi đã thêm chú thích vào trường trong Thực thể:

@TypeConverters(Converters.class)

public final ArrayList<MyListItem> myListItems;

0

Khi chúng ta sử dụng TypaConverters Thì Datatype Nên trả về kiểu phương thức TypeConverter. Ví dụ Phương thức TypeConverter Trả về Chuỗi sau đó Thêm Bảng COloum phải là Chuỗi

 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

Tất cả các câu trả lời trên là dành cho danh sách các chuỗi. Nhưng bên dưới sẽ giúp bạn tìm công cụ chuyển đổi cho danh sách các đối tượng của bạn.

Chỉ thay cho " YourClassName ", hãy thêm lớp Đối tượng của bạn.

 @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

Tất cả các câu trả lời trên đều đúng. Có, nếu bạn THỰC SỰ cần một mảng lưu trữ thứ gì đó vào một trường SQLite thì TypeConverter là một giải pháp.

Và tôi đã sử dụng câu trả lời được chấp nhận trong các dự án của mình.

Nhưng đừng làm vậy !!!

Nếu bạn cần mảng lưu trữ trong Thực thể trong 90% trường hợp, bạn cần tạo mối quan hệ một-nhiều hoặc nhiều-nhiều.

Nếu không, truy vấn SQL tiếp theo của bạn để chọn thứ gì đó có khóa bên trong mảng này sẽ hoàn toàn là địa ngục ...

Thí dụ:

Đối tượng foo có dạng json: [{id: 1, name: "abs"}, {id: 2, name: "cde"}

Thanh đối tượng: [{id, 1, foos: [1, 2], {...}]

Vì vậy, đừng làm cho thực thể như:

@Entity....
data class bar(
...
val foos: ArrayList<Int>)

Thực hiện như tiếp theo:

@Entity(tablename="bar_foo", primaryKeys=["fooId", "barId"])
data class barFoo(val barId: Int, val fooId: Int)

Và ghi nhớ foos của bạn: [] như các bản ghi trong bảng này.


Đừng đưa ra giả định nếu yopu đang lưu trữ danh sách các ID có sẵn trong lần gọi api đầu tiên nhưng không có trong lần gọi tiếp theo, thì bằng mọi cách, hãy lưu trữ các id đó ở đâu đó và sau đó sử dụng chúng để truy vấn api, lưu trữ nó vào một bảng có bảng nối , điều này sử dụng cả hai giải pháp, tôi đồng ý với bạn rằng điều này có thể được xem như một lối thoát dễ dàng và tuyệt vời isnt cho rất nhiều lý do
martinseal1987

0

Phiên bản Kotlin gốc sử dụng thành phần tuần tự hóa của Kotlin - kotlinx.serialization .

  1. Thêm plugin Gradle tuần tự hóa Kotlin và phần phụ thuộc vào build.gradle:
apply plugin: 'kotlinx-serialization'

dependencies {
   ...
   implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1"
}
  1. Thêm các bộ chuyển đổi Loại vào lớp Bộ chuyển đổi của bạn;
@TypeConverter
fun fromList(value : List<String>) = Json.encodeToString(value)

@TypeConverter
fun toList(value: String) = Json.decodeFromString<List<String>>(value)
  1. Thêm lớp Chuyển đổi của bạn vào lớp cơ sở dữ liệu của bạn:
@TypeConverters(Converters::class)
abstract class YourDatabase: RoomDatabase() {...}

Và bạn đã hoàn thành!

Các nguồn bổ sung:


-2

Sử dụng giải pháp chính thức từ phòng, chú thích @Embedded:

@Embedded(prefix = "mylist_array") private ArrayList<MyListItems> myListItems
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.