Android房间如何查询相关table?

Android Room how to query related table?

首先,我的数据是POKEMON!!!享受

我需要在数据库端执行此操作,过滤和排序 returned 数据不是使用分页的选项...

我正在使用 Room 我的数据库运行良好,但我现在想查询我的关系中的 pokemonType 列表

鉴于此数据 class

data class PokemonWithTypesAndSpecies @JvmOverloads constructor(

    @Ignore
    var matches : Int = 0,

    @Embedded
    val pokemon: Pokemon,
    @Relation(
        parentColumn = Pokemon.POKEMON_ID,
        entity = PokemonType::class,
        entityColumn = PokemonType.TYPE_ID,
        associateBy = Junction(
            value = PokemonTypesJoin::class,
            parentColumn = Pokemon.POKEMON_ID,
            entityColumn = PokemonType.TYPE_ID
        )
    )
    val types: List<PokemonType>,
    @Relation(
        parentColumn = Pokemon.POKEMON_ID,
        entity = PokemonSpecies::class,
        entityColumn = PokemonSpecies.SPECIES_ID,
        associateBy = Junction(
            value = PokemonSpeciesJoin::class,
            parentColumn = Pokemon.POKEMON_ID,
            entityColumn = PokemonSpecies.SPECIES_ID
        )
    )
    val species: PokemonSpecies?
)

我可以通过简单的查询获取我的数据,甚至可以搜索它

@Query("SELECT * FROM Pokemon WHERE pokemon_name LIKE :search")
fun searchPokemonWithTypesAndSpecies(search: String): LiveData<List<PokemonWithTypesAndSpecies>>

但现在我想要的是添加对宠物小精灵类型的过滤,如您所见,它是一个列表(这可能是幕后交易)并且在一个单独的 table 中,所以给定一个列表我想要的名为过滤器的字符串:

所以我希望我的测试看起来像这样

val bulbasaurSpeciesID = 1
val squirtleSpeciesID = 2
val charmanderSpeciesID = 3
val charizardSpeciesID = 4
val pidgeySpeciesID = 5
val moltresSpeciesID = 6

val bulbasaurID = 1
val squirtleID = 2
val charmanderID = 3
val charizardID = 4
val pidgeyID = 5
val moltresID = 6

val grassTypeID = 1
val poisonTypeID = 2
val fireTypeID = 3
val waterTypeID = 4
val flyingTypeID = 5

val emptySearch = "%%"

val allPokemonTypes = listOf(
    "normal",
    "water",
    "fire",
    "grass",
    "electric",
    "ice",
    "fighting",
    "poison",
    "ground",
    "flying",
    "psychic",
    "bug",
    "rock",
    "ghost",
    "dark",
    "dragon",
    "steel",
    "fairy",
    "unknown",
)


@Before
fun createDb() {
    val context = ApplicationProvider.getApplicationContext<Context>()
    db = Room.inMemoryDatabaseBuilder(
        context, PokemonRoomDatabase::class.java,
    ).setTransactionExecutor(Executors.newSingleThreadExecutor())
        .allowMainThreadQueries()
        .build()
    pokemonDao = db.pokemonDao()
    speciesDao = db.pokemonSpeciesDao()
    speciesJoinDao = db.pokemonSpeciesJoinDao()
    pokemonTypeDao = db.pokemonTypeDao()
    pokemonTypeJoinDao = db.pokemonTypeJoinDao()
}

@After
@Throws(IOException::class)
fun closeDb() {
    db.close()
}

