๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ’ป ํ”„๋กœ์ ํŠธ/๐Ÿ‘‘ VIP ์ดˆ๋Œ€์žฅ ๐Ÿ’Œ

[Spring] ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ๋” ๊ตฌ์ฒด์ ์œผ๋กœ ํ•ด๊ฒฐํ•ด๋ณด์ž!_ ๋ฆฌํŒฉํ† ๋ง

by hyeong._.ing 2026. 6. 8.

 

 

๋งŒ๋“ค์—ˆ๋˜ ์ฝ”๋“œ๋ฅผ ๋‹ค์‹œ ์‚ดํŽด๋ณด๋‹ˆ
๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ์• ๋งคํ•˜๊ฒŒ ํ•ด๊ฒฐํ•˜๊ณ  ์žˆ์—ˆ๋‹ค.
์ข€ ๋” ํ™•์‹คํ•˜๊ฒŒ ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ด ๋ณด์ž.

 

 

 

1. ์›์ธ

  • ์ดˆ๋ฐ˜ ๋กœ์ง
if (customerRepository.existsByCode(code)) {
    throw new DuplicateCodeException("์ดˆ๋Œ€์ฝ”๋“œ๊ฐ€ ์ค‘๋ณต๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
}

customerRepository.save(customer);
customerSearchRepository.save(toSearchEntity(customer));
CustomerService ์ค‘ save ๋ฉ”์„œ๋“œ์˜ ์ผ๋ถ€๋ถ„์ด๋‹ค. ์ฒ˜์Œ ๊ณ ๊ฐ ๋“ฑ๋ก ๋กœ์ง์€ ์ด๋Ÿฐ ํ๋ฆ„์ด์—ˆ๋‹ค. code๊ฐ€ ๋“ค์–ด์˜ค๋ฉด existsByCode๋กœ code๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ๋ณด๊ณ , ์กด์žฌํ•˜๋ฉด DuplicateCodeException์„ ๋˜์กŒ๋‹ค.

๋‘ ๊ด€๋ฆฌ์ž๊ฐ€ ๊ฑฐ์˜ ๋™์‹œ์— ๊ฐ™์€ ์ดˆ๋Œ€์ฝ”๋“œ๋ฅผ ๋“ค๊ณ  ์š”์ฒญ์ด ๋“ค์–ด์™”๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ณด์ž. ์ด๋Ÿฐ ๊ฒฝ์šฐ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค. 
A: existsByCode("1234") → false
B: existsByCode("1234") → falseโ€‹


existsByCode ๋‹จ๊ณ„์—์„œ ์•„์ง ์ค‘๋ณต์ด ์—†๋‹ค๊ณ  ํŒ๋‹จ(false)ํ•˜์—ฌ A, B ๋‘˜ ๋‹ค ํ†ต๊ณผํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค. 

 

@Table(
    name = "customer",
    uniqueConstraints = @UniqueConstraint(
        name = "uk_customer_code",
        columnNames = "code"
    )
)
๊ทธ๋Ÿฌ๋‚˜ ์‹ค์ œ๋กœ ๋‘˜ ๋‹ค ํ†ต๊ณผ๋˜์ง„ ์•Š๊ณ  ์ €์žฅ๋‹จ๊ณ„์—์„œ ๊ฐˆ๋ฆด ๊ฒƒ์ด๋‹ค.
A: save("1234") ์‹œ๋„ → ์„ฑ๊ณต
B: save("1234") ์‹œ๋„ → ์‹คํŒจ

OR

B: save("1234") ์‹œ๋„ → ์„ฑ๊ณต
A: save("1234") ์‹œ๋„ → ์‹คํŒจ

์™œ๋ƒ๋ฉด Customer ์—”ํ‹ฐํ‹ฐ์— ์œ„์˜ ์ฝ”๋“œ์ฒ˜๋Ÿผ ์œ ๋‹ˆํฌ ์ œ์•ฝ์„ ๊ฑธ์–ด๋’€๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๊ทธ๋ž˜์„œ ์ตœ์ข…์ ์œผ๋กœ ๋จผ์ € DB์— ๋ฐ˜์˜๋œ ์š”์ฒญ์€ ์„ฑ๊ณตํ•˜๊ณ  ๋‚˜์ค‘์— ๊ฐ™์€ ๊ฐ’์„ ๋„ฃ์œผ๋ ค๋Š” ์š”์ฒญ์€ ์œ ๋‹ˆํฌ ์ œ์•ฝ ์œ„๋ฐ˜์œผ๋กœ ์‹คํŒจํ•˜๋ฉด์„œ ์ค‘๋ณต ์ €์žฅ์€ ์•ˆ ๋œ๋‹ค.

 

  • ๊ฒฐ๋ก 
    - ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ ˆ๋ฒจ์˜ ์‚ฌ์ „ ๊ฒ€์‚ฌ(existByCode)๋Š” ๋™์‹œ์„ฑ ์ƒํ™ฉ์—์„œ ์™„์ „ํ•œ ๋ณด์žฅ์ด ์•„๋‹ˆ๋‹ค.
    - ๋‹คํ–‰ํžˆ Customer.cdoe ์นผ๋Ÿผ์—์„œ unique ์ œ์•ฝ์ด ๊ฑธ๋ ค ์žˆ์–ด์„œ ์‹ค์ œ DB์—๋Š” ๊ฐ™์€ ์ดˆ๋Œ€์ฝ”๋“œ๊ฐ€ ๋‘˜ ๋‹ค ์ €์žฅ๋˜์ง€๋Š” ์•Š๋Š”๋‹ค. 
    - ์ฆ‰, ์ตœ์ข… ์ •ํ•ฉ์„ฑ์€ DB unique constraint๊ฐ€ ๋ณด์žฅํ•œ๋‹ค.

  • ๋ฌธ์ œ์ 
    - unique ์ œ์•ฝ ์œ„๋ฐ˜์ด ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ช…ํ™•ํ•œ ์‘๋‹ต์„ ๋‚ด๋ ค์ฃผ์ง€ ๋ชปํ–ˆ๋‹ค.
@RestController
public class ApiExceptionHandler {

    @ExceptionHandler(DuplicateCodeException.class)
    public ResponseEntity<String> handleDup(DuplicateCodeException e) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
    }
}
์ด ํด๋ž˜์Šค๊ฐ€ ์—๋Ÿฌ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ํด๋ž˜์Šค์ธ๋ฐ, ๋ณด๋ฉด BAD_REQUEST๋กœ ์ƒํƒœ์ฝ”๋“œ๋ฅผ ์„ค์ •ํ–ˆ๋‹ค. ์ด๊ฑด 400 BadRequest์ด๋‹ค. 400 BadRequest๋Š” ๋ณดํ†ต ์š”์ฒญ์ด ์ž˜๋ชป ์™”์„ ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด์„œ ์ดˆ๋Œ€ ์ฝ”๋“œ๊ฐ€ 4์ž๋ฆฌ์ธ๋ฐ, 3์ž๋ฆฌ๊ฐ€ ์˜ค๋Š” ๊ฒฝ์šฐ ๋‚ด๋ฆฌ๋Š” ์‘๋‹ต์ด๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

