Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

다대다 구조 Relation을 이용해서 구현 / Fix: Foreign Key Constraint Fail… #53

Merged
merged 3 commits into from
Nov 23, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 23 additions & 14 deletions data/src/main/java/com/wakeup/data/database/dao/MomentDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,32 @@ import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import com.wakeup.data.database.entity.GlobeEntity
import com.wakeup.data.database.entity.MomentEntity
import com.wakeup.data.database.entity.MomentGlobeEntity
import com.wakeup.data.database.entity.MomentPictureEntity
import com.wakeup.data.database.entity.MomentWithGlobesAndPictures
import com.wakeup.data.database.entity.PictureEntity

@Dao
interface MomentDao {

@Transaction
@Query(
"""
SELECT * FROM moment
WHERE mainAddress LIKE '%' || :query || '%'
WHERE mainAddress LIKE '%' || :query || '%'
OR detailAddress LIKE '%' || :query || '%'
OR content LIKE '%' || :query || '%'
ORDER BY
CASE WHEN :sortType = 0 THEN date END DESC,
CASE WHEN :sortType = 1 THEN date END ASC
"""
)
fun getMoments(sortType: Int = 0, query: String): PagingSource<Int, MomentEntity>
fun getMoments(sortType: Int = 0, query: String): PagingSource<Int, MomentWithGlobesAndPictures>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

검색 기능이 이미 구현된건가요? 제 담당이긴 한데 😅
query 없이 모먼트를 가져오려면, query = ""를 파라미터 값으로 넘겨줘야 할까요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

검색 기능이 재민님이 구현하신 내용인데 검색 없이는 "" 맞습니다!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

엇 검색 쿼리는 원래 있었는데 저는 정렬만 추가했습니다!


@Transaction
@Query(
"""
SELECT *,
Expand All @@ -38,26 +43,30 @@ interface MomentDao {
ORDER BY distance
"""
)
fun getMomentsByNearestDistance(query: String, lat: Double?, lng: Double?): PagingSource<Int, MomentEntity>
fun getMomentsByNearestDistance(
query: String,
lat: Double?,
lng: Double?,
): PagingSource<Int, MomentWithGlobesAndPictures>

@Query(
"SELECT * FROM picture WHERE id IN" +
"(SELECT picture_id FROM moment_picture WHERE moment_id = :momentId)"
)
suspend fun getPictures(momentId: Long): List<PictureEntity>
@Query("SELECT globe_entity_id FROM globe WHERE name = :globeName")
suspend fun getGlobeIdByName(globeName: String): Long

@Query(
"SELECT * FROM globe WHERE id IN " +
"(SELECT globe_id FROM moment_globe WHERE moment_id = :momentId)"
)
suspend fun getGlobes(momentId: Long): List<GlobeEntity>
@Query("SELECT picture_entity_id FROM picture WHERE bitmap = :bitmap")
suspend fun getPictureIdByByteArray(bitmap: ByteArray): Long

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun saveMoment(moment: MomentEntity): Long

@Insert(onConflict = OnConflictStrategy.REPLACE)
@Insert(onConflict = OnConflictStrategy.IGNORE)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IGNORE 가 존재했군요!!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 들어가는 걸 update하는 것이 아니라 ignore하는 겁니다! 들어간다면, 업데이트된 id를 다시 잇는 과정보다는 찾는 과정이 더 빠를 것 같아서 선택했습니다

suspend fun savePicture(picture: List<PictureEntity>): List<Long>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

헷갈리지 않도록 s를 추가해서 savePictures()로 변경하는게 어떨까요?

Copy link
Member Author

@Choe-Ji-Hwan Choe-Ji-Hwan Nov 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 좋습니다!


@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun saveMomentPicture(momentPictures: List<MomentPictureEntity>)
Copy link
Member

@BBongKim BBongKim Nov 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기도 같은 맥락으로 s 를 추가해서 saveMomentPictures() 어떨까요?

뭔가 Picture에만 s가 붙는게 이상하게 느껴지신다면,

MomentPictureCrossRefEntity 또는 MomentPictureXRefEntity 와 같이 Entity이름을 변경하신다면

saveMomentPictureCrossRefs()로 표기할 수 도 있을 것 같습니다.

공식 문서에도 사용하는 네이밍 방식입니다.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

최대한 폴더 이름과 같게 하려고 했어요 entity 폴더니까 entitiy로 끝내야된다..? 따라서 그런데 공식 문서처럼 바꾸는 것이 좋은 네이밍 같아요. 뒤에만 s붙이는건 오해의 여지가 있으니까..? 변경하겠습니다.
MomentGlobeEntity -> MomentGlobeXRef
MomentPictureEntity -> MomentPictureXRef

함수 네이밍

suspend fun saveMomentPictures(momentPictures: List<MomentPictureXRef>)
->
suspend fun saveMomentPictureXRefs(momentPictures: List<MomentPictureXRef>)
suspend fun saveMomentGlobe(momentGlobe: MomentGlobeXRef)
->
suspend fun saveMomentGlobeXRef(momentGlobe: MomentGlobeXRef)


@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun saveGlobes(globes: List<GlobeEntity>): List<Long>

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun saveMomentGlobe(momentGlobe: MomentGlobeEntity)
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ import androidx.room.PrimaryKey
indices = [Index(value = ["name"], unique = true)]
)
data class GlobeEntity(
@PrimaryKey(autoGenerate = true) val id: Long = 0L,
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "globe_entity_id") val id: Long = 0L,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