@Test
@Throws(Exception::class)
fun testFiltering() {

    insertPokemonForFilterTest()

    val pokemon =
        pokemonDao.searchAndFilterPokemon(search = emptySearch, filters = allPokemonTypes)
            .getValueBlocking(scope)

    assertThat(pokemon?.size, equalTo(6)) // This fails list size is 9 with the current query

    val pokemonFiltered =
        pokemonDao.searchAndFilterPokemon(search = emptySearch, filters = listOf("fire", "flying"))
            .getValueBlocking(scope)

    assertThat(pokemon?.size, equalTo(4))
    
    assertThat(pokemonFiltered!![0].pokemon.name, equalTo("charizard")) // matches 2 filters and ID is 4
    assertThat(pokemonFiltered!![1].pokemon.name, equalTo("moltres")) // matches 2 filters and ID is 6
    assertThat(pokemonFiltered!![2].pokemon.name, equalTo("charmander")) // matches one filter and ID is 3
    assertThat(pokemonFiltered!![3].pokemon.name, equalTo("pidgey")) // mayches one filter and ID is 5

}

private fun insertPokemonForFilterTest() = runBlocking {
    insertBulbasaur()
    insertSquirtle()
    insertCharmander()
    insertCharizard()
    insertMoltres()
    insertPidgey()
}



private fun insertBulbasaur() = runBlocking {

    val bulbasaur = bulbasaur()
    val grassJoin = PokemonTypesJoin(pokemon_id = bulbasaurID, type_id = grassTypeID)
    val poisonJoin = PokemonTypesJoin(pokemon_id = bulbasaurID, type_id = poisonTypeID)
    val bulbasaurSpeciesJoin =
        PokemonSpeciesJoin(pokemon_id = bulbasaurID, species_id = bulbasaurSpeciesID)

    pokemonDao.insertPokemon(bulbasaur.pokemon)

    speciesDao.insertSpecies(bulbasaur.species!!)
    speciesJoinDao.insertPokemonSpeciesJoin(bulbasaurSpeciesJoin)

    pokemonTypeDao.insertPokemonType(pokemonType = bulbasaur.types[0])
    pokemonTypeDao.insertPokemonType(pokemonType = bulbasaur.types[1])
    pokemonTypeJoinDao.insertPokemonTypeJoin(grassJoin)
    pokemonTypeJoinDao.insertPokemonTypeJoin(poisonJoin)
}

private fun insertSquirtle() = runBlocking {

    val squirtle = squirtle()
    val squirtleSpeciesJoin =
        PokemonSpeciesJoin(pokemon_id = squirtleID, species_id = squirtleSpeciesID)
    val waterJoin = PokemonTypesJoin(pokemon_id = squirtleID, type_id = waterTypeID)

    pokemonDao.insertPokemon(squirtle.pokemon)

    speciesDao.insertSpecies(squirtle.species!!)
    speciesJoinDao.insertPokemonSpeciesJoin(squirtleSpeciesJoin)

    pokemonTypeDao.insertPokemonType(pokemonType = squirtle.types[0])
    pokemonTypeJoinDao.insertPokemonTypeJoin(waterJoin)

}

private fun insertCharmander() = runBlocking {

    val charmander = charmander()
    val fireJoin = PokemonTypesJoin(pokemon_id = charmanderID, type_id = fireTypeID)
    val charmanderSpeciesJoin =
        PokemonSpeciesJoin(pokemon_id = charmanderID, species_id = charmanderSpeciesID)

    pokemonDao.insertPokemon(charmander.pokemon)
    speciesDao.insertSpecies(charmander.species!!)
    speciesJoinDao.insertPokemonSpeciesJoin(charmanderSpeciesJoin)
    pokemonTypeDao.insertPokemonType(pokemonType = charmander.types[0])
    pokemonTypeJoinDao.insertPokemonTypeJoin(fireJoin)
}

private fun insertCharizard() = runBlocking {

    val charizard = charizard()
    val charizardSpeciesJoin =
        PokemonSpeciesJoin(pokemon_id = charizardID, species_id = charizardSpeciesID)

    val fireJoin = PokemonTypesJoin(pokemon_id = charizardID, type_id = fireTypeID)
    val flyingJoin = PokemonTypesJoin(pokemon_id = charizardID, type_id = flyingTypeID)

    pokemonDao.insertPokemon(charizard.pokemon)

    speciesDao.insertSpecies(charizard.species!!)
    speciesJoinDao.insertPokemonSpeciesJoin(charizardSpeciesJoin)

    pokemonTypeDao.insertPokemonType(pokemonType = charizard.types[0])
    pokemonTypeDao.insertPokemonType(pokemonType = charizard.types[1])
    pokemonTypeJoinDao.insertPokemonTypeJoin(fireJoin)
    pokemonTypeJoinDao.insertPokemonTypeJoin(flyingJoin)
}

