Как создать классы Entity и данных по ROOM в Android

Как создать классы Entity и данных по ROOM в Android?

У меня есть структура JSON:

data class ListResponse(val item: ListItem)

data class ListItem(
    @SerializedName("id")
    val id: List<CheckUnCheckItem>
)

data class CheckUnCheckItem(
    @SerializedName("check")
    val check: CheckItem,

    @SerializedName("unCheck")
    val UnCheck: UnCheckItem
)

data class CheckItem(
    @SerializedName("url")
    val url: String,

    @SerializedName("text")
    val text: String,

    @SerializedName("color")
    val color: String
)

data class UnCheckItem(
    @SerializedName("url")
    val urlUnCheck: String,

    @SerializedName("text")
    val textUnCheck: String,

    @SerializedName("color")
    val colorUnCheck: String
)

Но как я могу создать такую ​​сущность ROOM?

Нужно ли использовать @TypeConverter?

@Entity(tableName = TABLE_NAME)
data class ListEntity(
    @PrimaryKey @SerializedName("id")
    val id: CheckUnCheckItem,

    @SerializedName("check")
    val check: CheckItem,

    @SerializedName("unCheck")
    val unCheck: UnCheckItem,

    @SerializedName("url")
    val url: String,

    @SerializedName("text")
    val text: String,

    @SerializedName("size")
    val size: String
){
    companion object{
        const val TABLE_NAME = "db_table"
    }

    class RoomTypeConverters{
        @TypeConverter
        fun convertCheckItemListToJSONString(checkList: CheckItem): String = Gson().toJson(checkList)
        @TypeConverter
        fun convertJSONStringToCheckItemList(jsonString: String): CheckItem = Gson().fromJson(jsonString,CheckItem::class.java)

    }
}

верны ли мои данные и классы сущностей? Нужен ли класс ведьмы, расширяющий RoomDatabase? Или лучше мне нужно отделить БД и создать для проверки и снять отметку с другой БД?

0
0
96
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Или лучше мне нужно отделить БД и создать для проверки и снять отметку с другой БД?

Как подразумевает база данных, она способна хранить данные не по одному, а по многим. Таким образом, единая база данных — это все, что потребуется. SQLite — это реляционная база данных, предназначенная для хранения связанных данных. Связанные данные обычно хранятся в нескольких таблицах. Так что опять же одной базы данных, скорее всего, будет достаточно.

Нужно ли использовать @TypeConverter? На самом деле вам никогда не понадобятся преобразователи типов. Однако для любого объекта, кроме тех, которые обрабатываются напрямую (например, String, Int, Long, Double, Float, ByteArray), вам нужно либо разбить их на такие обрабатываемые объекты, либо иметь преобразователь типов, который будет преобразовывать объект в и из такой объект.

Например, на основе вашего аннотированного класса ListEntity @Entity тогда: -

  • для идентификатора поля потребуется TypeConverter, поскольку тип CheckUnCheckItem не является типом объекта, который может напрямую обрабатываться Room. Таким образом, вам понадобятся два преобразователя типов, которые могли бы преобразовывать CheckUncheckItem в тип, который может обрабатываться Room, и из него.
  • для проверки и снятия флажка потребуются два преобразователя типов (и похоже, что преобразователи типов, которые вы закодировали, будут обрабатывать преобразование).
  • URL-адрес полей, текст и размер, поскольку все они являются типами String, не нуждаются в преобразователях типов, поскольку Room обрабатывает строки.

Комната должна знать о преобразователях типов. Итак, вам нужна аннотация @TypeConverters. Его размещение определяет масштаб. Использование аннотации, предшествующей аннотации @Database, имеет наиболее далеко идущие возможности.

Нужен ли класс ведьмы, расширяющий RoomDatabase? Да. Однако он должен быть абстрактным классом и должен иметь абстрактную функцию для извлечения экземпляра каждого аннотированного интерфейса @Dao (или абстрактного класса, и в этом случае функции должны быть абстрактными, нет необходимости в абстрактном классе с Kotlin в качестве функций в интерфейсе может быть тел)).

Этот класс должен быть аннотирован аннотацией @Database, параметр entities аннотации должен включать список классов для каждой таблицы (@Entity аннотированный класс). например

