๐ Spring Data MongoDB Auto-Increment Sequence ๋ง๋ค๊ธฐ
์๋ ํ์ธ์, ์ด๋ฒ ์๊ฐ์ ์ ๋ฆฌํ ๋ด์ฉ์ Spring Data MongoDB์์ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ ํ ๋ ๋ง๋ค ์๋์ผ๋ก ์ฆ๊ฐํ๋ ํ๋๋ฅผ ์์ฑํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
ํด๋น ๊ธฐ๋ฅ์ MySQL์์์ AUTO INCREMENT์ ๋์ผํ๋ค๊ณ ๋ณด์๋ฉด ๋ฉ๋๋ค.
โป Spring Data MongoDB์ ๊ฒฝ์ฐ ์ฒ์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ํ๋ฆฐ ๋ด์ฉ์ด ์์ ์ ์์ต๋๋ค.
Sequence Collection ์์ฑ
package com.juhyun.shorturl.entity.sequence;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
@Getter
@Setter
@Document(collection = "auto_sequence")
public class AutoIncrementSequence {
@Id
private String id;
private Long seq;
}
๋จผ์ Auto Increment ์์ฑ์ ์ ์ฅํ๋ ์ปฌ๋ ์ (auto_sequence)์ ์์ฑํฉ๋๋ค.
Entity - ShortUrl
package com.juhyun.shorturl.entity;
import com.juhyun.shorturl.controller.dto.ReadShortUrlResponse;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.mongodb.core.mapping.Document;
import javax.validation.constraints.NotBlank;
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
@ToString
@Document(collection = "shorturl")
public class ShortUrl {
@Transient
public static final String SEQUENCE_NAME = "shorturl_sequence";
@Id
private Long _id;
...
}
ShortUrl Entity์์ ์์์ ์์ฑํ AutoIncrementSequence๋ฅผ ์ฐธ์กฐํ SEQUENCE_NAME ๋ณ์๋ฅผ ์์ฑํฉ๋๋ค.
ํด๋น ๋ณ์์๋ ์์์ฑ ํ๋์์ ์ ์ธํ๊ธฐ ์ํด @Transient ์ด๋
ธํ
์ด์
์ ์ถ๊ฐํฉ๋๋ค.
๐ค MongoDB _id
MongoDB๋ Collection๋ณ๋ก document๋ฅผ ์ ์ฅํ๋๋ฐ, document๋ณ _id ์์ฑ์ด ์กด์ฌํฉ๋๋ค.
์ด๋ Collection์ ์ ์ฅ๋ ์ฌ๋ฌ document์ ๋ํด ์ ๋ํฌํจ์ ์๋ณํ๊ธฐ ์ํ ๊ธฐ๋ณธ key์ ๋๋ค.
ํน๋ณํ ์ ์ธ์ด ์์ผ๋ฉด ObjectId type์ ์ฌ์ฉํ๋๋ฐ์, ์ ๋ ํด๋น ๊ฐ์ Long์ผ๋ก ์ ์ธ์ ํด์ฃผ์์ต๋๋ค.
MongoDB๋ Collection๋ณ document๋ฅผ ์ ์ฅํ๋๋ฐ, document๋ณ _id ์์ฑ์ด ์กด์ฌํฉ๋๋ค. ์ด๋ Collection์ ์ ์ฅ๋ ์ฌ๋ฌ document์ ๋ํด ์ ๋ํฌํ ๊ฐ์์ ์๋ณํ๊ธฐ ์ํ ๊ธฐ๋ณธ key์ ๋๋ค.
ํน๋ณํ ์ ์ธ์ด ์์ผ๋ฉด MongoDB์์๋ ObjectId type์ ์ฌ์ฉํฉ๋๋ค.
Spring Data MongoDB์์๋ @Id ์ด๋ ธํ ์ด์ ์ผ๋ก ์ง์ ๋ field๊ฐ ์กด์ฌํ๋ฉด MongoDB์ _id๋ก ๋งคํ์ด ๋ฉ๋๋ค.
๋ง์ฝ @Id ์ด๋ ธํ ์ด์ ์ด ์๋ค๋ฉด id๋ ์ด๋ฆ์ field๊ฐ ๋งคํ์ด ๋ฉ๋๋ค.
Java Entity์ id field๊ฐ String์ด๋ BigInteger๋ก ์ ์ธ์ด๋๋ฉด MongoDB์๋ ObjectId๋ก ๋ณํ๋์ด ์ ์ฅ์ด๋ฉ๋๋ค.
- String id -> Converter<String, ObjectId>
- BigInteger id -> Converter<BigInteger, ObjectId>
https://docs.mongodb.com/manual/reference/operator/aggregation/toObjectId/
Entity์์ id field์ ํ์ ์ผ๋ก String, BigInteger, Object๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ๊ถ์ฅํ๋ ๊ฒ ๊ฐ์ง๋ง(?) ์ ๋ Long ํ์ ์ผ๋ก ์ ์ธ์ ํ๋๋ฐ์, MongoDB์ ObjectId๋ก ๊ฐ์ด ์ ์ฅ๋ ๊ฒฝ์ฐ ๋ฌธ์์ด์ด ์ถ๊ฐ๋์ด์ ์ Auto Increment๋ฅผ ์ถ๊ฐํ๋๋ผ๋ MongDB์๋ ์๋ ์ฌ์ง์ฒ๋ผ ๋ฌธ์์ด ์ซ์๋ก ์ ์ฅ์ด ๋ฉ๋๋ค.
์ ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ _id ๊ฐ์ ๊ธฐ์ค์ผ๋ก ์ ๋ ฌ์ ํด์ผํ๋ ๊ฒฝ์ฐ๊ฐ ์์๋๋ฐ ๋ฌธ์์ด ์ซ์์ธ _id๋ฅผ ์ ๋ ฌํ ๊ฒฝ์ฐ ์๋ ์ฌ์ง์ฒ๋ผ ์ํ๋ ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์์์ต๋๋ค.
๋ฐ๋ผ์ ์ ๋ ฌ ํ ์ํ๋ ๊ฒฐ๊ณผ๋ฅผ ์ป๊ธฐ์ํด ์ ๋ _id๊ฐ์ Long์ผ๋ก ์ ์ธํ์ฌ ์ฌ์ฉํ์์ต๋๋ค.
์ ๊ด๋ จ๋ ๋ฌธ์ ์ ๋ํด ์ฐพ์๋ณด๋ค๋ณด๋ ๋น์ทํ ๋ฌธ์ ๋ก MongoDB 3.4๋ฒ์ ์ดํ๋ก๋ "Decimal128" ํ์ ์ด ์ถ๊ฐ๋์ด์ ํด๋น ํ์ ์ผ๋ก Converterํด์ ํด๊ฒฐํ๋ ํฌ์คํ ๋ ๋ณด์์ต๋๋ค.
์ ์ฉํด๋ณด์ง๋ ์์์ง๋ง, ๋ค์์ ๊ธฐํ๊ฐ ๋๋ค๋ฉด ์ ์ฉํด๋ณด๊ฒ ์ต๋๋ค.
http://www.mytechtip.com/2018/01/sort-bigdecimal-mongodb-spring-data.html
Spring Data: How to Sort BigDecimal Fields Correctly When Using MongoDB
If you use spring data together with mongodb and one of your entity has some fields of type "BigDecimal", You have probably already noticed ...
www.mytechtip.com
SequenceGeneratorService ์์ฑ
package com.juhyun.shorturl.entity.sequence;
import lombok.RequiredArgsConstructor;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import java.util.Objects;
import static org.springframework.data.mongodb.core.FindAndModifyOptions.options;
import static org.springframework.data.mongodb.core.query.Criteria.where;
@RequiredArgsConstructor
@Service
public class SequenceGeneratorService {
private final MongoOperations mongoOperations;
public Long generateSequence(String seqName) {
AutoIncrementSequence counter = mongoOperations.findAndModify(Query.query(where("_id").is(seqName)),
new Update().inc("seq", 1), options().returnNew(true).upsert(true), AutoIncrementSequence.class);
//return BigInteger.valueOf(!Objects.isNull(counter) ? counter.getSeq() : 1);
return !Objects.isNull(counter) ? counter.getSeq() : 1;
}
}
Entity์ id๋ก ์ฌ์ฉ๋ ์ ์๋๋ก ์๋์ผ๋ก ์ฆ๊ฐํ๋ SequenceGeneratorService ํด๋์ค๋ฅผ ์์ฑํฉ๋๋ค.
Listener ์์ฑ
package com.juhyun.shorturl.entity.sequence;
import com.juhyun.shorturl.entity.ShortUrl;
import lombok.RequiredArgsConstructor;
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent;
import org.springframework.stereotype.Component;
@RequiredArgsConstructor
@Component
public class ShortUrlListener extends AbstractMongoEventListener<ShortUrl> {
private final SequenceGeneratorService generatorService;
@Override
public void onBeforeConvert(BeforeConvertEvent<ShortUrl> event) {
event.getSource().setId(generatorService.generateSequence(ShortUrl.SEQUENCE_NAME));
}
}
๋ง์ง๋ง์ผ๋ก ์๋ก์ด Entitry๊ฐ ์ถ๊ฐ๋ ๋๋ง๋ค id๊ฐ์ด ์๋์ผ๋ก ์ฆ๊ฐํ๋๋ก AbstractMongoEventListener ์ถ์ ํด๋์ค๋ฅผ ์์๋ฐ๋ ShortUrlListener ํด๋์ค๋ฅผ ์์ฑํ๊ณ , onBeforeConvert() ๋ฉ์๋๋ฅผ ์ค๋ฒ๋ผ์ด๋ ํด์ ๊ตฌํํฉ๋๋ค.
AbstractMongoEventListener ์ถ์ ํด๋์ค๋ ์ดํ๋ฆฌ์ผ์ด์ ์ด๋ฒคํธ ๋ฆฌ์ค๋์ธ ApplicationListener ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํฉ๋๋ค.
onBeforeConvert() ๋ฉ์๋์ ์ต์ด ์ง์ ์ ํ ๋๋ _id = null ์ธ ์ํ๋ก ๋ค์ด์ค๊ฒ ๋ฉ๋๋ค.
๊ทธ ํ setId() ๋ฉ์๋๋ฅผ ํตํด ์์์ ์์ฑํ Service์ negerateSequence ๋ฉ์๋๋ฅผ ํธ์ถํฉ๋๋ค.
๋งค๊ฐ๋ณ์์ seqName์ Entity์ ์กด์ฌํ๋ static ๋ณ์์ธ SEQUENCE_NAME ์ ๊ฐ์ ๋๋ค.
์ ์ฝ๋๋ MongoOperations ํด๋์ค์ findAndModify() ๋ฉ์๋๋ ์ฌ๋ฌ ๋ฉ์๋๊ฐ ์ค๋ฒ๋ก๋ฉ์ด ๋์ด์๋๋ฐ SequenceGeneratorService ํด๋์ค์์ ์ฌ์ฉํ๋ ๋ฉ์๋๋ ์์ ๊ฐ์ด 4๊ฐ์ ๋งค๊ฐ๋ณ์๋ฅผ ๋ฐ๊ณ ์์ต๋๋ค.
์ ์ฝ๋๋ MongoOperations ์ธํฐํ์ด์ค์ ๊ตฌํ์ฒด์ธ MongoTemplate ํด๋์ค์ findAndModify() ์ ๋ค์ํ ๋ฉ์๋๋ค์ ๋๋ค.
์๋น์ค์์ ์ฌ์ฉํ๊ณ ์๋ ๋ฉ์๋๋ ์ ์ฌ์ง์ฒ๋ผ 4๊ฐ์ ์ธ์๋ฅผ ๋ฐ๊ณ ์์ต๋๋ค.
Query: Query.query(where("_id").is(seqName))
UpdateDefinition: new Update().inc("seq", 1)
FindAndModifyOptions: options().returnNew(true).upsert(true)
Class<T>: AutoIncrementSequence.class
- Query: MongoDB์ Query Object๋ฅผ ์๋ฏธํฉ๋๋ค.
- UpdateDefinition: ์ธํฐํ์ด์ค๋ก Update๊ฐ ๊ตฌํ์ฒด์ ๋๋ค. ๋งค์นญ๋๋ documents์ ์ ๋ฐ์ดํธํฉ๋๋ค.
- FindAndModifyOptions: ์ด๋ฆ ๊ทธ๋๋ก Find & Modify์ ์ํํ๋ ํด๋์ค ์ ๋๋ค.
- Class<T>: ํ๋ผ๋ฏธํฐ ํ์ ์ ๋๋ค.
์ ํด๋์ค๋ฅผ ๋น ์ ธ๋์ค๋ฉด counter์ seq ๊ฐ์ Auto Increment ๋๋ ๊ฐ์ผ๋ก ๋ณ๊ฒฝ์ด ๋ฉ๋๋ค.
SequenceGeneratorService ํด๋์ค์ generateSequence()์ ๊ฐ์ ๊ฐ์ ธ์จ ๋ค Listener์์ setId() ๋ฉ์๋๋ฅผ ํตํด _id์ value๋ฅผ ์ค์ ํฉ๋๋ค.
์ ๋ฆฌ
์ด์์ผ๋ก Spring Data MongoDB์์ Auto Increment๋ฅผ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์์๋ณด์์ต๋๋ค.
์ ๋ด์ฉ์ ๊ฐ๋จํ๊ฒ ์ ๋ฆฌํ๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- Sequence Collection ์์ฑ
- Entity ์์ฑ(์ฐธ์กฐํ ์ํ์ค ๋ณ์ ์ ์ธ)
- SequenceGeneratorService ์์ฑ
- Listener ์์ฑ
์ฒ์ ์ฌ์ฉํ๋ค๋ณด๋ ๊ฝค ๋ง์ ์ฝ์ง์ด ์์๋๋ฐ์ ๐ญ ์์ง๋ ์ด๋ ต๋ค์.. ๊ทธ์น๋ง ์ฒ์์ฌ์ฉํ๋ค๋ณด๋ ์ฌ๋ฐ๊ธฐ๋ ํ๊ณ ๋น๋ถ๊ฐ์ ๊ฝค ์ฌ์ฉํ ๊ฒ ๊ฐ์ต๋๋ค.
References
- https://www.baeldung.com/spring-boot-mongodb-auto-generated-field
- https://ozofweird.tistory.com/entry/Spring-Boot-MongoDB%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EA%B0%84%EB%8B%A8%ED%95%9C-API-%EC%A0%9C%EC%9E%91
- https://luvstudy.tistory.com/62
- https://www.mongodb.com/
'CS > Database' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
MySQL EXPLAIN ์คํ๊ณํ ๋ง์คํฐํ๊ธฐ(feat. RealMySQL 8.0) (0) | 2023.01.07 |
---|---|
[Spring Data MongoDB] - Auto Increment Sequence ์ด๊ธฐํํ๊ธฐ (0) | 2021.09.11 |
[MySQL] - ํธ๋์ญ์ ์ ๊ฒฉ๋ฆฌ ์์ค(Isolation level) (11) | 2021.09.05 |
DBCP(DataBase Conncetion Pool), ์ปค๋ฅ์ ํ ์ด๋? (0) | 2021.08.10 |
์ฌ์ฉ์๊ฐ ๊ฒ์๋ฌผ์ ์์ฑํ ๋์ ํธ๋์ญ์ ์ฒ๋ฆฌ (6) | 2021.08.05 |
๋๊ธ