์ค‘๋ณต ์˜ค๋ฅ˜๋Š” ๋ณดํ†ต 409 Conflict๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ๊ทธ๋ž˜์„œ ์ด ์ƒํƒœ์™€ ๋งž๊ฒŒ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•ด์•ผ ํ•œ๋‹ค. ์ฒซ ๋ฒˆ์งธ๋กœ ์ ์šฉ ๋ฒ”์œ„๋ฅผ ๋„“ํž ๊ฒƒ์ด๋‹ค. @RestController๋Š” ํ•ด๋‹น ์ปจํŠธ๋กค๋Ÿฌ ๋‚ด๋ถ€์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ๋งŒ ์žก๋Š”๋ฐ, ์ด๊ฑธ ์ „์—ญ์ ์œผ๋กœ ๊ฐ์ง€ํ•ด์„œ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ•  ๊ฒƒ์ด๋‹ค. ๋‘ ๋ฒˆ์งธ๋Š” DB ๋ ˆ๋ฒจ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ ์ œ์•ฝ ์กฐ๊ฑด ์œ„๋ฐ˜(DataIntegrityViolationException)๊นŒ์ง€ ํ•จ๊ป˜ ์ฒ˜๋ฆฌํ•  ๊ฑฐ๋‹ค. ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ํŒŒ์‹ฑ ํ•ด์„œ ์–ด๋–ค unique ์ œ์•ฝ ์กฐ๊ฑด์„ ์œ„๋ฐ˜ํ–ˆ๋Š”์ง€ ๊ตฌ์ฒด์ ์œผ๋กœ ํŒ๋‹จํ•˜๋Š” ๋กœ์ง์œผ๋กœ ์ˆ˜์ •ํ•  ๊ฒƒ์ด๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ 400 BadRequset๋ฅผ 409 Conflict๋กœ ๋ฐ”๊ฟ€ ๊ฒƒ์ด๋‹ค. 



    - ์ดˆ๋Œ€ ์ฝ”๋“œ ๊ฒ€์ฆ์€ ํ”„๋ŸฐํŠธ์—”๋“œ์—์„œ๋งŒ ํ•˜๊ณ  ์žˆ์–ด์„œ ๋ฐฑ์—”๋“œ ๊ฒ€์ฆ ๋กœ์ง๋„ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค.

        String code = customer.getCode();

        if (code != null && customerRepository.existsByCode(code)) {
            throw new DuplicateCodeException("์ดˆ๋Œ€์ฝ”๋“œ๊ฐ€ ์ค‘๋ณต๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
        }
๋‚˜๋„ ๋ชฐ๋ž๋‹ค. ํ”„๋ก ํŠธ์—”๋“œ์—์„œ 4์ž๋ฆฌ๋ฅผ ๋ง‰์•„๋‘๊ณ  ๋ฐฑ์—”๋“œ์—๋Š” ์ž‘์„ฑํ•˜์ง€ ์•Š์•˜๋‹ค. ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ๋ง‰์•˜์œผ๋ฉด ๋˜๋Š” ๊ฑฐ ์•„๋‹ˆ๋ƒ๊ณ  ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ด๋ ‡๊ฒŒ ์ž‘์„ฑํ•˜๋ฉด ์•ˆ ๋œ๋‹ค. ์• ์ดˆ์— ํ”„๋ŸฐํŠธ์—”๋“œ๋Š” ๊ฒ€์ฆ์„ ์‰ฝ๊ฒŒ ์šฐํšŒํ•  ์ˆ˜ ์žˆ๋Š” ๊ตฌ์กฐ์ด๋‹ค. ๊ทธ๋ ‡๊ธฐ์— ๋ฐฑ์—”๋“œ๋Š” ๋ฐฉ์–ด์  ์„ค๊ณ„๋ฅผ ํ•ด์•ผ ํ•œ๋‹ค. ๋ฐฑ์—”๋“œ๋Š” ์Šค์Šค๋กœ ์ž์‹ ์˜ ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ์„ ์ง€ํ‚ฌ ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.

ํ”„๋ŸฐํŠธ์—”๋“œ ๊ฒ€์ฆ ๋กœ์ง์€ UX ๋ชฉ์ ์ด๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์‹ค์ˆ˜๋กœ ์ดˆ๋Œ€ ์ฝ”๋“œ 4์ž๋ฆฌ ์ค‘ 3์ž๋ฆฌ๋งŒ ์ž…๋ ฅํ–ˆ์„ ๋•Œ, ์„œ๋ฒ„๊นŒ์ง€ ๊ฐ”๋‹ค ์˜ค๋Š” ์‹œ๊ฐ„์„ ๋‚ญ๋น„ํ•˜์ง€ ์•Š๊ณ  ๋ฐ”๋กœ 4์ž๋ฆฌ๋ฅผ ์ž…๋ ฅํ•˜๋ผ๊ณ  ์•Œ๋ ค์ฃผ์–ด ๋น ๋ฅธ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ œ๊ณตํ•œ๋‹ค. ๋ฐฑ์—”๋“œ ๊ฒ€์ฆ์€ ์šฐํšŒ๋œ ๊ณต๊ฒฉ์ด๋‚˜ ์ž˜๋ชป๋œ ๋ฐ์ดํ„ฐ๊ฐ€ DB ์‹œ์Šคํ…œ์„ ๋ง๊ฐ€๋œจ๋ฆฌ์ง€ ์•Š๋„๋ก ์ตœ์ข…์ ์œผ๋กœ ๋ฐฉ์–ดํ•˜๋Š” ๋ชฉ์ ์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.

์ด ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด custoemr ์—”ํ‹ฐํ‹ฐ์—์„œ code๋ฅผ ์ง์ ‘ ๋ฝ‘์•„ ๋“ค๊ณ  ์™€์„œ ํ™•์ธํ•˜๊ณ  ์žˆ๋‹ค. ์ด๋ ‡๊ฒŒ ๊ฒ€์ฆ์ด ํ•„์š”ํ•œ ์นผ๋Ÿผ์˜ ๊ฒฝ์šฐ ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…์œผ๋กœ ๋”ฐ๋กœ ๋นผ์„œ ๊ฒ€์ฆ ๋กœ์ง์„ ์งœ๋ฉด ๋œ๋‹ค.

 

 

 

- ์ดˆ๋Œ€์ฝ”๋“œ ๋™์ž‘

 

[Spring, React] ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ๊ณ ๊ฐ ๋“ฑ๊ธ‰์— ๋งž๋Š” ํŽ˜์ด์ง€๋ฅผ ๊ฒฐ๊ณผ๋กœ ๋ณด๋‚ด๊ธฐ

VIP ์ด์ƒ ๋“ฑ๊ธ‰์˜ ๊ณ ๊ฐ์ด์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•˜๋Š” ํŽ˜์ด์ง€์— ๋ณธ์ธ ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด๊ทธ์— ๋งž๋Š” ๋“ฑ๊ธ‰ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋„๋ก ๋งŒ๋“ค์–ด๋ณด์ž. 1. ๋™์ž‘ ํ™”๋ฉดGIF ์ด๋ฏธ์ง€ 2. Spring ์ฝ”๋“œ ๋™์ž‘ ์„ค๋ช…1) ์ดˆ๋Œ€ ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด

post-this.tistory.com

 

 

 

 


 

 

 

 

 

 2. ๋ฆฌํŒฉํ† ๋ง

  • InviteCode ํด๋ž˜์Šค
// JPA์—๊ฒŒ ์ด ํด๋ž˜์Šค๋Š” ๋‹ค๋ฅธ ์—”ํ‹ฐํ‹ฐ์˜ ์ผ๋ถ€๋กœ ํฌํ•จ๋  ์ˆ˜ ์žˆ๋Š” ๊ฐ’ ๊ฐ์ฒด์ž„์„ ์•Œ๋ฆฐ๋‹ค.
@Embeddable
// Lombok์„ ํ†ตํ•ด ํ•„๋“œ(value)์˜ Getter ๋ฉ”์„œ๋“œ๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•œ๋‹ค.
@Getter
// JPA๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ํ•„์š”ํ•œ ๊ธฐ๋ณธ ์ƒ์„ฑ์ž๋ฅผ ๋งŒ๋“ ๋‹ค.
// ๋‹ค๋งŒ ๋ฌด๋ถ„๋ฉธํ•œ ์™ธ๋ถ€ ์ƒ์„ฑ์„ ๋ง‰๊ธฐ ์œ„ํ•ด PROTECTED๋กœ ๋ ˆ๋ฒจ์„ ์„ค์ •ํ•ด์„œ ์ ‘๊ทผ์„ ์ œ์–ดํ•œ๋‹ค.
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class InviteCode {

    // ์ฝ”๋“œ ํŒจํ„ด์€ ์ˆซ์ž๋กœ ์ด๋ค„์ง„ 4์ž๋ฆฌ์ด๋‹ค.
    private static final String CODE_PATTERN = "\\d{4}";

    // ์ดˆ๋Œ€์ฝ”๋“œ ๋ฌธ์ž์—ด ๊ฐ’์ด ์ €์žฅ๋˜๋Š” ์บก์Аํ™”๋œ(private) ํ•„๋“œ์ด๋‹ค.
    private String value;

    private InviteCode(String value) {
    
        // ์ƒ์„ฑ ์‹œ์ ์— ์ž…๋ ฅ๊ฐ’์ด ์œ ํšจํ•œ์ง€ ๊ฒ€์ฆํ•œ๋‹ค. 
        if (!isValid(value)) {
            // ์ž˜๋ชป๋˜์—ˆ์œผ๋ฉด ํ‘œ์ค€ ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋‹ค.
            throw new IllegalArgumentException("์ดˆ๋Œ€์ฝ”๋“œ๋Š” ์ˆซ์ž 4์ž๋ฆฌ๋กœ ์ž…๋ ฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.");
        }

        // ๊ฒ€์ฆ์ด ํ†ต๊ณผ๋˜๋ฉด trim์œผ๋กœ ๊ณต๋ฐฑ์„ ์ œ๊ฑฐํ•œ ๋’ค ํ•„๋“œ์— ์•ˆ์ „ํ•˜๊ฒŒ ์ €์žฅํ•œ๋‹ค.
        this.value = value.trim();
    }

    // ๋“ค์–ด์˜จ ์ดˆ๋Œ€์ฝ”๋“œ๋ฅผ ๊ฐ์ฒด๋กœ ๋งŒ๋“œ๋Š” ์ •์  ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ์ด๋‹ค.(of ๋ฉ”์„œ๋“œ)
    // ์ „์—๋Š” ์ดˆ๋Œ€์ฝ”๋“œ๋ฅผ ๋‹จ์ˆœํžˆ String์œผ๋กœ ๋‹ค๋ค˜์œผ๋ฉด ์ด์   ์ดˆ๋Œ€์ฝ”๋“œ๋ฅผ ๊ฐ’ ๊ฐ์ฒด๋กœ ๊ฐ์‹ผ๋‹ค.
    public static InviteCode of(String value) {
        // "1234" ๋ฌธ์ž์—ด์€ -> InviteCode.of("1234")๊ฐ€ ๋˜๋ฉด์„œ ๊ฐ์ฒด ์ƒ์„ฑ๋œ๋‹ค.
        // ๊ทธ๋ฆฌ๊ณ ๋‚˜์„œ ์œ„์— ์žˆ๋Š” InviteCode ์ƒ์„ฑ์ž ์•ˆ์œผ๋กœ ๋“ค์–ด๊ฐ€์„œ ๊ฒ€์‚ฌํ•œ๋‹ค.
        return new InviteCode(value);
    }

    // ์ดˆ๋Œ€์ฝ”๋“œ์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์ฆํ•˜๋Š” ๋„๋ฉ”์ธ ๋กœ์ง์ด๋‹ค.
    public static boolean isValid(String value) {
        // null๋„ ์•„๋‹ˆ๊ณ , ๊ณต๋ฐฑ๋„ ์•„๋‹ˆ๊ณ , ๊ณต๋ฐฑ ์ œ๊ฑฐ ํ›„ 4์ž๋ฆฌ ์ˆซ์ž์—ฌ์•ผํ•œ๋‹ค.
        return value != null && !value.isBlank() && value.trim().matches(CODE_PATTERN);
    }

    // ๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ๊ฐ€ ๋‹ฌ๋ผ๋„ ์•ˆ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ฐ™์œผ๋ฉด ๊ฐ™์€ ๊ฐ์ฒด๋กœ ์ทจ๊ธ‰ํ•˜๊ธฐ ์œ„ํ•œ
    // ๋™๋“ฑ์„ฑ ๋น„๊ต๋ฅผ ์ •์˜ํ•˜๋Š” ๋ฉ”์„œ๋“œ์ด๋‹ค.
    @Override
    public boolean equals(Object o) {
        
        // ๋น„๊ต ๋Œ€์ƒ๊ณผ ๋‚˜์˜ ๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ๊ฐ€ ์™„๋ฒฝํžˆ ๊ฐ™๋‹ค๋ฉด ์ฐธ์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
        if (this == o)
            return true;
        
        // ๋น„๊ต ๋Œ€์ƒ์ด null์ด๊ฑฐ๋‚˜ InviteCode ํƒ€์ž…์ด ์•„๋‹ˆ๋ผ๋ฉด ๊ฑฐ์ง“์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
        if (!(o instanceof InviteCode that))
            return false;
        
        // ์ตœ์ข…์ ์œผ๋กœ ๋‚ด๋ถ€์˜ value๊ฐ€ ์„œ๋กœ ์ผ์น˜ํ•˜๋Š”์ง€ ๋น„๊ตํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
        return Objects.equals(value, that.value);
    }

    // ๊ฐ์ฒด๋ฅผ ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ๋Š” ํ•ด์‹œ์ฝ”๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฉ”์„œ๋“œ์ด๋‹ค.
    @Override
    public int hashCode() {
        
        // ๋‚ด๋ถ€ value ๊ฐ’์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ด์‹œ์ฝ”๋“œ ์ƒ์„ฑํ•œ๋‹ค.
        // ํ•ด์‹œ ๊ธฐ๋ฐ˜ ์ปฌ๋ ‰์…˜์—์„œ ์ค‘๋ณต ์ฒดํฌ๊ฐ€ ์ •ํ™•ํžˆ ์ž‘๋™ํ•œ๋‹ค.
        return Objects.hash(value);
    }
}
์บก์Аํ™”์˜ ํ•ต์‹ฌ์€ ์™ธ๋ถ€์—์„œ ๊ฐ์ฒด์˜ ๋‚ด๋ถ€ ๋ฐ์ดํ„ฐ๋ฅผ ๋งˆ์Œ๋Œ€๋กœ ์ ‘๊ทผํ•˜๊ฑฐ๋‚˜ ์ˆ˜์ •ํ•˜์ง€ ๋ชปํ•˜๋„๋ก ์ˆจ๊ธฐ๊ณ  ๊ฐ์ฒด๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์•ˆ์ „ํ•œ ํ†ต๋กœ๋ฅผ ํ†ตํ•ด์„œ๋งŒ ์†Œํ†ตํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์‚ฌ์‹ค ์บก์Аํ™”๋ฅผ ํ•˜๋ ค๊ณ  ํ•œ ๊ฑด ์•„๋‹ˆ์—ˆ๋Š”๋ฐ, ํ•˜๊ณ  ๋ณด๋‹ˆ ์บก์Аํ™”๊ฐ€ ๋˜์—ˆ๋‹ค(?)
private String value;โ€‹

