Spring

37. 데이터의 변환과 검증(1) - 패스트캠퍼스 백엔드 부트캠프 3기

gkss2tpt 2025. 2. 17. 19:05

1. WebDataBinder

  • RegisterController에 변환기능 추가하기
import java.util.Date;

public class User {
    private String id;
    private String pwd;
    private String name;
    private String email;
    private Date birth;
    private String sns;

    @Override
    public String toString() {
        return "User{" +
                "id='" + id + '\'' +
                ", pwd='" + pwd + '\'' +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", birth='" + birth + '\'' +
                ", sns='" + sns + '\'' +
                '}';
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Date getBirth() {
        return birth;
    }

    public void setBirth(Date birth) {
        this.birth = birth;
    }

    public String getSns() {
        return sns;
    }

    public void setSns(String sns) {
        this.sns = sns;
    }


}
  • String birth를 Date birth로 변환

  • Spring이 바꿔줄수 있는 타입이라 변환되었다.

  • 패턴을 2020-12-31로 바꾸면?

  • 에러가 나온다.
public String save(User user, Model m) throws Exception{
    // 1. 유효성 검사
    if(!isValid(user)) {
        String msg = URLEncoder.encode("id를 잘못입력하셨습니다.","UTF-8") ;

        m.addAttribute("msg", msg);
        return "forward:/register/add";
//      return "redirect:/register/add?msg="+msg;   // URL재작성(rewriting)
    }
    // 2. DB에 신규회원 정보를 저장
    return "registerInfo";

}
  • BindingResult 추가 (주의) 바인딩할 객체 바로 뒤에 와야한다.
public String save(User user, BindingResult result,Model m) throws Exception{
    System.out.println("result="+result);
    // 1. 유효성 검사
    if(!isValid(user)) {
        String msg = URLEncoder.encode("id를 잘못입력하셨습니다.","UTF-8") ;

        m.addAttribute("msg", msg);
        return "forward:/register/add";
//      return "redirect:/register/add?msg="+msg;   // URL재작성(rewriting)
    }
    // 2. DB에 신규회원 정보를 저장
    return "registerInfo";

}
  • 다시 실행하면?

  • 에러가 나오지 않는 모습
  • birth에 null 이 들어갔고... 변환에 실패했다.
  • BindingResult : 예외가 발생하면 컨트롤러에게 바인딩 결과를 주고 컨트롤러가 처리하게 한다.
  • 에러가 발생하지 않은게 아니라 에러페이지로 이동하지 않은 것 뿐이다.

  • typeMismatch : 타입이 맞지 않아서 생긴에러
@InitBinder
public void toDate(WebDataBinder binder){
    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
    binder.registerCustomEditor(Date.class, new CustomDateEditor(df,false));
}
  • toDate메서드를 RegisterController클래스에 정의 해준다.
  • new CustomDateEditor('형식지정', '빈값 허용/비허용')

  • 변환 성공

  • 에러도 발생하지 않았다.
  • sns타입을 String배열로 바꿔보자...
public class User {
    private String id;
    private String pwd;
    private String name;
    private String email;
    private Date birth;
    private String[] sns;

    @Override
    public String toString() {
        return "User{" +
                "id='" + id + '\'' +
                ", pwd='" + pwd + '\'' +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", birth='" + birth + '\'' +
                ", sns='" + Arrays.toString(sns) + '\'' +
                '}';
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Date getBirth() {
        return birth;
    }

    public void setBirth(Date birth) {
        this.birth = birth;
    }

    public String[] getSns() {
        return sns;
    }

    public void setSns(String[] sns) {
        this.sns = sns;
    }


}

 

  • 콘솔에서는 잘나온다.
  • loginForm.jsp의 input에 취미를 추가

  • User클래스에 getter, setter, toString, String배열을 hobby를 선언
public class User {
    private String id;
    private String pwd;
    private String name;
    private String email;
    private Date birth;
    private String[] hobby;
    private String[] sns;

    @Override
    public String toString() {
        return "User{" +
                "id='" + id + '\'' +
                ", pwd='" + pwd + '\'' +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", birth='" + birth + '\'' +
                ", hobby=" + Arrays.toString(hobby) +
                ", sns='" + Arrays.toString(sns) + '\'' +
                '}';
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Date getBirth() {
        return birth;
    }

    public void setBirth(Date birth) {
        this.birth = birth;
    }

    public String[] getSns() {
        return sns;
    }

    public void setSns(String[] sns) {
        this.sns = sns;
    }


    public String[] getHobby() {
        return hobby;
    }

    public void setHobby(String[] hobby) {
        this.hobby = hobby;
    }
}
  • 재시작하면...

  • 화면에는 나오지 않았지만...

