๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
CS/Database

[Spring Data MongoDB] Auto-Increment Sequence ๋งŒ๋“ค๊ธฐ

by ์ฃผ๋ฐœ2 2021. 9. 10.
๋ฐ˜์‘ํ˜•

๐Ÿ“Ž  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

 

 

๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€