private๋กœ  ์ ‘๊ทผํ•˜์ง€ ๋ชปํ•˜๋„๋ก ๋ง‰์•˜๊ณ , of( ) ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๊ฒ€์ฆ์„ ๊ฑฐ์นœ ๊ฐ’๋งŒ ์ €์žฅํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ–ˆ๋‹ค. -> ์ด๋Ÿฐ ์‹์ด ์บก์Аํ™”๋ผ๊ณ  ํ•œ๋‹ค. 
    public static InviteCode of(String value) {
        return new InviteCode(value);
    }โ€‹

 

์ž๋ฐ”์—์„œ ํ‘œ์ค€์ ์œผ๋กœ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ •์  ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ๋ฅผ ๋งŒ๋“ค ๋•Œ, of๋ผ๋Š” ์ด๋ฆ„์„ ์ฃผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค. of๊ฐ€ ์ ํžŒ ๊ฑธ ๋ณด๊ณ  ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ด ์ฃผ๋Š” ์ •์  ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ๋ผ๋Š” ๊ฑธ ์•Œ ์ˆ˜ ์žˆ๋‹ค. 
 @Override
    public boolean equals(Object o) {
        if (this == o) // ๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ ํ™•์ธ
            return true;
        if (!(o instanceof InviteCode that)) // InviteCode ๊ฐ์ฒด์ธ์ง€ ํ™•์ธ
            return false;
        return Objects.equals(value, that.value); // ์ฝ”๋“œ๊ฐ€ ๊ฐ™์€๊ฒŒ ์žˆ๋Š”์ง€ ํ™•์ธ
    }


