๐ 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
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 |
๋๊ธ