Spring

SpringBoot [Bean Validation]

DAHLIA CHOI 2022. 7. 21. 12:02

 

Bean Validation์ด๋ž€?

์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•˜๋Š” JAVA์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ํ‘œ์ค€ ๊ธฐ์ˆ ์ด๋‹ค. Bean Validation์€ ํŠน์ •ํ•œ ๊ตฌํ˜„์ฒด๊ฐ€ ์•„๋‹ˆ๋ผ Bean Validation 2.0์ด๋ผ๋Š” ๊ธฐ์ˆ  ํ‘œ์ค€์ด๋‹ค. ๊ฒ€์ฆ ์• ๋…ธํ…Œ์ด์…˜๊ณผ ์—ฌ๋Ÿฌ ์ธํ„ฐํŽ˜์ด์Šค์˜ ๋ชจ์Œ์ด๋‹ค. 

 

 

โญ ์ผ๋‹จ bean validation์„ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์— ์˜์กด๊ด€๊ณ„๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค!

Maven

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Gradle

implementation 'org.springframework.boot:spring-boot-starter-validation'

 

 

๐ŸŸช ์–ด๋…ธํ…Œ์ด์…˜ ์ •๋ฆฌ

  • @NotBlank : ๋นˆ ๊ฐ’ + ๊ณต๋ฐฑ๋งŒ ์žˆ๋Š” ๊ฒฝ์šฐ๋ฅผ ํ—ˆ์šฉํ•˜์ง€ ์•Š์Œ
  • @NotNull :  null์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š์Œ
  • @Range(min = 10, max = 100) :  10๋ถ€ํ„ฐ 100 ๋ฒ”์œ„ ์•ˆ์˜ ๊ฐ’๋งŒ ํ—ˆ์šฉํ•จ
  • @Max(100) : ์ตœ๋Œ€ 100๊นŒ์ง€๋งŒ ํ—ˆ์šฉํ•จ
  • @Email : emailํ˜•์‹์„ ๊ฐ–์ถ”์–ด์•ผ ํ•จ
  • @NotEmpty : null์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š์œผ๋ฉฐ ๊ณต๋ฐฑ ๋ฌธ์ž์—ด์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š์Œ

์ด์™ธ์˜ ๋‹ค๋ฅธ ์–ด๋…ธํ…Œ์ด์…˜๋“ค์€ ์•„๋ž˜ ์‚ฌ์ดํŠธ์— ์ ‘์†ํ•˜๋ฉด ๋œ๋‹ค.

 

Hibernate Validator 6.2.3.Final - Jakarta Bean Validation Reference Implementation: Reference Guide

Validating data is a common task that occurs throughout all application layers, from the presentation to the persistence layer. Often the same validation logic is implemented in each layer which is time consuming and error-prone. To avoid duplication of th

docs.jboss.org

 

โ—พ ์–ด๋…ธํ…Œ์ด์…˜ ์–ด๋–ป๊ฒŒ ์“ฐ๋Š”์ง€ ํ™•์ธํ•ด๋ณด๊ธฐ

@Data
public class Item {

    @NotBlank
    private String itemName;

    @NotNull
    @Range(min = 10, max = 1000)
    private Integer price;

    @NotNull
    @Max(value = 1000)
    private Integer quantity;

}
์•„์ดํ…œ ์ด๋ฆ„์€ ๋นˆ์นธ ๋˜๋Š” ๊ณต๋ฐฑ์ด์–ด์„œ๋Š” ์•ˆ ๋œ๋‹ค.
๊ฐ€๊ฒฉ์€ 10 ์ด์ƒ 1000 ์ดํ•˜์˜ ๊ฐ’์ด์–ด์•ผ ํ•œ๋‹ค.
์ˆ˜๋Ÿ‰์€ 1000์„ ๋„˜์–ด๊ฐ€๋ฉด ์•ˆ ๋œ๋‹ค.

 

๐ŸŸช ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•ด๋ณด์ž๋ฉด 

    public String addItem(@Validated @ModelAttribute Item item, BindingResult bindingResult) {
    
        if (bindingResult.hasErrors()) {
            log.info("errors={}", bindingResult);
            return "items/addForm";
        }
    }

๐Ÿ‘‰ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” @Valid ๋˜๋Š” @Validated ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์—ฌ์ฃผ๋ฉด ๋œ๋‹ค.

@Validated์œผ๋กœ ๊ฒ€์ฆํ•œ ๊ฐ์ฒด๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์€ ๊ฐ์ฒด๋ผ๋ฉด Controller์˜ ๋ฉ”์„œ๋“œ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์žˆ๋Š” BindingResult ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ™•์žฅํ•œ ๊ฐ์ฒด๋กœ ๋“ค์–ด์˜จ๋‹ค. ๋”ฐ๋ผ์„œ bindingResult.hasError() ๋ฉ”์„œ๋“œ๋Š” ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์— ์‹คํŒจํ•  ๊ฒฝ์šฐ true๋ฅผ ๋ฐ˜ํ™˜ํ•ด์„œ ๊ฒ€์ฆํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๋ฐ”์ธ๋”ฉ์— ์„ฑ๊ณตํ•œ ํ•„๋“œ๋งŒ Bean Validation์„ ์ ์šฉํ•œ๋‹ค. typeMismatch ๊ฐ™์€ ๊ฒฝ์šฐ๋Š” FieldError์— ์ถ”๊ฐ€ํ•ด์„œ BindingResult์— ๋‹ด๋Š” ๊ฒƒ ๊ฐ™๋‹ค!

 