์ž๋ฐ”์˜ ๋ชจ๋“  ํด๋ž˜์Šค๋Š” ์ตœ์ƒ์œ„ ํด๋ž˜์Šค์ธ Object๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์†๋ฐ›๋Š”๋‹ค. equals(Object o)๋Š” ์ด Object ํด๋ž˜์Šค๊ฐ€ ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์žฌ์ •์˜ํ•œ ๊ฒƒ์ด๋‹ค. ์ฝ”๋“œ์—์„œ Object o๋Š” ๋น„๊ต ๋Œ€์ƒ์ด ๋  ๋ฉ”๋ชจ๋ฆฌ์— ์กด์žฌํ•˜๋Š” ์–ด๋–ค ๊ฐ์ฒด๋ฅผ ๋œปํ•œ๋‹ค. ์ž๋ฐ”์˜ ๋ชจ๋“  ๊ฐ์ฒด๋Š” Object ํƒ€์ž…์œผ๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ Object o๊ฐ€ ๋œ ๊ฒƒ์ด๋‹ค.

this๋Š” ํ˜„์žฌ ์ด equals ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ์žˆ๋Š” ์ฃผ์ฒด ๊ฐ์ฒด๋ฅผ ๋œปํ•œ๋‹ค. that์€ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋“ค์–ด์˜จ Obejct o๋ฅผ InviteCode ํƒ€์ž…์œผ๋กœ ํ˜•๋ณ€ํ™˜ํ•˜์—ฌ ๊บผ๋‚ธ ์ƒ๋Œ€๋ฐฉ ๊ฐ์ฒด๋ฅผ ๋œปํ•œ๋‹ค. 

