@Query говорит, что у него нет полей, хотя они есть в самом запросе -> Kotlin Android Room

Я получил этот код в своем DAO:

@Query("select Conversation.*, User.* from Conversation join User on Conversation.createdBy = User.userUuid where conversationUuid = :conversationUuid")
fun selectAllForOverview(conversationUuid: UUID): LiveData<List<ConversationSelectAllForOverview>>

Это ConversationSelectAllForOverview

data class ConversationSelectAllForOverview(
    @Embedded(prefix = "arg0")
    val arg0: DbConversation,
    @Embedded(prefix = "arg1")
    val arg1: DbUser
)

Я читал, что мне нужно аннотировать свои поля с помощью prefix, чтобы избавиться от ошибок, когда они имеют общие имена полей. Я получаю эту ошибку, и я не знаю, как я могу ее удалить. Я на 100% уверен, что все столбцы доступны, так как DbConversation и DbUser просто генерируются из базы данных. Как я могу решить эту проблему? DbConversation и DbUser делят несколько столбцов, см. определение DbConversation здесь: https://gist.github.com/Jasperav/381243e7b3cf387bfc0e9f1343f9faeb. DbUser выглядит так же.

error: The columns returned by the query does not have the fields
 [conversationUuid,createdBy,tsCreated,distanceMapped,showOnMap,showOnOverview,allowMessagesByInRangeRegularUsers,allowMessagesByOutOfRangeRegularUsers,stillReadableForOutOfRangeRegularUsers,freedomInReplies,title,subject,likes,latitude,longitude,hasPassword,isSubscribed,showOnMapScreen,isLiked,bypassChecks,isHidden,nsfw,currentDirectEvents,totalDirectEventsAfterLastJoin,subscriptions,userUuid,username,karma,tsCreated,allowsPrivateChats,allowsNsfw,thisUserBlockedCurrentUser,incomingFriendshipRequest,outstandingFriendshipRequest,friends,bio,appRoleMapped]
 in entity.ConversationSelectAllForOverview even though they are
 annotated as non-null or primitive. Columns returned by the query:
 [conversationUuid,createdBy,tsCreated,distanceMapped,showOnMap,showOnOverview,allowMessagesByInRangeRegularUsers,allowMessagesByOutOfRangeRegularUsers,stillReadableForOutOfRangeRegularUsers,freedomInReplies,title,subject,likes,avatar,latitude,longitude,hasPassword,isSubscribed,showOnMapScreen,isLiked,bypassChecks,isHidden,conversationReportReasonMapped,nsfw,currentDirectEvents,totalDirectEventsAfterLastJoin,lastReadConversationEventPk,mostRecentConversationEventUuid,relevance,subscriptions,userUuid,username,karma,tsCreated,allowsPrivateChats,allowsNsfw,avatar,currentUserBlockedThisUserTsCreated,thisUserBlockedCurrentUser,searchScreenScore,recentSearchedTsCreated,userReportReasonMapped,incomingFriendshipRequest,outstandingFriendshipRequest,friends,bio,appRoleMapped]
     public abstract androidx.lifecycle.LiveData<java.util.List<entity.ConversationSelectAllForOverview>>
 selectAllForOverview(@org.jetbrains.annotations.NotNull()
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
0
50
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Взгляните на свои классы DbConversation и DbUser и убедитесь, что эти классы @Embeddable. Если нет, поместите аннотацию в класс. Другое решение — снова посмотреть на ошибку. В нем говорится, что запрос возвращает набор столбцов, которых нет в вашем DTO (ConversationSelectAllForOverview); поля вашего DTO - это объекты, а не поля столбцов. Вы можете создать интерфейс, получающий все те столбцы, которые возвращает БД:

interface nameInterface(
    val conversationUuid: Any,
    val createdBy: Any,
    val tsCreated: Any,
    val distanceMapped: Any,
    val showOnMap: Any,
    val showOnOverview: Any,
    val allowMessagesByInRangeRegularUsers: Any,
    val allowMessagesByOutOfRangeRegularUsers: Any,
    val stillReadableForOutOfRangeRegularUsers: Any,
    val freedomInReplies: Any,
    val title: Any,
    val subject: Any,
    val likes: Any,
    val latitude: Any,
    val longitude: Any,
    val hasPassword: Any,
    val isSubscribed: Any,
    val showOnMapScreen: Any,
    val isLiked: Any,
    val bypassChecks: Any,
    val isHidden: Any,
    val nsfw: Any,
    val currentDirectEvents: Any,
    val totalDirectEventsAfterLastJoin: Any,
    val subscriptions: Any,
    val userUuid: Any,
    val username: Any,
    val karma: Any,
    val tsCreated: Any,
    val allowsPrivateChats: Any,
    val allowsNsfw: Any,
    val thisUserBlockedCurrentUser: Any,
    val incomingFriendshipRequest: Any,
    val outstandingFriendshipRequest: Any,
    val friends: Any,
    val bio: Any,
    val appRoleMapped: Any)
Embeddable не существует
J. Doe 15.02.2023 19:33
Ответ принят как подходящий

Проблема

Вы добавляете префикс к столбцам в аннотации @Embedded в сочетании с выходными столбцами без префикса в соответствии с запросом.

Например, класс ConversationSelectAllForOverview ожидает найти столбец с именем arg0conversationUuid в выходных данных/результатах запроса, но в запросе есть только столбец talkUuid.

Исправление

Вместо использования select Conversation.*, User.* .... вам нужно использовать

 select Conversation.conversationUuid AS arg0conversationUuid, Conversation.createdBy AS arg0createdBy ....
  • AS присвоение выходному столбцу разговора Uuid псевдонима arg0conversationUuid и т. д.

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

например используя (только частично скорректировано): -

@Query("select " +
        "Conversation.conversationUuid AS arg0conversationUuid" +
        ", Conversation.createdBy AS arg0createdBy" +
        ", Conversation.tsCreated AS arg0tsCreated" +
        ", Conversation.distanceMapped AS arg0distanceMapped" +
        /* .... */
        ", User.* from Conversation join User on Conversation.createdBy = User.userUuid where conversationUuid = :conversationUuid")
fun selectAllForOverview(conversationUuid: UUID): LiveData<List<ConversationSelectAllForOverview>>
  • тогда сообщение The columns returned by the query does not have the fields [showOnMap,showOnOverview ....
    • т. е. talkUuid, createdBy, tsCreated и DistanceMapped теперь не включаются в список несоответствия полей и столбцов.

Альтернативное исправление (непроверенное и зависящее от библиотек Room 2.5.0)

Другим решением, которое может работать с комнатой 2.5.0 (не проверено), будет использование аннотации @Relation вместо аннотации @Embedded для ребенка (детей). например без каких-либо других изменений, кроме: -

data class ConversationSelectAllForOverview(
    @Embedded/*(prefix = "arg0")*/
    val arg0: DbConversation,
    @Relation(
        entity = DbUser::class,
        parentColumn = "createdBy",
        entityColumn = "userUuid"
    )
    val arg1: DbUser
)

а затем с помощью: -

@Transaction
@Query("SELECT * FROM Conversation WHERE conversationUUid=:conversationUuid")
fun selectAllForOverview(conversationUuid: UUID): LiveData<List<ConversationSelectAllForOverview>>

Затем он успешно компилируется (опять же не запускается, см. ниже). т. е. нет проблем с дублированными столбцами (опять же см. ниже).

Вы также можете использовать (обратите внимание, что имя функции отличается, чтобы можно было скомпилировать оба): -

@Transaction
@Query("select Conversation.*,User.* from Conversation join User on Conversation.createdBy = User.userUuid where conversationUuid = :conversationUuid")
fun selectAllForOverviewAlt(conversationUuid: UUID): LiveData<List<ConversationSelectAllForOverview>>
  • Однако столбцы JOINed являются излишними, поскольку Room использует параметры @Relationship для последующего создания объекта DbUser с помощью последующего запроса (отсюда и @Transaction).

ПРИМЕЧАНИЕ Комната используется для получения последнего найденного значения для выходного столбца с таким же именем и применения этого последнего значения ко всем полям с таким именем. Сообщается, что это было исправлено в версии 2.5.0. Однако это не было подтверждено. Таким образом, вы можете получить непредвиденные результаты, поэтому, если вы выберете этот подход, вы должны подтвердить, что значения соответствуют ожидаемым (т. е. проверить значения столбцов/полей с одинаковыми именами).

Дополнительная демонстрация

Ниже приведена рабочая демонстрация, основанная на коде класса question DbConversation. НО, для упрощения был составлен другой код, и, кроме того, многие поля были закомментированы. LiveData закомментирован и .allowMainThreadQueries для упрощения демонстрации.

В демоверсии используются оба исправления, а для @Relation как исходный запрос, так и предложенный более удобный запрос.

Точка останова отладки использовалась для демонстрации наборов из 3 возвратов.

Код базы данных (все, что нужно для демонстрации, плюс кое-что, что не нужно комментировать). Следует отметить, что большая часть кода может отличаться, были сделаны простые предположения.

:-

@Entity(
    tableName = "Conversation",
    primaryKeys = ["conversationUuid"],
    indices = [/*Index(value = ["nsfw", "relevance"]), Index(value = ["isSubscribed"]),*/ Index(value = ["createdBy"])/*, Index(
        value = ["avatar"]
    ), Index(value = ["mostRecentConversationEventUuid"])*/],
    foreignKeys = [/*ForeignKey(
        entity = DbConversationEventMostRecent::class,
        childColumns = ["mostRecentConversationEventUuid"],
        parentColumns = ["conversationEventUuid"],
        onDelete = SET_NULL,
        onUpdate = CASCADE,
    ), ForeignKey(
        entity = DbMedia::class,
        childColumns = ["avatar"],
        parentColumns = ["mediaUuid"],
        onDelete = CASCADE,
        onUpdate = NO_ACTION,
    ), */ForeignKey(
        entity = DbUser::class,
        childColumns = ["createdBy"],
        parentColumns = ["userUuid"],
        onDelete = CASCADE,
        onUpdate = NO_ACTION,
    )]
)
data class DbConversation(
    @ColumnInfo(typeAffinity = ColumnInfo.TEXT)
    val conversationUuid: UUID,
    @ColumnInfo(typeAffinity = ColumnInfo.TEXT)
    val createdBy: UUID,
    @ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
    val tsCreated: Long,
    @ColumnInfo(typeAffinity = ColumnInfo.TEXT)
    val distanceMapped: ConversationDistance,
    @ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
    val showOnMap: Boolean,
    /*
    @ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
    val showOnOverview: Boolean,
    @ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
    val allowMessagesByInRangeRegularUsers: Boolean,
    @ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
    val allowMessagesByOutOfRangeRegularUsers: Boolean,
    @ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
    val stillReadableForOutOfRangeRegularUsers: Boolean,
    @ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
    val freedomInReplies: Boolean,
    @ColumnInfo(typeAffinity = ColumnInfo.TEXT)
    val title: String,
    @ColumnInfo(typeAffinity = ColumnInfo.TEXT)
    val subject: String,
    @ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
    val likes: Long,
    @ColumnInfo(typeAffinity = ColumnInfo.TEXT)
    val avatar: UUID?,
    @ColumnInfo(typeAffinity = ColumnInfo.REAL)
    val latitude: Double,
    @ColumnInfo(typeAffinity = ColumnInfo.REAL)
    val longitude: Double,
    @ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
    val hasPassword: Boolean,
    @ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
    val isSubscribed: Boolean,
    @ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
    val showOnMapScreen: Boolean,
    @ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
    val isLiked: Boolean,
    @ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
    val bypassChecks: Boolean,
    @ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
    val isHidden: Boolean,
    @ColumnInfo(typeAffinity = ColumnInfo.TEXT)
    val conversationReportReasonMapped: ConversationReportReason?,
    @ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
    val nsfw: Boolean,
    @ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
    val currentDirectEvents: Long,
    @ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
    val totalDirectEventsAfterLastJoin: Long,
    @ColumnInfo(typeAffinity = ColumnInfo.BLOB)
    val lastReadConversationEventPk: ConversationEventPk?,
    @ColumnInfo(typeAffinity = ColumnInfo.TEXT)


    val mostRecentConversationEventUuid: UUID?,
    @ColumnInfo(typeAffinity = ColumnInfo.REAL)
    val relevance: Double?,
    @ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
    val subscriptions: Long
    */

)

@Dao
interface AllDAOs {

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    fun insert(dbUser: DbUser): Long
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    fun insert(dbConversation: DbConversation): Long

    @Query("select " +
            "Conversation.conversationUuid AS arg0conversationUuid" +
            ", Conversation.createdBy AS arg0createdBy" +
            ", Conversation.tsCreated AS arg0tsCreated" +
            ", Conversation.distanceMapped AS arg0distanceMapped" +
            ", Conversation.showOnMap AS arg0showOnMap" +
            /* .... */
            ",User.userUuid AS arg1userUuid" + /*?????? made up/incomplete/asssumed */
            ",User.userName AS arg1userName" +
            " from Conversation join User on Conversation.createdBy = User.userUuid where conversationUuid = :conversationUuid")
    fun selectAllForOverviewOld(conversationUuid: UUID): /*LiveData<*/List<OldConversationSelectAllForOverview>/*>*/

    @Query("SELECT * FROM Conversation WHERE conversationUuid=:conversationUuid")
    fun selectConversationByUuid(conversationUuid: UUID): List<DbConversation>


    @Query("SELECT * FROM Conversation WHERE conversationUuid=:conversationUuid")
    fun selectAllForOverview(conversationUuid: UUID): /*LiveData<*/List<ConversationSelectAllForOverview>/*>*/

    @Query("select Conversation.*,User.* from Conversation join User on Conversation.createdBy = User.userUuid where conversationUuid = :conversationUuid")
    fun selectAllForOverviewAlt(conversationUuid: UUID): /*LiveData<*/List<ConversationSelectAllForOverview>/*>*/
}

data class ConversationDistance(
    val blah: String
)
data class ConversationReportReason(
    val blah: String
)
data class ConversationEventPk(
    val blah: ByteArray
)

class RoomTypeConverters {
    @TypeConverter
    fun fromConversationDistanceToJSON(conversationDistance: ConversationDistance): String = Gson().toJson(conversationDistance)
    @TypeConverter
    fun toConversationDistanceFromJSON(json: String): ConversationDistance = Gson().fromJson(json,ConversationDistance::class.java)
    @TypeConverter
    fun fromConversationReportReasonToJSON(conversationReportReason: ConversationReportReason): String = Gson().toJson(conversationReportReason)
    @TypeConverter
    fun toConversationReportReasonFromJSON(json: String): ConversationReportReason = Gson().fromJson(json,ConversationReportReason::class.java)
    @TypeConverter
    fun fromConversationEventPkToByteArray(conversationEventPk: ConversationEventPk): ByteArray = ByteArray(100)
    @TypeConverter
    fun toConversationEventPkFromByteArray(byteArray: ByteArray): ConversationEventPk = ConversationEventPk(byteArray)

}

@Entity(tableName = "User")
data class DbUser(
    @PrimaryKey
    val userUuid: UUID,
    val userName: String
)
@Entity
data class DbMedia(
    @PrimaryKey
    val mediaUuid: UUID,
    val mediaName: String
)
@Entity
data class DbConversationEventMostRecent(
    @PrimaryKey
    val conversationEventUuid: UUID
)

data class ConversationSelectAllForOverview(
    @Embedded/*(prefix = "arg0")*/
    val arg0: DbConversation,
    @Relation(
        entity = DbUser::class,
        parentColumn = "createdBy",
        entityColumn = "userUuid"
    )
    val arg1: DbUser
)

data class OldConversationSelectAllForOverview(
    @Embedded(prefix = "arg0")
    val arg0: DbConversation,
    @Embedded(prefix = "arg1")
    val arg1: DbUser
)

@TypeConverters(value = [RoomTypeConverters::class])
@Database(entities = [DbMedia::class,DbUser::class,DbConversationEventMostRecent::class,DbConversation::class], exportSchema = false, version = 1)
abstract class TheDatabase: RoomDatabase() {
    abstract fun getAllDAOs(): AllDAOs
    companion object {
        private var instance: TheDatabase? = null
        fun getInstance(context: Context): TheDatabase {
            if (instance==null) {
                instance=Room.databaseBuilder(context,TheDatabase::class.java,"the_database")
                    .allowMainThreadQueries() /* For brevity of demo */
                    .build()
            }
            return instance as TheDatabase
        }
    }
}

Код активности: -

class MainActivity : AppCompatActivity() {
    lateinit var db: TheDatabase
    lateinit var dao: AllDAOs
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        db = TheDatabase.getInstance(this)
        dao = db.getAllDAOs()

        val u1uuid = UUID(10L,10L)
        val u2uuid = UUID(11L,12L)
        val c1uuid = UUID(20L,20L)
        val c2uuid = UUID(21L,21L)
        val c3uuid = UUID(22L,10L)
        val c4uuid = UUID(15L,10L)

        dao.insert(DbUser(u1uuid,"Fred"))
        dao.insert(DbUser(u2uuid,"Mary"))
        dao.insert(DbConversation(c1uuid,u1uuid,0, ConversationDistance("blah blah blah"),true))
        dao.insert(DbConversation(c2uuid,u2uuid,0,ConversationDistance("uhm uhm uhm"),true))
        dao.insert(DbConversation(c3uuid,u1uuid,0,ConversationDistance("meh meh meh"),true))
        dao.insert(DbConversation(c4uuid,u1uuid,0,ConversationDistance("good good good"),true))

        val c1 = dao.selectConversationByUuid(c1uuid)
        val c2 = dao.selectConversationByUuid(c2uuid)
        val c3 = dao.selectConversationByUuid(c3uuid)
        val c4 = dao.selectConversationByUuid(c4uuid)

        val t1c1 = dao.selectAllForOverviewOld(c1uuid)
        val t1c2 = dao.selectAllForOverview(c1uuid)
        val t1c3 = dao.selectAllForOverviewAlt(c1uuid)

        val t2c1 = dao.selectAllForOverviewOld(c2uuid)
        val t2c2 = dao.selectAllForOverview(c2uuid)
        val t2c3 = dao.selectAllForOverviewAlt(c2uuid)

        if (t1c1==null) t1c1==null /*<<<<<<<<<< BREAKPOINT HERE >>>>>>>>>>*/
    }
}

При запуске из новой установки окно отладки с t1 ?? а т2?? расширен: -

Как видно, все 3 запроса дают одинаковый результат. Таким образом, самым простым решением было бы

  • убедитесь, что вы используете библиотеки Room 2.5.0, И
  • используйте @Relation вместо @Embedded и используйте более краткий запрос, просто извлекая соответствующие DbConversation(s).

Комната действительно ужасна, что заставляет меня делать это. Почему он не десериализует столбцы по индексу, а по имени? В любом случае, я исправил эту проблему, добавив префикс столбцов, как было предложено... это работает, но да, это некрасиво :(

J. Doe 16.02.2023 08:49

@ J.Doe, если вы просто используете чистые объекты (также известные как @Relation), тогда нет необходимости а) кодировать JOIN или б) использовать префиксы. Лично я всегда использовал бы уникальные имена столбцов, возможно, с именем таблицы как частью имени. Не говорю, что вы должны, просто говорите, что можете.

MikeT 16.02.2023 08:56

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