โญ @Valid vs @Validated์˜ ์ฐจ์ด์ 

@Valid์™€ @Validated๋ชจ๋‘ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•  ๋•Œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค. ํ•˜์ง€๋งŒ @Valid๋Š” Java์—์„œ ์ง€์›ํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ด๊ณ  @Validated์€ Spring์—์„œ ์ง€์›ํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค. ๋‚˜์ค‘์— groups๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ์—๋Š” @Valid๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ ๋œ๋‹ค! ์ด๊ฑด Spring์—์„œ ์ง€์›ํ•˜๋Š” ๊ธฐ๋Šฅ์ด๊ธฐ ๋•Œ๋ฌธ์—! ์›ฌ๋งŒํ•ด์„œ๋Š” ์œ ์šฉํ•œ ๊ธฐ๋Šฅ์ด ๋งŽ์€ @Validated์„ ์“ฐ๋Š” ๊ฒŒ ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.

 

 

 

๐ŸŸช ์—๋Ÿฌ ์ฝ”๋“œ ์ˆ˜์ •

bean validation์„ ์ด์šฉํ•˜๋ฉด ๊ธฐ๋ณธ์ ์œผ๋กœ ๋””ํดํŠธ ์—๋Ÿฌ์ฝ”๋“œ๊ฐ€ ์กด์žฌํ•œ๋‹ค! ํ•˜์ง€๋งŒ ๋” ๊ฐ„๊ฒฐํ•˜๊ฑฐ๋‚˜ ๋ณด๊ธฐ ์ข‹๊ฒŒ ๋ฐ”๊พธ๊ณ  ์‹ถ์œผ๋ฉด ์—๋Ÿฌ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜๋ฉด ๋œ๋‹ค.

 

errors.properties์— ์ด๋Ÿฐ ์‹์œผ๋กœ ์ถ”๊ฐ€์‹œ์ผœ์ฃผ๋ฉด ๋œ๋‹ค.

NotBlank={0} ๊ณต๋ฐฑX
Range={0}, {2} ~ {1} ํ—ˆ์šฉ
Max={0}, ์ตœ๋Œ€ {1}
{0}์€ ํ•„๋“œ๋ช…์„ ์˜๋ฏธํ•œ๋‹ค. itemName, price, quantity ๊ฐ™์€ ๊ฑฐ!

๊ทผ๋ฐ ์œ„์˜ ๋ฐฉ๋ฒ•์€ ๊ฐ€์žฅ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ๋‹ค!

์ €๊ฑฐ๋ณด๋‹ค ์šฐ์„ ์ˆœ์œ„๋ฅผ ๋†’๊ฒŒ ํ•˜๋ ค๋ฉด ์• ์ดˆ์— 

@NotBlank(message = "๊ณต๋ฐฑ์€ ์ž…๋ ฅํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")

bean validation ์–ด๋…ธํ…Œ์ด์…˜ ์ž…๋ ฅํ•  ๋•Œ message ์†์„ฑ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

์•„๋‹ˆ๋ฉด errors.properties์— ๋” ์ž์„ธํ•œ ๊ฒฝ๋กœ๋กœ ์ง€์ •ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

typeMismatch.java.lang.Integer="์ˆซ์ž๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”"

 

 

๐ŸŸช Bean Validation ํ•œ๊ณ„

๋งŒ์•ฝ ๋ฐ์ดํ„ฐ๋ฅผ ๋“ฑ๋กํ•  ๋•Œ์™€ ์ˆ˜์ •ํ•  ๋•Œ์˜ ์š”๊ตฌ์‚ฌํ•ญ์ด ๋‹ค๋ฅผ ๊ฒฝ์šฐ์—๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. 

ex) ํšŒ์›๊ฐ€์ž…์„ ํ•  ๋•Œ์—๋Š” ๋‚˜์ด๊ฐ€ ํ•„์ˆ˜ ์ž…๋ ฅ ๊ฐ’์ด๋‚˜ ์ˆ˜์ •ํ•  ๋•Œ๋Š” ๋‚˜์ด๋ฅผ ์ ์ง€ ์•Š์•„๋„ ๊ดœ์ฐฎ์€ ๊ฒฝ์šฐ!

์ด๋Ÿด ๊ฒฝ์šฐ์—๋Š” ์„œ๋กœ ์ถฉ๋Œํ•˜๊ฒŒ ๋˜๋ฉด์„œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธด๋‹ค!!

 

์ด๋ฅผ ํ•ด๊ฒฐํ•  ๋ฐฉ์•ˆ์€ ๋‘ ๊ฐ€์ง€์ด๋‹ค

 

โšซ groups