์‰ฝ๊ฒŒ ์„ค๋ช…ํ•ด ๋ณด๊ฒ ๋‹ค. ๊ตฌ์ค€ํ‘œ ํšŒ์›์ด 1234๋ผ๋Š” ์ดˆ๋Œ€์ฝ”๋“œ๋ฅผ ๋“ค๊ณ  ์™”๋‹ค. ๊ทธ๋Ÿฌ๋ฉด if(this == o)๋ฅผ ํ†ตํ•ด ์ฃผ์†Œ ๊ฐ’์ด ๊ฐ™์€ ๊ฒŒ ์žˆ๋Š”์ง€ ํ™•์ธํ•œ๋‹ค. ๋งŒ์•ฝ ๊ฐ™์€ ์ฃผ์†Œ๊ฐ’์ด ์žˆ๋‹ค๋ฉด ๋‚ด๋ถ€ ๋ฌธ์ž์—ด์„ ํ™•์ธํ•˜์ง€ ์•Š๊ณ  ๋ฐ”๋กœ true๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ๊ทธ๋‹ค์Œ ๋„˜๊ฒจ๋ฐ›์€ ๊ฐ์ฒด๊ฐ€ inviteCode ํƒ€์ž…์ธ์ง€๋ฅผ ํ™•์ธํ•œ๋‹ค. String ๋ฌธ์ž์—ด์„ ๋ณด๋ƒˆ๋‹ค๋ฉด ๊ตฌ์กฐ๊ฐ€ ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์— false๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์ด์ œ ๋งˆ์ง€๋ง‰์œผ๋กœ ๊ตฌ์ค€ํ‘œ ํšŒ์›์˜ ์ดˆ๋Œ€ ์ฝ”๋“œ๊ฐ€ ๋‹ค๋ฅธ ํšŒ์›์˜ ์ดˆ๋Œ€์ฝ”๋“œ๊ฐ€ ๋งž๋Š” ๊ฒŒ ์žˆ๋Š” Object.equals๋กœ ํ™•์ธํ•œ๋‹ค. ๊ธ€์ž๊ฐ€ ๋˜‘๊ฐ™์œผ๋ฉด true๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

 

 

 

- @Embedded์™€ @Embeddable

 

[Spring][JPA] @Embeddable์™€ @Embedded๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ

์–ด๋–ค ํ•„๋“œ๋ฅผ ์ผ๋ฐ˜์ ์ธ ๋ฌธ์ž์—ด๋กœ ๋‘๋Š” ๊ฒƒ์ด ์•„๋‹Œ์˜๋ฏธ ์žˆ๋Š” ๊ฐ์ฒด๋กœ ๋งŒ๋“ค์–ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ@Embeddable๊ณผ @Embedded๋ฅผ ๋ถ™์ธ๋‹ค.๋” ์ž์„ธํ•˜๊ฒŒ ์•Œ์•„๋ณด์ž. 1. @Embeddable๊ณผ @Embedded@Embeddable์€ ๊ฐ’ ํƒ€์ž…์„ ์ •์˜ํ•˜๋Š” ๊ณณ