  • 배열에 취미들이 하나의 문자로 들어가버렸다.
  • 나눠주려면?
@InitBinder
public void toDate(WebDataBinder binder){
    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
    binder.registerCustomEditor(Date.class, new CustomDateEditor(df,false));
}
@InitBinder
public void toCustom(WebDataBinder binder){
    binder.registerCustomEditor(String[].class, new StringArrayPropertyEditor("#"));
}
  • registerInfo.jsp에 hobby추가
<body>
<h1>id=${user.id}</h1>
<h1>pwd=${user.pwd}</h1>
<h1>name=${user.name}</h1>
<h1>email=${user.email}</h1>
<h1>birth=${user.birth}</h1>
<h1>hobby=${user.hobby}</h1>
<h1>sns=${user.sns}</h1>
</body>
  • 다시 회원가입을 눌러주면...

  • 배열로 잘 들어간 모습

  • @DateTimeFormat(pattern=" ")
public class User {
    private String id;
    private String pwd;
    private String name;
    private String email;
    @DateTimeFormat(pattern="yyyy-MM-dd")
    private Date birth;
    private String[] hobby;
    private String[] sns;
    
}
//    @InitBinder
//    public void toDate(WebDataBinder binder){
//        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
//        binder.registerCustomEditor(Date.class, new CustomDateEditor(df,false));
//    }
    @InitBinder
    public void toCustom(WebDataBinder binder){
        binder.registerCustomEditor(String[].class, new StringArrayPropertyEditor("#"));
    }
  • 주석처리후 다시 실행해보면..

  • 잘 변환된 모습
  • @InitBinder는 컨트롤러 내에서만 적용가능하다.

2. PropertyEditor : 양방향 타입 변환(String -> 타입, 타입 -> String), 특정 타입이나 이름의 필드에 적용 가능

@InitBinder
public void toCustom(WebDataBinder binder){
    binder.registerCustomEditor(String[].class, "hobby", new StringArrayPropertyEditor("#"));
}
  • 이름 지정을 해주면 지정한 필드에만 패턴이 적용된다.
  • 디폴트 PropertyEditor - 스프링이 기본적으로 제공
  • 커스텀 PropertyEditor - 사용자가 직접 구현. PropertyEditorSupport를 상속하면 편리
  • 모든 컨트롤러 내에서의 변환 - WebBindingInitializer를 구현후 등록
  • 특정 컨트롤러 내에서의 변환 - 컨트롤러에 @InitBinder가 붙은 메서드를 작성

3. Converter : 단방향 타입 변환(타입A -> 타입B), PropertyEditor의 단점을 개선(stateful -> stateless)

  • PropertyEditor의 단점 : 인스턴스 변수를 사용 -> 싱글톤 사용 불가능
public class StringToStringArrayConverter implements Converter<String, String[]> {
    @Override
    public String[] convert(String source) {
        return source.split("#");	// String -> String[]
    }
}
  • Converter<타입A, 타입B> -> 타입A에서 타입B로 변환된다.
  • ConversionService - 타입 변환 서비스를 제공, 여러 Converter를 등록 가능
    • WebDataBinder에 DefaultFormattingConversionService이 기본등록
    • 모든 컨트롤러 내에서의 변환 - ConfigurableWebBindingInitializer를 설정해서 사용
    • 특정 컨트롤러 내에서의 변환 - 컨트롤러에 @InitBinder가 붙은 메서드를 작성

4. Formatter

  • 양방향 타입 변환(String -> 타입, 타입 -> String)
  • 바인딩할 필드에 적용 - @NumberFormat, @DateTimeFormat
public interface Formatter<T> extends Printer<T>, Parser<T> {
}

public interface Printer<T> {
    String print(T object, Locale locale);	// Object -> String
}

public interface Parser<T> {
    T parse(String text, Locale locale) throws ParseException;	// String -> Object
}
@DateTimeFormat(pattern="yyyy/MM/dd")
Date birth;

@NumberFormat(pattern="###,###")
BigDecimal salary;
  • ConversionService에 들어있는 데이터를 보려면...
@InitBinder
public void toDate(WebDataBinder binder){
    ConversionService conversionService = binder.getConversionService();
    System.out.println("conversionService="+conversionService);
    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
    binder.registerCustomEditor(Date.class, new CustomDateEditor(df,false));
}
@InitBinder
public void toCustom(WebDataBinder binder){
    binder.registerCustomEditor(String[].class, "hobby", new StringArrayPropertyEditor("#"));
}
  • ConversionService에 binder.getConversionService()를 넣어주고... 재시작을 해주면

  • ConversionService에 등록된 converter들을 볼 수 있다.
  • WebDataBinder의 우선순위
    • 커스텀 PropertyEditor
    • ConversionService
    • 디폴트 PropertyEditor