groups๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์—ญํ• ์„ ๋ถ„๋ฆฌ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค. ๋“ฑ๋ก๊ณผ ์ˆ˜์ •์„ ๊ฐ๊ฐ ๋‹ค๋ฅธ ๊ทธ๋ฃน์œผ๋กœ ๋‚˜๋ˆ„๋Š” ๊ฒƒ์ด๋‹ค.

์œ„์—์„œ ๋งํ–ˆ๋“ฏ์ด ์ด๊ฑด ์Šคํ”„๋ง์ด ์ œ๊ณตํ•ด์ฃผ๋Š” ๊ธฐ๋Šฅ์ด๊ธฐ ๋•Œ๋ฌธ์— @Validated๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค!

 

๋“ฑ๋ก์šฉ ์ธํ„ฐํŽ˜์ด์Šค

public interface SaveCheck {}

 

์ˆ˜์ •์šฉ ์ธํ„ฐํŽ˜์ด์Šค

public interface UpdateCheck {}

 

์ด๋ ‡๊ฒŒ ๊ฐ๊ฐ ๋งŒ๋“ค์–ด์ฃผ๊ณ  bean validation ์–ด๋…ธํ…Œ์ด์…˜์— ์‚ฌ์šฉํ•  ๊ทธ๋ฃน์„ ์ง€์ •ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

@Data
public class Members {
    @NotNull(groups = {SaveCheck.class, UpdateCheck.class})
    private String name;
    
    @NotNull(groups = SaveCheck.class)
    private Integer age;
}
์ด ์˜๋ฏธ๋Š” name์—๋Š” ๋‘ ๊ทธ๋ฃน ๋ชจ๋‘ @NotNull ์–ด๋…ธํ…Œ์ด์…˜์„ ์ ์šฉ์‹œํ‚ค๊ณ , age๋Š” ํšŒ์›์„ ๋“ฑ๋กํ•  ๋•Œ๋งŒ ์ ์šฉ์‹œํ‚จ๋‹ค๋Š” ์˜๋ฏธ

 

๊ทธ๋Ÿผ ์ปจํŠธ๋กค๋Ÿฌ์—๋„ ๊ทธ๋ฃน์„ ์ง€์ •ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

    public String addMember(@Validated(SaveCheck.class)) {}

 

 

โญ ๊ทผ๋ฐ ์ด ๋ฐฉ๋ฒ•์€ ๋„ˆ๋ฌด ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•ด ๋ณด์ธ๋‹ค. ๊ทธ๋ž˜์„œ ์‹ค๋ฌด์—์„œ ์ž˜ ์•ˆ ์“ด๋‹ค๊ณ  ํ•œ๋‹ค!

์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ๋ถ„๋ฆฌํ•ด์„œ ์ ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ๋” ๊ฐ„๋‹จํ•˜๊ณ  ์ข‹๋‹ค 

 

๋‹ค์‹œ Members ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์›๋ž˜๋Œ€๋กœ ๋ฐ”๊พธ์–ด์ฃผ๊ณ ,

@Data
public class Members {
    private String name;
    private Integer age;
}
@Data
public class MemberSaveForm {

    @NotNull
    private String name;
    
    @NotNull
    private Integer age;
}
@Data
public class MemberUpdateForm {

    @NotNull
    private String name;
    
    private Integer age;
}

์ด๋ ‡๊ฒŒ ํผ์„ ๋‘ ๊ฐœ๋กœ ๋‚˜๋ˆ„์–ด์ฃผ๊ณ  ๋‚˜์„œ

 

    public String addItem(@Validated @ModelAttribute("Members") memberSaveForm form, BindingResult bindingResult, RedirectAttributes redirectAttributes) {

        if (bindingResult.hasErrors()) {
            log.info("errors={}", bindingResult);
            return "Members/addForm";
        }

        //์„ฑ๊ณต ๋กœ์ง
        Members members = new Members();
        members.setMemberName(form.getMemberName());
        members.setAge(form.getAge());

        Members savedMember = membersRepository.save(members);
        redirectAttributes.addAttribute("itemId", savedMember.getId());
        redirectAttributes.addAttribute("status", true);
        return "redirect:/items/{itemId}";
    }

์ด๋Ÿฐ ์‹์œผ๋กœ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์ˆ˜์ •ํ•˜๋ฉด ๋œ๋‹ค. 

@ModelAttribute๋ฅผ ์™œ ์ˆ˜์ •ํ–ˆ๋ƒ, ํผ์„ ๋‘ ๊ฐœ๋กœ ๋‚˜๋ˆ„๊ณ  ๋‚˜์„œ ์ ์šฉํ•ด์•ผ ํ•˜๋Š” bean validation ์–ด๋…ธํ…Œ์ด์…˜์ด ๋‹ค๋ฅธ๋ฐ, model attribute์— ("Members")๋ฅผ ์•ˆ ํ•ด์ฃผ๊ฒŒ ๋˜๋ฉด

model.addAttribute("memberSaveForm", form);

์ด๋ ‡๊ฒŒ memberSaveForm์ด๋ผ๋Š” MVC Model์— ๋‹ด๊ธฐ๊ฒŒ ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๊ผญ ์ด๋ฆ„์„ ๋„ฃ์–ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.