private fun insertPidgey() = runBlocking {

    val pidgey = pidgey()
    val pidgeySpeciesJoin =
        PokemonSpeciesJoin(pokemon_id = pidgeyID, species_id = pidgeySpeciesID)
    val flyingJoin = PokemonTypesJoin(pokemon_id = pidgeyID, type_id = flyingTypeID)

    pokemonDao.insertPokemon(pidgey.pokemon)

    speciesDao.insertSpecies(pidgey.species!!)
    speciesJoinDao.insertPokemonSpeciesJoin(pidgeySpeciesJoin)

    pokemonTypeDao.insertPokemonType(pokemonType = pidgey.types[0])
    pokemonTypeJoinDao.insertPokemonTypeJoin(flyingJoin)
}

private fun insertMoltres() = runBlocking {

    val moltres = moltres()
    val moltresSpeciesJoin =
        PokemonSpeciesJoin(pokemon_id = moltresID, species_id = moltresSpeciesID)

    val fireJoin = PokemonTypesJoin(pokemon_id = moltresID, type_id = fireTypeID)
    val flyingJoin = PokemonTypesJoin(pokemon_id = moltresID, type_id = flyingTypeID)

    pokemonDao.insertPokemon(moltres.pokemon)

    speciesDao.insertSpecies(moltres.species!!)
    speciesJoinDao.insertPokemonSpeciesJoin(moltresSpeciesJoin)

    pokemonTypeDao.insertPokemonType(pokemonType = moltres.types[0])
    pokemonTypeDao.insertPokemonType(pokemonType = moltres.types[1])
    pokemonTypeJoinDao.insertPokemonTypeJoin(fireJoin)
    pokemonTypeJoinDao.insertPokemonTypeJoin(flyingJoin)
}

fun bulbasaur(): PokemonWithTypesAndSpecies = PokemonWithTypesAndSpecies(
    pokemon = Pokemon(id = bulbasaurID, name = "bulbasaur"),
    species = PokemonSpecies(
        id = bulbasaurSpeciesID,
        species = "Seed pokemon",
        pokedexEntry = "There is a plant seed on its back right from the day this Pokémon is born. The seed slowly grows larger."
    ),
    types = listOf(
        PokemonType(id = poisonTypeID, name = "poison", slot = 1),
        PokemonType(id = grassTypeID, name = "grass", slot = 2)
    )
)

fun squirtle(): PokemonWithTypesAndSpecies = PokemonWithTypesAndSpecies(
    pokemon = Pokemon(id = squirtleID, name = "squirtle"),
    species = PokemonSpecies(
        id = squirtleSpeciesID,
        species = "Turtle pokemon",
        pokedexEntry = "Small shell pokemon"
    ),
    types = listOf(PokemonType(id = waterTypeID, name = "water", slot = 1))
)

fun charmander(): PokemonWithTypesAndSpecies = PokemonWithTypesAndSpecies(
    pokemon = Pokemon(id = charmanderID, name = "charmander"),
    species = PokemonSpecies(
        id = charmanderSpeciesID,
        species = "Fire lizard pokemon",
        pokedexEntry = "If the flame on this pokemon's tail goes out it will die"
    ),
    types = listOf(PokemonType(id = fireTypeID, name = "fire", slot = 1))
)