@TypeConverters(value = [ListEntity.RoomTypeConverters::class])
@Database(entities = [ListEntity::class], exportSchema = false, version = 1)
abstract class TheDatabase: RoomDatabase(){

}
  • Однако использование вышеизложенного вместе с вашими классами приводит к ошибке сборки в соответствии с: -

  • ListEntity.java:11: error: Cannot figure out how to save this field into database. You can consider adding a type converter for it. private final a.a.so74708202kotlinroomentitydesign.CheckUnCheckItem id = null;

    • как объяснено, CheckUnCheckItem не может быть обработан Room.

Изменение класса RoomTypeConverters следующим образом: -

class RoomTypeConverters{
    @TypeConverter
    fun convertItemListToJSONString(invoiceList: Item): String = Gson().toJson(invoiceList)
    @TypeConverter
    fun convertJSONStringToItemList(jsonString: String): Item = Gson().fromJson(jsonString,Item::class.java)
    @TypeConverter
    fun convertCheckUnCheckItemToJSONString(cuc: CheckUnCheckItem): String = Gson().toJson(cuc)
    @TypeConverter
    fun convertJSONStringToCheckUnCheckItem(jsonString: String): CheckUnCheckItem = Gson().fromJson(jsonString,CheckUnCheckItem::class.java)

}

Решает проблему сборки, и теоретически у вас есть потенциально пригодная для использования база данных.

Однако вам, очевидно, нужен код для доступа к базе данных. Таким образом, вы, скорее всего, хотели бы иметь. как упоминалось ранее, аннотированный интерфейс @Dao, например

@Dao
interface TheDAOs {
    @Insert
    fun insert(listEntity: ListEntity): Long
    @Query("SELECT * FROM ${TABLE_NAME}")
    fun getAll(): List<ListEntity>
}
  • Этого будет достаточно для вставки строк в базу данных и для извлечения всех строк из базы данных в файл List<ListEntity).

  • Из базы данных built вам нужно получить экземпляр TheDAO, и, таким образом, аннотированный класс @Database может быть

:-

@TypeConverters(value = [ListEntity.RoomTypeConverters::class])
@Database(entities = [ListEntity::class], exportSchema = false, version = 1)
abstract class TheDatabase: RoomDatabase(){
    abstract fun getTheDAOsInstance(): TheDAOs

}

Чтобы продемонстрировать фактическое использование вышеизложенного, рассмотрите следующий код в действии: -

class MainActivity : AppCompatActivity() {

    lateinit var roomDBInstance: TheDatabase
    lateinit var theDAOs: TheDAOs
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        roomDBInstance = Room.databaseBuilder(this,TheDatabase::class.java,"The_database_name.db")
            .allowMainThreadQueries() /* NOTE ADDED FOR CONVENIENCE AND BREVITY */
            .build()
        /* Note the database itself does not yet exist, it's creation is delayed until an attempt is made to access it. So:- */
        theDAOs = roomDBInstance.getTheDAOsInstance() /* Still the database is not created/accessed */
        showData(theDAOs.getAll()) /* No data has been added BUT the database will now exist */

        theDAOs.insert(
            ListEntity(
                id =  CheckUnCheckItem(
                    check = Item (
                        url  = "URL001",
                        text = "TEXT001",
                        color = "RED"
                    ),
                    unCheck =  Item(
                        url  = "URL002",
                        text = "TEXT002",
                        color = "BLUE"
                    )
                ),
                check =  Item(url = "URL003", text  = "TEXT003", color  = "WHITE"),
                unCheck =  Item(url = "URL004", text = "TEXT004", color = "BLACK"),
                url = "URL005", text = "TEXT005", size = "BIG"
            )
        )
        showData(theDAOs.getAll())
    }

    fun showData(listEntities: List<ListEntity>) {
        for (li in listEntities) {
            Log.d(
                "DBINFO",
                "id is $li.id.check.url${li.id.check.text}.... " +
                        "\n\tcheck is ${li.check.url} .... " +
                        "\n\tuncheck is ${li.unCheck.url} ...." +
                        "\n\turl is ${li.url} text is ${li.text} size is ${li.size}"
            )
        }
    }
}

Вывод в журнал: -

D/DBINFO: id is ListEntity(id=CheckUnCheckItem(check=Item(url=URL001, text=TEXT001, color=RED), unCheck=Item(url=URL002, text=TEXT002, color=BLUE)), check=Item(url=URL003, text=TEXT003, color=WHITE), unCheck=Item(url=URL004, text=TEXT004, color=BLACK), url=URL005, text=TEXT005, size=BIG).id.check.urlTEXT001.... 
        check is URL003 .... 
        uncheck is URL004 ....
        url is URL005 text is TEXT005 size is BIG

База данных через проверку приложений "-

Итак, наконец