모든 entity 의 id 를 동일하게 한다면, 중간에 entity 를 빼고 동일하게 해도 좋을 것 같습니다!🙂

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 아까 이야기한 것처럼 id 이름에 entity 모두 제거했습니다!

@ColumnInfo(name = "name") val name: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import androidx.room.PrimaryKey
foreignKeys = [
ForeignKey(
entity = PictureEntity::class,
parentColumns = ["id"],
parentColumns = ["picture_entity_id"],
childColumns = ["thumbnail_id"],
onDelete = CASCADE
onUpdate = CASCADE
)
]
)
data class MomentEntity(
@PrimaryKey(autoGenerate = true) val id: Long = 0L,
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "moment_entity_id") val id: Long = 0L,
@Embedded val place: PlaceEntity,
@ColumnInfo(name = "thumbnail_id") val thumbnailId: Long?,
@ColumnInfo(name = "content") val content: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,26 @@ package com.wakeup.data.database.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey

@Entity(
tableName = "moment_globe",
foreignKeys = [
ForeignKey(
entity = MomentEntity::class,
parentColumns = ["id"],
childColumns = ["moment_id"],
onDelete = ForeignKey.CASCADE
parentColumns = ["moment_entity_id"],
childColumns = ["moment_entity_id"],
onUpdate = ForeignKey.CASCADE
),
ForeignKey(
entity = GlobeEntity::class,
parentColumns = ["id"],
childColumns = ["globe_id"],
onDelete = ForeignKey.CASCADE
parentColumns = ["globe_entity_id"],
childColumns = ["globe_entity_id"],
onUpdate = ForeignKey.CASCADE
)
]
],
primaryKeys = ["moment_entity_id", "globe_entity_id"]
)
data class MomentGlobeEntity(
@PrimaryKey(autoGenerate = true) val id: Long = 0L,
@ColumnInfo(name = "moment_id", index = true) val momentId: Int,
@ColumnInfo(name = "globe_id", index = true) val globeId: Int,
@ColumnInfo(name = "moment_entity_id", index = true) val momentId: Long,
@ColumnInfo(name = "globe_entity_id", index = true) val globeId: Long,
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,26 @@ package com.wakeup.data.database.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey

@Entity(
tableName = "moment_picture",
foreignKeys = [
ForeignKey(
entity = MomentEntity::class,
parentColumns = ["id"],
childColumns = ["moment_id"],
onDelete = ForeignKey.CASCADE
parentColumns = ["moment_entity_id"],
childColumns = ["moment_entity_id"],
onUpdate = ForeignKey.CASCADE
),
ForeignKey(
entity = PictureEntity::class,
parentColumns = ["id"],
childColumns = ["picture_id"],
onDelete = ForeignKey.CASCADE
parentColumns = ["picture_entity_id"],
childColumns = ["picture_entity_id"],
onUpdate = ForeignKey.CASCADE
)
]
],
primaryKeys = ["moment_entity_id", "picture_entity_id"]
)
data class MomentPictureEntity(
@PrimaryKey(autoGenerate = true) val id: Long = 0L,
@ColumnInfo(name = "moment_id", index = true) val momentId: Long,
@ColumnInfo(name = "picture_id", index = true) val pictureId: Long,
@ColumnInfo(name = "moment_entity_id", index = true) val momentId: Long,
@ColumnInfo(name = "picture_entity_id", index = true) val pictureId: Long,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.wakeup.data.database.entity

import androidx.room.Embedded
import androidx.room.Junction
import androidx.room.Relation

data class MomentWithGlobesAndPictures(
@Embedded val moment: MomentEntity,
@Relation(
parentColumn = "moment_entity_id",
entity = GlobeEntity::class,
entityColumn = "globe_entity_id",
associateBy = Junction(MomentGlobeEntity::class)
)
val globeList: List<GlobeEntity>,
@Relation(
parentColumn = "moment_entity_id",
entity = PictureEntity::class,
entityColumn = "picture_entity_id",
associateBy = Junction(MomentPictureEntity::class)
)
val pictureList: List<PictureEntity>,
)
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import androidx.room.PrimaryKey
indices = [Index(value = ["bitmap"], unique = true)]
)
data class PictureEntity(
@PrimaryKey(autoGenerate = true) val id: Long = 0L,
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "picture_entity_id") val id: Long = 0L,
@ColumnInfo(name = "bitmap") val bitmap: ByteArray,
) {
override fun equals(other: Any?): Boolean {
Expand All @@ -30,4 +30,5 @@ data class PictureEntity(
result = 31 * result + bitmap.contentHashCode()
return result
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.wakeup.data.database.mapper

import com.wakeup.data.database.entity.GlobeEntity
import com.wakeup.data.database.entity.MomentEntity
import com.wakeup.data.database.entity.MomentWithGlobesAndPictures
import com.wakeup.data.database.entity.PictureEntity
import com.wakeup.domain.model.Moment
import com.wakeup.domain.model.Place
Expand All @@ -17,6 +18,18 @@ fun MomentEntity.toDomain(pictures: List<PictureEntity>, globes: List<GlobeEntit
)
}

fun MomentWithGlobesAndPictures.toDomain(pictures: List<PictureEntity>, globes: List<GlobeEntity>): Moment {
return Moment(
id = moment.id,
place = moment.place.toDomain(),
pictures = pictures.map { it.toDomain() },
content = moment.content,
globes = globes.map { it.toDomain() },
date = moment.date
)
}


fun Moment.toEntity(place: Place, thumbnailId: Long?): MomentEntity {
return MomentEntity(
place = place.toEntity(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,47 @@ package com.wakeup.data.repository

import androidx.paging.PagingData
import androidx.paging.map
import com.wakeup.data.database.entity.GlobeEntity
import com.wakeup.data.database.entity.MomentGlobeEntity
import com.wakeup.data.database.entity.MomentPictureEntity
import com.wakeup.data.database.mapper.toDomain
import com.wakeup.data.database.mapper.toEntity
import com.wakeup.data.source.local.moment.MomentLocalDataSource
import com.wakeup.domain.model.Location
import com.wakeup.domain.model.Moment
import com.wakeup.domain.model.Place
import com.wakeup.domain.model.SortType
import com.wakeup.domain.model.Picture
import com.wakeup.domain.repository.MomentRepository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import timber.log.Timber
import kotlinx.coroutines.launch
import javax.inject.Inject

class MomentRepositoryImpl @Inject constructor(
private val localDataSource: MomentLocalDataSource,
) : MomentRepository {

override fun getMoments(sort: SortType, query: String, myLocation: Location?): Flow<PagingData<Moment>> =
init {
// todo: globe_test_code (must remove to release)
CoroutineScope(Dispatchers.IO).launch {
localDataSource.saveGlobes(listOf(
GlobeEntity(name = "default"),
GlobeEntity(name = "globe 1"),
GlobeEntity(name = "globe 2"),
GlobeEntity(name = "globe 3"))
)
}
}

override fun getMoments(
sort: SortType,
query: String,
myLocation: Location?,
): Flow<PagingData<Moment>> =
localDataSource.getMoments(sort, query, myLocation?.toEntity()).map { pagingData ->
pagingData.map { momentEntity ->
momentEntity.toDomain(
localDataSource.getPictures(momentEntity.id),
localDataSource.getGlobes(momentEntity.id)
)
pagingData.map { momentInfo ->
momentInfo.toDomain(momentInfo.pictureList, momentInfo.globeList)
}
}

Expand All @@ -37,15 +52,20 @@ class MomentRepositoryImpl @Inject constructor(
return
} else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분에 else가 필요 없어 보입니다. 😃

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞네요 early return

val pictureIndexes =
localDataSource.savePicture(moment.pictures.map { it.toEntity() })
localDataSource.savePictures(moment.pictures.map { it.toEntity() })
// 정책: moment 추가할 때 항상 globe 하나 선택해서 추가(default 도 하나 선택해서 추가 임).
val globeIndex = localDataSource.getGlobeId(moment.globes[0].name)
val momentIndex =
localDataSource.saveMoment(moment.toEntity(moment.place, pictureIndexes[0]))

localDataSource.saveMomentPicture(
localDataSource.saveMomentPictures(
pictureIndexes.map { pictureId ->
MomentPictureEntity(momentId = momentIndex, pictureId = pictureId)
}
)
localDataSource.saveMomentGlobe(
MomentGlobeEntity(momentId = momentIndex, globeId = globeIndex)
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,32 @@ package com.wakeup.data.source.local.moment

import androidx.paging.PagingData
import com.wakeup.data.database.entity.GlobeEntity
import com.wakeup.data.database.entity.LocationEntity
import com.wakeup.data.database.entity.MomentEntity
import com.wakeup.data.database.entity.MomentGlobeEntity
import com.wakeup.data.database.entity.MomentPictureEntity
import com.wakeup.data.database.entity.MomentWithGlobesAndPictures
import com.wakeup.data.database.entity.PictureEntity
import com.wakeup.data.database.entity.LocationEntity
import com.wakeup.domain.model.SortType
import kotlinx.coroutines.flow.Flow

interface MomentLocalDataSource {

fun getMoments(sortType: SortType, query: String, myLocation: LocationEntity?): Flow<PagingData<MomentEntity>>
fun getMoments(sortType: SortType, query: String, myLocation: LocationEntity?): Flow<PagingData<MomentWithGlobesAndPictures>>

/* suspend fun getPictures(momentId: Long): List<PictureEntity>

suspend fun getPictures(momentId: Long): List<PictureEntity>
suspend fun getGlobes(momentId: Long): List<GlobeEntity>*/

suspend fun getGlobes(momentId: Long): List<GlobeEntity>
suspend fun getGlobeId(globeName: String): Long

suspend fun saveMoment(moment: MomentEntity): Long

suspend fun savePicture(picture: List<PictureEntity>): List<Long>
suspend fun savePictures(pictures: List<PictureEntity>): List<Long>

suspend fun saveMomentPictures(momentPictures :List<MomentPictureEntity>)

suspend fun saveGlobes(globes: List<GlobeEntity>): List<Long>

suspend fun saveMomentPicture(MomentPictures :List<MomentPictureEntity>)
suspend fun saveMomentGlobe(momentGlobe :MomentGlobeEntity)
}
Loading