fun charizard(): PokemonWithTypesAndSpecies = PokemonWithTypesAndSpecies(
    pokemon = Pokemon(id = charizardID, name = "charizard"),
    species = PokemonSpecies(
        id = charizardSpeciesID,
        species = "Fire flying lizard pokemon",
        pokedexEntry = "Spits fire that is hot enough to melt boulders. Known to cause forest fires unintentionally"
    ),
    types = listOf(
        PokemonType(id = fireTypeID, name = "fire", slot = 1),
        PokemonType(id = flyingTypeID, name = "flying", slot = 2)
    )
)

fun moltres(): PokemonWithTypesAndSpecies = PokemonWithTypesAndSpecies(
    pokemon = Pokemon(id = moltresID, name = "moltres"),
    species = PokemonSpecies(
        id = moltresSpeciesID,
        species = "Fire bird pokemon",
        pokedexEntry = "Known as the legendary bird of fire. Every flap of its wings creates a dazzling flash of flames"
    ),
    types = listOf(
        PokemonType(id = fireTypeID, name = "fire", slot = 1),
        PokemonType(id = flyingTypeID, name = "flying", slot = 2)
    )
)

fun pidgey(): PokemonWithTypesAndSpecies = PokemonWithTypesAndSpecies(
    pokemon = Pokemon(id = pidgeyID, name = "pidgey"),
    species = PokemonSpecies(
        id = pidgeySpeciesID,
        species = "Bird pokemon",
        pokedexEntry = "Pidgey is a Flying Pokémon. Among all the Flying Pokémon, it is the gentlest and easiest to capture. A perfect target for the beginning Pokémon Trainer to test his Pokémon's skills."
    ),
    types = listOf(PokemonType(id = flyingTypeID, name = "flying", slot = 1))
)

查询将是

@Query("SELECT * FROM Pokemon INNER JOIN PokemonType, PokemonTypesJoin ON Pokemon.pokemon_id = PokemonTypesJoin.pokemon_id AND PokemonType.type_id = PokemonTypesJoin.type_id WHERE pokemon_name LIKE :search AND type_name IN (:filters) ORDER BY pokemon_id ASC")
fun searchAndFilterPokemon(search: String, filters: List<String>): LiveData<List<PokemonWithTypesAndSpecies>>

我猜这是行不通的,因为此时 Room 还没有从另一个 table 收集类型,它甚至可能没有查询列表,我认为这部分

type_name IN (:filters)

正在对照列表检查列,而我想要的是对照列表的列表 ‍♂️ 但老实说,我很高兴只是说我已经跌倒了,无法起床有人可以帮忙吗?任何帮助表示赞赏

您是否将 android 的 Room 与 Cmobilecom-JPA 进行了比较? JPA 非常擅长查询关系。使用 JPA(标准)的优势很明显,使您的代码可在 android、服务器端 java 或 swing 项目上重用。

也许我可以滥用某些列的名称,但试试这个查询:

@Query("SELECT pok.id, pok.name FROM Pokemon AS pok
INNER JOIN PokemonTypesJoin AS p_join ON pok.id = p_join.pokemon_id
INNER JOIN PokemonType AS pok_type ON pok_type.id = p_join.type_id
WHERE pok.name LIKE :search AND pok_type.name IN (:filters) 
GROUP BY pok.id, pok.name ORDER BY count(*) DESC, pok.id ASC")

感谢@serglytikhonov 我的查询成功了,现在看起来像这样

@Query("""SELECT * FROM Pokemon 
                 INNER JOIN PokemonTypesJoin 
                 ON Pokemon.pokemon_id = PokemonTypesJoin.pokemon_id 
                 INNER JOIN PokemonType 
                 ON PokemonType.type_id = PokemonTypesJoin.type_id 
                 WHERE pokemon_name LIKE :search AND type_name IN (:filters)
                 GROUP BY Pokemon.pokemon_id, Pokemon.pokemon_name
                 ORDER BY count(*) DESC, pokemon_id ASC""")
fun searchAndFilterPokemon(search: String, filters: List<String>): LiveData<List<PokemonWithTypesAndSpecies>>

主要部分是这个计数(*)和分组非常感谢