верны ли мои данные и классы сущностей?

С точки зрения базы данных да, они работают после нескольких поправок. Однако я подозреваю, что ваши классы, вероятно, не такие, как вы предполагали.


Альтернативный подход

Если бы к этому подходили с точки зрения базы данных и нормализовали, без раздувания и без необходимости преобразователей типов, то рассмотрите следующее:

Встроенные элементы (снять отметку и проверить) в основном повторяются, поэтому, вероятно, это может быть таблица (связанная с db_table). Отсюда 2 таблицы. Один для ListEntity (альтернативный), а другой для элементов (AlternativeItem), поэтому 2 аннотированных класса @Entity могут быть: -

/* Alternative Approach */
@Entity(
    /* Foreign Keys NOT REQUIRED, they enforce Referential Integrity */
    foreignKeys = [
        ForeignKey(
            entity = AlternativeItem::class,
            parentColumns = ["alternativeItemId"],
            childColumns = ["unCheckIdMap"]
            /* OPTIONAL within a Foreign Key, they help automatically maintain Referential Integrity*/,
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE
        ),
        ForeignKey(
            entity = AlternativeItem::class,
            parentColumns = ["alternativeItemId"],
            childColumns = ["checkIdMap"],
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE
        )
    ]
)
data class Alternative(
    @PrimaryKey
    val id: Long?=null,
    @ColumnInfo(index = true)
    val unCheckIdMap: Long, /* map to the id of the related Item (AlternativeItem) for the uncheck */
    @ColumnInfo(index = true)
    val checkIdMap: Long, /* map to the id of the related Item (AlternativeItem) for the uncheck */
    val url: String,
    val text: String,
    val size: String
)
@Entity
data class AlternativeItem(
    @PrimaryKey
    val alternativeItemId: Long?=null,
    val alternativeItemUrl: String,
    val alternativeItemText: String,
    val alternativeItemColor: String
)

Поскольку вам обычно нужна альтернатива вместе со связанными с ней AlternativeItems, тогда POJO, который обеспечивает единство: -

data class AlternativeWithUncheckAndCheck(
    @Embedded
    val alternative: Alternative,
    @Relation(entity = AlternativeItem::class, parentColumn = "unCheckIdMap", entityColumn = "alternativeItemId")
    val unCheck: AlternativeItem,
    @Relation(entity = AlternativeItem::class, parentColumn = "checkIdMap", entityColumn = "alternativeItemId")
    val check: AlternativeItem
)

В аннотированном интерфейсе @Dao потребуются некоторые дополнительные функции, поэтому: -

@Insert
fun insert(alternative: Alternative): Long
@Insert
fun insert(alternativeItem: AlternativeItem): Long

@Transaction
@Query("")
fun insertAlternativeAndUncheckAndCheck(alternative: Alternative, uncheck: AlternativeItem, check: AlternativeItem): Long {
    var uncheckId = insert(uncheck)
    var checkId = insert(check)
    return insert(Alternative(null,url = alternative.url, text = alternative.text, size = alternative.size, unCheckIdMap = uncheckId, checkIdMap = checkId ))
}

@Transaction
@Query("SELECT * FROM alternative")
fun getAllAlternativesWithRelatedUnCheckAndCheck(): List<AlternativeWithUncheckAndCheck>
  • обратите внимание, что insertAlternativeAndUncheckAndCheck делает то, что говорит (обратите внимание, что он слишком прост и может потребовать некоторых улучшений, чтобы расширить принцип)

Чтобы продемонстрировать это, все, что требуется, — это добавить новые объекты в entities parameter, а затем добавить некоторый код в действие.

Исправленная аннотация @Database: -

@Database(entities = [ListEntity::class, /* for the alternative approach */ Alternative::class, AlternativeItem::class], exportSchema = false, version = 1)

Код действия (который обслуживает оба подхода в аналогичном/эквивалентном способе хранения и извлечения данных): -

class MainActivity : AppCompatActivity() {

    lateinit var roomDBInstance: TheDatabase
    lateinit var theDAOs: TheDAOs
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        roomDBInstance = Room.databaseBuilder(this,TheDatabase::class.java,"The_database_name.db")
            .allowMainThreadQueries() /* NOTE ADDED FOR CONVENIENCE AND BREVITY */
            .build()
        /* Note the database itself does not yet exist, it's creation is delayed until an attempt is made to access it. So:- */
        theDAOs = roomDBInstance.getTheDAOsInstance() /* Still the database is not created/accessed */
        showData(theDAOs.getAll()) /* No data has been added BUT the database will now exist */

