所以,我有一个关于动漫应用程序的案例,其中一个流派可以是多个动漫,而一个动漫可以有多个流派。所以,我把它变成了
Many to Many Relationship
并使用 Room Database
将它们与交叉引用连接起来。
我的动漫实体:
@Entity(
tableName = "anime",
indices = [Index(value = ["mal_id"], unique = true)]
)
data class AnimeBasicEntity(
@PrimaryKey
@ColumnInfo(name = "mal_id")
val malId: Int,
// Other properties, not included because too long and might not related
)
我的类型实体:
@Entity(
tableName = "genre",
indices = [Index(value = ["mal_id"], unique = true)]
)
data class AnimeGenreEntity(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "mal_id")
val malId: Int,
// Other properties, not included because too long and might not related
)
还有我的交叉参考:
@Entity(
tableName = "anime_genre_cross_reference",
primaryKeys = ["anime_id", "genre_id"],
foreignKeys = [
ForeignKey(
entity = AnimeBasicEntity::class,
parentColumns = ["mal_id"],
childColumns = ["anime_id"],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = AnimeGenreEntity::class,
parentColumns = ["mal_id"],
childColumns = ["genre_id"],
onDelete = ForeignKey.CASCADE
),
]
)
data class AnimeGenreCrossReference(
@ColumnInfo(name = "anime_id", index = true)
val animeId: Int,
@ColumnInfo(name = "genre_id", index = true)
val genreId: Int
)
我为返回结果做了一个包装,如下所示:
data class AnimeFullEntity(
@Embedded
val anime: AnimeBasicEntity,
@Relation(
parentColumn = "mal_id",
entityColumn = "mal_id",
associateBy = Junction(
value = AnimeGenreCrossReference::class,
parentColumn = "anime_id",
entityColumn = "genre_id"
)
)
val genres: List<AnimeGenreEntity>
)
这是我的
DAO
:
@Transaction
@Query("SELECT * FROM anime WHERE mal_id =:malId LIMIT 1")
suspend fun getAnimeByAnimeId(malId: Int): AnimeFullEntity
我尝试像这样使用
CROSS JOIN
和 INNER JOIN
:
@Transaction
@Query(
"SELECT * FROM anime " +
"CROSS JOIN anime_genre_cross_reference ON anime_id=:malId " +
"WHERE mal_id =:malId"
)
suspend fun getAnimeByAnimeId(malId: Int): AnimeFullEntity
查询成功,但查询期间发生某种非致命错误。我似乎无法追踪它,因为它说该位置超出了我的代码行(错误发生在第 207 行,而我的代码在 150 左右)。但是,当我返回到上一个查询时,查询返回一个空列表。
我认为您可能会对 whwre 使用 mal_id 值感到困惑,因为您通过使其等于传递的值来限制 JOIN,而实际上 JOIN 可能应该是当一列等于另一列时。
也许考虑对第二个查询进行以下微妙的更改:-
SELECT * FROM anime CROSS JOIN anime_genre_cross_reference ON anime_id=mal_id WHERE mal_id =:malId
- i.e. `ON anime_id (column name) = mal_id (column name NOT passed value)
- Which may sometimes result in nothing matching the JOIN
但是,当使用
@Query
注释的 SELECT 时这样做没有任何好处,因为 Room 只关心 AnimeBasic,然后它使用 @Relation 通过结果的交叉引用(关联)表获取 AnimeGenres动漫完整。
您似乎也有重复的索引。当使用
@Primary
键时,这是一个唯一索引。在复合索引的情况下,索引的第一列根据该列,因此不需要在该列上也有索引。在第二列上有一个索引可能是个好主意(如果省略,Room 会发出警告)。
示范
考虑以下
@Dao
带注释的界面:-
@Dao
interface AllDAOs {
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(animeBasicEntity: AnimeBasicEntity): Long
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(animeGenreEntity: AnimeGenreEntity): Long
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(animeGenreCrossReference: AnimeGenreCrossReference): Long
@Transaction
@Query("SELECT * FROM anime WHERE mal_id =:malId LIMIT 1")
/*suspend*/ fun getAnimeByAnimeId(malId: Int): AnimeFullEntity
@Transaction
@Query("SELECT * FROM anime CROSS JOIN anime_genre_cross_reference ON anime_id=mal_id WHERE mal_id =:malId")
fun getAnimeByAnimeIdOther(malId: Int): AnimeFullEntity
@Transaction
@Query("SELECT * FROM anime CROSS JOIN anime_genre_cross_reference ON anime_id=:malId WHERE mal_id =:malId")
fun getAnimeByAnimeIdOriginal(malId: Int): AnimeFullEntity
}
然后是一些活动代码:-
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 ab01id = dao.insert(AnimeBasicEntity(10))
val ab02id = dao.insert(AnimeBasicEntity(20))
val ag01id = dao.insert(AnimeGenreEntity(100))
val ag02id = dao.insert(AnimeGenreEntity(200))
dao.insert(AnimeGenreCrossReference(ab01id.toInt(),ag01id.toInt()))
dao.insert(AnimeGenreCrossReference(ab01id.toInt(),ag02id.toInt()))
var af = dao.getAnimeByAnimeId(ab01id.toInt())
var sb = StringBuilder()
for (g in af.genres) {
sb.append("\n\t${g.malId}")
}
Log.d("DBINFO","AF has abid as ${af.anime.malId} and has ${af.genres.size} Genres. They are:-${sb}")
af = dao.getAnimeByAnimeIdOther(ab01id.toInt())
sb = StringBuilder()
for (g in af.genres) {
sb.append("\n\t${g.malId}")
}
Log.d("DBINFO","AF has abid as ${af.anime.malId} and has ${af.genres.size} Genres. They are:-${sb}")
af = dao.getAnimeByAnimeIdOriginal(ab01id.toInt())
sb = StringBuilder()
for (g in af.genres) {
sb.append("\n\t${g.malId}")
}
Log.d("DBINFO","AF has abid as ${af.anime.malId} and has ${af.genres.size} Genres. They are:-${sb}")
}
}
运行时日志包括:-
2024-01-29 16:46:39.934 D/DBINFO: AF has abid as 10 and has 2 Genres. They are:-
100
200
2024-01-29 16:46:39.941 D/DBINFO: AF has abid as 10 and has 2 Genres. They are:-
100
200