๐ MongoRepository Custom ๋ฐ $elemMatch ์ฌ์ฉํ๊ธฐ
์ต๊ทผ ์ฌ์ด๋ ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉด์ MongoDB๋ฅผ ์ฒ์์ผ๋ก ์ฌ์ฉํด๋ณด๊ณ ์์ต๋๋ค.
๊ฐ๋จํ ๋ก์ง์ Query Method๋ @Query ์ด๋ ธํ ์ด์ ์ผ๋ก ๊ฐ๋จํ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํด์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์์ผ๋, MongoDB์ ๊ฒฝ์ฐ ๋ฌธ๋ฒ์ ์ต์์น ์๋ค๋ณด๋ ์ง์ Repository๋ฅผ ์ปค์คํ ์ผ๋ก ์์ฑํด์ ๊ด๋ฆฌํ๋๊ฒ ๋ณด๋ค ํธ๋ฆฌํ ๋ฏ ํ์ฌ ์ปค์คํ ๋ ํฌ์งํ ๋ฆฌ๋ฅผ ๋ง๋๋ ๋ฐฉ๋ฒ์ ๋ํด ์๊ฐํ๊ณ ์ ํฉ๋๋ค.
์ถ๊ฐ์ ์ผ๋ก ์ฝ๊ฐ ์ฝ์ง์ํ๋ Query ๋ฐ Criteria, $elemMatch๋ฅผ ํตํด MongoDB์์ Array field๋ฅผ ๊ฒ์ํ๋ ๋ฐฉ๋ฒ์ ๋ํด์๋ ์ถ๊ฐ์ ์ผ๋ก ์ดํด๋ณด๊ฒ ์ต๋๋ค.
๋ฌผ๋ก QueryDSL์ ์ ์ฉํ๋ฉด ์ข ๋ ํธํ๊ฒ ์์ฑ์ด ๊ฐ๋ฅํ ๊ฒ ๊ฐ์ผ๋, ์ถํ ๋ณ๊ฒฝํ๋๋ก ํ๊ณ ์ด๋ฒ์๋ Custom Repository๋ง ์์ฑํด๋ณด๊ฒ ์ต๋๋ค.
(ํฌ์คํ ์ ํ๋ฆฐ ๋ด์ฉ์ด ์กด์ฌํ ์ ์์ผ๋ ํผ๋๋ฐฑ ์ฃผ์๋ฉด ๊ฐ์ฌํ๊ฒ ์ต๋๋ค. ๐)
๐ ๋ถ์
ํ์ฌ ํ๋ก์ ํธ์์ ์ฌ์ฉ์ค์ธ Bookmark Entity์ ํ๋๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
(์๋ฌด๋๋ NoSQL์ ํน์ฑ ์ ํ๋์ ํ ์ด๋ธ ๋ฐ ํ๋์ ์ฟผ๋ฆฌ๋ก ์ฌ๋ฌ๊ฐ์ง๋ค์ ํด๊ฒฐํ๋ ค๋ค ํ๋ค๋ณด๋ Big Table์ด ๋ ๋ฏ ํ ๋๋์ ๋๋ค..๐)
reimnd๋ Array ํ์์ผ๋ก ๋ด๋ถ์ userId, remindTime, fcmToken, remindCheck, remindStatus ํ๋๊ฐ ์กด์ฌํฉ๋๋ค.
์ ํ๋์์ remind์ ๊ฒฝ์ฐ ๊ธฐ์กด์๋ Bookmark์ 1:1 ๋งคํ์ด์์ง๋ง, ์ถ๊ฐ ๊ธฐ๋ฅ์ ๊ตฌํํ๋ฉด์ Bookmark์ Remind์ ์ฐ๊ด๊ด๊ณ๊ฐ 1:N์ด ๋์๊ณ , ์ด๋ก ์ธํด ๋ค์๊ณผ ๊ฐ์ ๊ณ ๋ฏผ์ด ์๊ฒผ์ต๋๋ค.
- Embedding vs Referencing
์ ์์ ๊ฒฝ์ฐ ํ๋์ ํ ์ด๋ธ์์ ๋ชจ๋ ๊ฒ์ ๊ด๋ฆฌํ๋ ๋ฐ๋ฉด, ํ์์ ๊ฒฝ์ฐ ์ผ๋ฐ์ ์ผ๋ก RDBMS์์ ์ฌ์ฉํ๋ ์ฌ๋ฌ ํ ์ด๋ธ๊ฐ JOIN์ ์ฌ์ฉํ๋ ๋ฐฉ์์ด๋ผ๊ณ ์๊ฐํ์๋ฉด ๋ฉ๋๋ค.
์ ๋ Embedding๋ฅผ ์ ํํ์ฌ Remind์ ๊ฒฝ์ฐ Array๋ก ๊ด๋ฆฌํ๊ณ ์ ํ๋๋ฐ์, ์๋์์ ์ค๋ช ๋๋ฆฌ๊ฒ ์ง๋ง ๊ฐ์ฅ ํฐ ์ด์ ๋ ๋จ์ผ ์ฟผ๋ฆฌ๋ก ๋ชจ๋ ๊ด๋ จ๋ ์ ๋ณด๋ฅผ ์ป์ ์ ์์ด ์ฑ๋ฅ์์ ์ด์ ์ด ์กด์ฌํ๋ค๊ณ ์๊ฐ์ ํ์ต๋๋ค.
MongoDB Document์์ ์์ ๊ฐ์ ๋ฌธ์ ์ ๋ํด ํด๊ฒฐ์์ ์ค๋ช ํ๊ณ ์๋๋ฐ์, ์ ๋ฆฌํ๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
Embedding
Advantages
- ๋จ์ผ ์ฟผ๋ฆฌ๋ก ๋ชจ๋ ๊ด๋ จ๋ ์ ๋ณด๋ค์ ์ป์ ์ ์์ต๋๋ค.
- ์ดํ๋ฆฌ์ผ์ด์ ์ฝ๋์ joins์ด๋ $lookup๊ณผ ๊ฐ์ ์ฝ๋ ๊ตฌํ์ ํผํ ์ ์์ต๋๋ค.
- ๋จ์ผ ์์์ฑ์ ์์ ์ผ๋ก ๊ด๋ จ ์ ๋ณด๋ค์ ์ ๋ฐ์ดํธ ํ ์ ์์ต๋๋ค.
- ๋ฉํฐ ์์ ์ผ๋ก ์ธํ ํธ๋์ญ์ ์ด ํ์ํ๋ค๋ฉด, transaction operator๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
Limitations
- ๋ง์ฝ ๋ง์ ํ๋๊ฐ ์ฐ๊ด์ฑ์ด ์๋ค๋ฉด, documents๊ฐ ์ปค์ง์๋ก ์ค๋ฒํค๋๊ฐ ๋ ๋ฐ์ํฉ๋๋ค.
- MongoDB์๋ 16MB์ Document Size ์ ํ์ด ์กด์ฌํฉ๋๋ค. ๋ฐ๋ผ์, ๋จ์ผ documen์ ๋๋ฌด ๋ง์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ ๊ฒฝ์ฐ ์ ์ฌ์ ์ผ๋ก ์ด๋ฌํ ์ ํ์ ๋๋ฌํ ์ ์์ต๋๋ค.
Referencing
Advantages
- ๋ฐ์ดํฐ๋ฅผ ๋๋๊ธฐ์ ๋ณด๋ค ์์ documents๋ฅผ ์ ์งํ ์ ์์ต๋๋ค.
- ์์์ ์ธ๊ธํ 16MB Documnet Size ์ ํ์ ๋๋ฌํ ๊ฐ๋ฅ์ฑ์ด ๋ฎ์ต๋๋ค.
- ์์ฃผ ์ ๊ทผํ์ง ์๋ ์ ๋ณด๋ ์ฟผ๋ฆฌ์ ํ์ํ์ง ์์ต๋๋ค.
- ๋ฐ์ดํฐ ์ค๋ณต์ด ๊ฐ์ํฉ๋๋ค.
Limitations
- ๊ด๋ จ documents์ ๋ฐ์ดํฐ๋ฅผ ์ป๊ธฐ ์ํด ์ต์ ๋ ๋ฒ์ ์ฟผ๋ฆฌ ํน์ $lookup์ด ํ์ํฉ๋๋ค.
์ ๋ I/O Call์ ์ค์ด๊ณ ์ Embedding์ ์ฌ์ฉํ๊ณ , ๊ฒฐ๊ตญ ์ ๋ต์ ์๋ ํธ๋ ์ด๋ ์คํ์ด๊ธฐ์ ์ํฉ์ ๋ง๋ ์ ํ์ ํ๋ฉด ๋ ๋ฏ ํฉ๋๋ค.
ํน์ ๋น์ทํ ์ํฉ์ด์๋ผ๋ฉด ์์ ๋งํฌ๋ก ์ฒจ๋ถํด๋์๋ MongoDB Document์ ์์ธํ ์ค๋ช ์ด ๋์์์ผ๋ ์ฐธ๊ณ ํ์๋ฉด ๋ฉ๋๋ค!
๐ ๋ฌธ์
ํ์ฌ Documents์ ์ ์ฅ๋์ด ์๋ ๋ฐ์ดํฐ ํ์์ ์๋์ ๊ฐ์ต๋๋ค.
์์์ remindList Array ๋ด์์ userId(+remindCheck & remindStatus)์ ์กฐ๊ฑด์ ๋ฐ๋ผ ํด๋น ์์๋ง ๊ฐ์ ธ์์ผ ํ๋๋ฐ์,
๊ธฐ์กด์ Query Method๋ @Query์ ๋ฐฉ์์ผ๋ก๋ ๋ฆฌ์คํธ์์ userId๊ฐ ํฌํจํ๋ ๋ชจ๋ ์์๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฌธ์ ๊ฐ ์์์ต๋๋ค.
์๋ฅผ๋ค์ด Array๋ด์ userId = 26์ธ ์ฟผ๋ฆฌ๋ฅผ ์กฐํํ๋ฉด 0๋ฒ์งธ ์์์ธ ๋นจ๊ฐ์ ๋ค๋ชจ๋ฐ์ค๋ง ํ์ํ์ง๋ง, ์ค์ ๊ฒฐ๊ณผ๋ ํ๋์ ๋ค๋ชจ๋ฐ์ค์ธ ์ ์ฒด๊ฐ ๋์ค๋ ๋ฌธ์ ๊ฐ ์์๋๋ฐ์, ์ฌ์ฉํ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
@Query(value = "{\$and: [{remindList: {\$elemMatch: {userId: ?0}}}, {remindList: {\$elemMatch: {remindCheck: false}}}, {remindList: {\$elemMatch: {remindCheck: false}}} ]}")
fun findAllBookmarkByUserIds(userId: Long): List<Bookmark>
์ฟผ๋ฆฌ์.. ์๋ ๋ง์ ๊ดํธ๋ค์ด ์ฌ์ฉํ๋ค๋ณด๋ ๊ฐ์ธ์ ์ผ๋ก๋ ๋๋ฌด ๋ณต์กํ๊ณ ๊ฐ๋ ์ฑ์ด ๋จ์ด์ง๊ธฐ์ MongoRepository๋ฅผ Customํด์ ๊ตฌํํด์ฃผ์์ต๋๋ค.
(์ํ๋ ๊ฒฐ๊ณผ๊ฐ์ด ์๋์จ๊ฑด ์๋ฌด๋๋ ์ฟผ๋ฆฌ์ ๋ฌธ์ ๊ฐ ์๋ ๋ฏ ํ๋ฐ ์์ธํ๋ ๋ชจ๋ฅด๊ฒ ๋ค์.. ๐ญ)
๐ ๊ตฌ์กฐ
๊ตฌ์กฐ๋ ๋งค์ฐ ๋จ์ํ๋ฐ์ ๊ธฐ์กด์ MongoRepository๋ฅผ ์์๋ฐ์์ ์ฌ์ฉ์ค์ธ BookmarkRepository ์ธํฐํ์ด์ค๊ฐ ์กด์ฌํ๊ณ , ์ถ๊ฐ์ ์ผ๋ก Customํ๊ณ ์ ํ๋ ์ธํฐํ์ด์ค(MongoTemplateRepository)๋ฅผ ์์ฑํ๊ณ ์ด๋ฅผ ๊ตฌํํ๋ ํด๋์ค(BookmarkRepositoryImpl)์ ์์ฑํ ๋ค ๊ธฐ์กด์ BookmarkRepository ์ธํฐํ์ด์ค์์ MongoTemplateRepository ์ธํฐํ์ด์ค๋ฅผ ์์ํ๋ฉด ๋ฉ๋๋ค.
(์ฐธ๊ณ ๋ก ๊ตฌํ ํด๋์ค๋ ๋ฐ๋์ Impl์ผ๋ก ๋๋์ผ ํ๋ค๊ณ ํฉ๋๋ค..;)
๊ตฌํ ์ฝ๋
interface MongoTemplateRepository {
fun findAllRemindByUserId(userId: Long): List<Bookmark>
}
import com.yapp.web2.domain.bookmark.entity.Bookmark
import org.springframework.data.mongodb.core.MongoTemplate
import org.springframework.data.mongodb.core.query.Criteria
import org.springframework.data.mongodb.core.query.Query
class BookmarkRepositoryImpl(
private val mongoTemplate: MongoTemplate
) : MongoTemplateRepository {
// userId ๊ธฐ์ค์ผ๋ก ๋ฆฌ๋ง์ธ๋ ๋ฐ์ก๋์์ผ๋ฉฐ ๋ฏธํ์ธ์ธ ๋ถ๋งํฌ ๋ฆฌ์คํธ ์กฐํ
override fun findAllRemindByUserId(userId: Long): List<Bookmark> {
val query: Query = Query()
query.addCriteria(
Criteria.where("remindList").elemMatch(
Criteria.where("userId").`is`(userId)
.and("remindCheck").`is`(false)
.and("remindStatus").`is`(true)
)
)
query.fields().include("userId")
query.fields().include("folderId")
query.fields().include("link")
query.fields().include("title")
query.fields().include("remindList.$")
return mongoTemplate.find(query, Bookmark::class.java)
}
}
interface BookmarkRepository : MongoRepository<Bookmark, String>, MongoTemplateRepository {
...
}
์ฝ๋๋ ์์ ๊ฐ์ต๋๋ค. ๊ตฌํ์ฒด์ธ BookmarkRepositoryImpl ํด๋์ค์๋ MongoTemplate์ ์ฃผ์ ๋ฐ๋๋ก ํ๋๋ฅผ ์ถ๊ฐํฉ๋๋ค.
(๋น์ทํ ๊ฐ๋ ์ผ๋ก MongoOperations์ด ์์ต๋๋ค. MongoTemplate๋ ํด๋์ค, MongoOperations๋ ์ธํฐํ์ด์ค์ด๋ฉฐ MongoTemplate์ด MongoOperations์ ๊ตฌํํ๊ณ ์์ต๋๋ค. MongoTemplate - MongoOperations )
๊ทธ ํ Query ๋ฐ Criteria๋ฅผ ํตํด ์กฐ๊ฑด์ ์์ฑํ๋๋ฐ์, elemMatch๋ Array์์ ์กฐ๊ฑด์ ๋ง์กฑํ๋ ์์๋ฅผ ์ฐพ๊ณ ์ ํ ๋ ์ฌ์ฉํ ์ ์์ต๋๋ค.
($elemMatch์ ๊ฒฝ์ฐ ์กฐ๊ฑด์ ์ผ์นํ๋ ๋ฐ์ดํฐ๊ฐ ์ฌ๋ฌ ๊ฐ ์ผ๊ฒฝ์ฐ ํ๋์ ๋ฐ์ดํฐ๋ง ๋ฆฌํดํ๊ธฐ ๋๋ฌธ์ ์กฐ๊ฑด์ ๋ง์กฑํ๋ ๋ชจ๋ ์์๋ค์ ์ฐพ์ผ๋ ค๋ฉด Aggregation Operations ์ ์ฌ์ฉํด์ ์์ ์ ํด์ผ ํ๋ ๋ฏ ํ๋ฐ ํด๋ณด์ง๋ ์์์ ์์ธํ๋ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค ^^; - ์ฐธ๊ณ ๋งํฌ
์ ์ ๊ฒฝ์ฐ userId๊ฐ ์ค๋ณต๋ ์ผ์ด ์๊ธฐ ๋๋ฌธ์ ๋ฐ์ดํฐ๊ฐ ๋ณต์ ๊ฐ๊ฐ ๋์ฌ ์ ์๊ธฐ์ $elemMatch๋ฅผ ์ฌ์ฉํ์์ต๋๋ค. ๐ญ)
query().fields().include() ๊ฐ์ ๊ฒฝ์ฐ๋ find ๊ฒฐ๊ณผ๊ฐ์ @Document๊ฐ ์ค์ ๋ Entity์ ๋งคํ๋ ๋ ๋งคํ์ํฌ ํ๋๋ฅผ ํฌํจํ ๋ ์ฌ์ฉํ ์ ์๋๋ฐ์, ํ์ํ ๊ฐ์ ๋ํด์ ์ค์ ํด์ฃผ์ง ์์ผ๋ฉด null์ด ๋ค์ด์ค๋ ์ค์ ์ ํด์ฃผ์ด์ผ ํฉ๋๋ค.
userId, folderId, link ํ๋๋ Bookmark Entity์์ ์์ฑ์๋ก ํ์ํ ๊ฐ์ด๋ผ ์ค์ ์ ํด์ฃผ์๊ณ , title์ ์ฌ์ฉ๋ ๊ฐ์ด์ด์ ์ค์ ์ ํด์ฃผ์์ต๋๋ค.
๋ง์ฝ ์์ฑ์๊ฐ ์ฌ๋ฌ๊ฐ๋ผ๋ฉด @PersistenceConstructor ์ด๋ ธํ ์ด์ ์ ํตํด Entity๋ก ๋งคํ๋ ๋ ์ ํํ ์์ฑ์๋ฅผ ์ง์ ์ ํํ ์๋ ์์ต๋๋ค.
- @PersistenceConstructor: Marks a given constructor - even a package protected one - to use when instantiating the object from the database. Constructor arguments are mapped by name to the key values in the retrieved Document.
๐ ๊ฒฐ๊ณผ
์ ๊ตฌํ ์ฝ๋๋ฅผ ํ ์คํธํด๋ณด๋ฉด ์คํ๋๋ ์ฟผ๋ฆฌ ๋ฐ ๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
find using query: { "remindList" : { "$elemMatch" : { "userId" : 26, "remindCheck" : false, "remindStatus" : true}}} fields: Document{{remindList.$=1, link=1, title=1, userId=1, folderId=1}}
ํฌ์คํธ๋งจ์ ํตํด ํ ์คํธ๋ฅผ ํด๋ณด๋ฉด userId, remindCheck, remindStatus ์กฐ๊ฑด์ ์ผ์นํ๋ Array์ ์์๋ง ์ถ๋ ฅ๋๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
๋ค๋ฅธ ์ฝ๋์์๋ ์ํ๋ ๊ฒฐ๊ณผ๊ฐ ์๋์ค๋ ์ฟผ๋ฆฌ๊ฐ ์๋๋ฐ, ์ธ๋ฅ ์์ ํ๋ฌ ๊ฐ์ผ๊ฒ ์ต๋๋ค. ๐
๐ ์ฐธ๊ณ ๋ฌธ์
- https://www.mongodb.com/developer/products/mongodb/mongodb-schema-design-best-practices/
- https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/
- https://stackoverflow.com/questions/68128476/getting-a-list-object-in-a-object-with-spring-and-mongotemplate
- https://stackoverflow.com/questions/47518923/mongodb-and-operator-with-nested-objects-in-arrays-not-working-as-expected
- https://stackoverflow.com/questions/58064707/mongotemplate-return-userid-null-within-springboot-project
- https://ckddn9496.tistory.com/102
- https://www.mongodb.com/docs/manual/reference/operator/query/elemMatch/
'Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Spring Boot profiles ์ค์ ํ๊ธฐ (1) | 2022.09.05 |
---|---|
Spring Thread, Transaction, Connection ๊ด๊ณ (0) | 2022.09.03 |
Spring Boot SQL ์ค์ (hibernate, logging) (4) | 2022.03.12 |
Spring AOP - (2) AOP ๊ฐ๋ ๋ฐ ์ค์ต (0) | 2021.09.24 |
[Spring] - ๋ก๊น : Log4j, Log4j2, Slf4j, Logback (0) | 2021.09.15 |
๋๊ธ