post-this.tistory.com

 

 

  • Customer ํด๋ž˜์Šค
    @Embedded
    @AttributeOverride(name = "value", column = @Column(name = "code", nullable = false, length = 4))
    private InviteCode inviteCode;

 

 

  • CustomerService์˜ save ๋ฉ”์„œ๋“œ
        if (customerRepository.existsByInviteCode(inviteCode)) {
            throw new DuplicateCodeException("์ดˆ๋Œ€์ฝ”๋“œ๊ฐ€ ์ค‘๋ณต๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
        }
์ด์ œ ์•„๊นŒ ๋งŒ๋“  InviteCode ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋„ฃ์–ด์„œ ์ดˆ๋Œ€ ์ฝ”๋“œ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•œ๋‹ค. ๋งจ ์ฒ˜์Œ ์ฝ”๋“œ๋Š” Code๋ฅผ ๋ฝ‘์•„์„œ ์ง์ ‘ ๋„ฃ์—ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ๋˜๋ฉด JPA์˜ ๊ฐ์ฒด ์ค‘์‹ฌ ๊ตฌ์กฐ๋ž‘ ์•ˆ ๋งž๊ฒŒ ๋œ๋‹ค. ๊ทธ๋ž˜์„œ ํ˜„์žฌ ์ฝ”๋“œ์ฒ˜๋Ÿผ ๋ฐ”๊ฟ”์คฌ๋‹ค.

 

 

 

  • ApiExceptionHandler ํด๋ž˜์Šค
// ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‚ด์˜ ๋ชจ๋“  @RestController์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ๋ฅผ ์ด๊ณณ์—์„œ ์ฒ˜๋ฆฌํ•œ๋‹ค.
@RestControllerAdvice
public class ApiExceptionHandler {

    // DuplicateCodeException ์˜ˆ์™ธ๋ฅผ ๊ฐ์ง€ํ•˜๋Š” ๋ฉ”์„œ๋“œ์ด๋‹ค.
    @ExceptionHandler(DuplicateCodeException.class)
    public ResponseEntity<String> handleDup(DuplicateCodeException e) {
        // HTTP ์ƒํƒœ ์ฝ”๋“œ๋ฅผ 409 Conflict๋กœ ์„ค์ •ํ–ˆ๋‹ค.
        return ResponseEntity.status(HttpStatus.CONFLICT).body(e.getMessage());
    }
    
    // DB ๋ ˆ๋ฒจ์—์„œ ์ œ์•ฝ ์กฐ๊ฑด ์œ„๋ฐ˜์œผ๋กœ ์ธํ•ด ๋ฐœ์ƒํ•˜๋Š”
    // DataIntegrityViolationException ์˜ˆ์™ธ๋ฅผ ๊ฐ์ง€ํ•œ๋‹ค.
    @ExceptionHandler(DataIntegrityViolationException.class)
    public ResponseEntity<String> handleDataIntegrityViolation(DataIntegrityViolationException e) {
        
        // ๋ฐ‘์— ์ž‘์„ฑํ•œ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ดˆ๋Œ€์ฝ”๋“œ ์ค‘๋ณต ๋•Œ๋ฌธ์— ์ผ์–ด๋‚œ๊ฑด์ง€ ํ™•์ธํ•œ๋‹ค.
        if (isCustomerCodeUniqueViolation(e)) {
            // ์ดˆ๋Œ€์ฝ”๋“œ ์ค‘๋ณต์ด ๋งž์œผ๋ฉด HTTP 409 ์ƒํƒœ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
            return ResponseEntity.status(HttpStatus.CONFLICT).body("์ดˆ๋Œ€์ฝ”๋“œ๊ฐ€ ์ค‘๋ณต๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
        }
        // ์ดˆ๋Œ€์ฝ”๋“œ ์ค‘๋ณต์ด ์•„๋‹Œ ๋‹ค๋ฅธ DB ์ œ์•ฝ ์กฐ๊ฑด ์œ„๋ฐ˜์ธ ๊ฒฝ์šฐ 409์™€ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
        return ResponseEntity.status(HttpStatus.CONFLICT).body("๋ฐ์ดํ„ฐ ์ค‘๋ณต ๋˜๋Š” ์ œ์•ฝ ์กฐ๊ฑด ์œ„๋ฐ˜์ด ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.");
    }

    // DB ์˜ˆ์™ธ์˜ ์ด์œ ๋ฅผ ๋ถ„์„ํ•˜๋Š” ๋ฉ”์„œ๋“œ์ด๋‹ค.
    private boolean isCustomerCodeUniqueViolation(DataIntegrityViolationException e) {
        
        // ์Šคํ”„๋ง ์˜ˆ์™ธ ํ”„๋ ˆ์ž„์›Œํฌ ๊ธฐ๋Šฅ์œผ๋กœ ๋ž˜ํ•‘๋œ ์—๋Ÿฌ์—์„œ ๊ฐ€์žฅ ๊ตฌ์ฒด์ ์ธ ์—๋Ÿฌ๋ฅผ ๊บผ๋‚ด์˜จ๋‹ค.
        Throwable cause = e.getMostSpecificCause();
        // ์‚ผํ•ญ ์—ฐ์‚ฌ์ž๋กœ ์ตœํ•˜๋‹น ์—๋Ÿฌ๊ฐ€ ์žˆ์œผ๋ฉด ๊ฐ–๊ณ  ์˜ค๊ณ  ์—†์œผ๋ฉด ์ƒ์œ„ ์—๋Ÿฌ e๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
        String message = cause == null ? e.getMessage() : cause.getMessage();
        // "uk_customer_code"๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋Š”์ง€๋ฅผ true, false๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
        return message != null && message.toLowerCase().contains("uk_customer_code");
    }

}

 

 

 

 


 

 

 

 

3. ํ•ด๊ฒฐ?

  • ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋Œ€ํ‘œ์ ์œผ๋กœ 3๊ฐ€์ง€ ์ •๋„๊ฐ€ ์žˆ๋‹ค.
  • ์ฒซ ๋ฒˆ์งธ๋Š” ์ง€๊ธˆ ํ•œ ๋ฐฉ๋ฒ•์ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค Unique ์ œ์•ฝ ์กฐ๊ฑด์ด๋‹ค.
  • ๋‘ ๋ฒˆ์งธ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋น„๊ด€์  ๋ฝ์ด๋‹ค. existByCode๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ์ฝ๊ธฐ ์ž ๊ธˆ์„ ๊ฑธ๊ฑฐ๋‚˜, ๊ณต์œ  ์ž์›์— ๋Œ€๊ธฐํ•˜๋„๋ก ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ๋น„๊ด€์  ๋ฝ์€ ํ˜„์žฌ ํ”„๋กœ์ ํŠธ์— ๋งž์ง€ ์•Š๋Š”๋‹ค. ์™œ๋ƒ๋ฉด ๋™์‹œ์— ๊ฐ™์€ ์ดˆ๋Œ€์ฝ”๋“œ๊ฐ€ ๋“ค์–ด์˜ค๋ฉด Insert ๋˜๊ธฐ ์ „์ด๋ผ์„œ ๋ฝ์„ ๊ฑธ ์ˆ˜ ์—†๊ฒŒ ๋œ๋‹ค. ๊ทธ๋ž˜์„œ ๊ฒฐ๊ตญ ๋™์ผํ•œ ์ดˆ๋Œ€์ฝ”๋“œ๊ฐ€ ๋„˜์–ด๊ฐ€๊ฒŒ ๋œ๋‹ค. ์ง€๊ธˆ ์ฝ”๋“œ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์€ ๋„ค์ž„๋“œ ๋ฝ์ด๋‹ค. ๋„ค์ž„๋“œ ๋ฝ์€ DB์— ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋“  ์—†๋“ , ๋ฉ”๋ชจ๋ฆฌ ๊ณต๊ฐ„์— ๋“ค์–ด์˜จ ์ดˆ๋Œ€์ฝ”๋“œ๋ฅผ ๊ฐ€์ƒ์œผ๋กœ ๋งŒ๋“ค์–ด ๋ฝ์„ ๊ฑธ์–ด์„œ ๋™์‹œ์„ฑ์„ ๋ง‰์„ ์ˆ˜ ์žˆ๋‹ค.
  • ์„ธ ๋ฒˆ์งธ๋Š” Redis๋ฅผ ์ด์šฉํ•œ ๋ถ„์‚ฐ ๋ฝ์ด๋‹ค. ์„œ๋ฒ„๊ฐ€ ์—ฌ๋Ÿฌ ๋Œ€์ธ ํ™˜๊ฒฝ์—์„œ ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ๊ธ€๋กœ๋ฒŒ ๋™์‹œ์„ฑ ์ œ์–ด ๋ฐฉ์‹์ด๋‹ค. 
  • ์ด ์ค‘ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค Unique ์ œ์•ฝ ์กฐ๊ฑด์ด ํ˜„์žฌ ์ฝ”๋“œ์—์„œ ์ž˜ ๋งž๋‹ค๊ณ  ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค. ์–ด์ฐจํ”ผ ๊ฐœ์ธ ํ”„๋กœ์ ํŠธ๊ณ  ์‹ค์ œ๋กœ ์„œ๋ฒ„๋ฅผ ์šด์˜ํ•  ์ƒ๊ฐ์€ ์—†๊ธฐ์— ์ด๋ ‡๊ฒŒ ํ•ด๊ฒฐํ–ˆ๋‹ค.
  • ๊ทธ๋‚˜์ €๋‚˜ ์ด ์ฝ”๋“œ๋ฅผ ๊ณ ์น˜๋ฉด์„œ ์ƒˆ๋กœ์šด ๋ฌธ์ œ๋ฅผ ๋ฐœ๊ฒฌํ–ˆ๋‹ค. ๊ทธ๊ฑด ๋ฐ”๋กœ ElasticSearch ์ธ๋ฑ์Šค ์ €์žฅ๊ณผ DB์˜ ๋ถˆ์ผ์น˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด๋‹ค. ์ด ๋ถ€๋ถ„์„ ๋‹ค์Œ์— ์–ด๋–ป๊ฒŒ ๊ณ ์ณค๋Š”์ง€ ์„ค๋ช…ํ•ด ๋ณด๊ฒ ๋‹ค.