        theDAOs.insert(
            ListEntity(
                id =  CheckUnCheckItem(
                    check = Item (
                        url  = "URL001",
                        text = "TEXT001",
                        color = "RED"
                    ),
                    unCheck =  Item(
                        url  = "URL002",
                        text = "TEXT002",
                        color = "BLUE"
                    )
                ),
                check =  Item(url = "URL003", text  = "TEXT003", color  = "WHITE"),
                unCheck =  Item(url = "URL004", text = "TEXT004", color = "BLACK"),
                url = "URL005", text = "TEXT005", size = "BIG"
            )
        )
        showData(theDAOs.getAll())


        /* Alternative equivalent */

        theDAOs.insertAlternativeAndUncheckAndCheck(
            Alternative(url = "URL005", size = "BIG", text = "TEXT005", checkIdMap = -1, unCheckIdMap = -1),
            check = AlternativeItem(alternativeItemUrl = "URL001", alternativeItemText = "TEXT001", alternativeItemColor = "RED"),
            uncheck = AlternativeItem(alternativeItemUrl = "URL002", alternativeItemText = "TEXT002", alternativeItemColor = "BLUE" )
        )
        showAlternativeData(theDAOs.getAllAlternativesWithRelatedUnCheckAndCheck())
    }

    fun showData(listEntities: List<ListEntity>) {
        for (li in listEntities) {
            Log.d(
                "DBINFO",
                "id is $li.id.check.url${li.id.check.text}.... " +
                        "\n\tcheck is ${li.check.url} .... " +
                        "\n\tuncheck is ${li.unCheck.url} ...." +
                        "\n\turl is ${li.url} text is ${li.text} size is ${li.size}"
            )
        }
    }

    fun showAlternativeData(listAlternatives: List<AlternativeWithUncheckAndCheck>) {
        for (la in listAlternatives) {
            Log.d("DBALTINFO",
            "id is ${la.alternative.id} URL is ${la.alternative.url} TEXT is ${la.alternative.text} SIZE is ${la.alternative.size} " +
                    "\n\t UNCHECK id is ${la.unCheck.alternativeItemId} url is ${la.unCheck.alternativeItemUrl} text is ${la.unCheck.alternativeItemText} color is ${la.unCheck.alternativeItemColor}" +
                    "\n\t CHECK id is ${la.check.alternativeItemId} url is ${la.check.alternativeItemUrl} text is ${la.check.alternativeItemText} color is ${la.check.alternativeItemColor}")
        }
    }
}
  • Обратите внимание, что альтернативный код, вероятно, больше соответствует тому, что вы, вероятно, хотите, в соответствии с интерпретацией показанного JSON.

При запуске результат теперь: -

D/DBINFO: id is ListEntity(id=CheckUnCheckItem(check=Item(url=URL001, text=TEXT001, color=RED), unCheck=Item(url=URL002, text=TEXT002, color=BLUE)), check=Item(url=URL003, text=TEXT003, color=WHITE), unCheck=Item(url=URL004, text=TEXT004, color=BLACK), url=URL005, text=TEXT005, size=BIG).id.check.urlTEXT001.... 
        check is URL003 .... 
        uncheck is URL004 ....
        url is URL005 text is TEXT005 size is BIG
        
        
D/DBALTINFO: id is 1 URL is URL005 TEXT is TEXT005 SIZE is BIG 
         UNCHECK id is 1 url is URL002 text is TEXT002 color is BLUE
         CHECK id is 2 url is URL001 text is TEXT001 color is RED
  • есть подозрение, что BLACK/WHITE или RED/BLUE являются излишними в вашей интерпретации JSON для классов данных (и, следовательно, исключены из альтернативы).

База данных через App Inspection (в отношении альтернативного подхода): -

и :-

то есть хранятся только фактические данные, BLOAT (описания полей/типов, разделители, вложенные данные) не сохраняется, таким образом

  • база данных будет содержать больше данных в меньшем объеме.
  • Таким образом, обработка данных будет более эффективной (например, буфер может хранить больше фактических данных вместо BLOAT).
  • запрос данных, таких как, например, поиск всех СИНИХ, является прямым поиском этого, в то время как с преобразованными данными у вас могут возникнуть проблемы с различием между BLOAT и фактическими данными.

Однако отрицательным является то, что требуется больше кода и размышлений.

Обратите внимание, что этот ответ предназначен для рассмотрения основных принципов и, безусловно, не является полностью исчерпывающим.

Другие вопросы по теме