티스토리 뷰

Spring

[Spring] @ModelAttribute

snail voyager 2025. 1. 30. 18:43
728x90
반응형

Spring MVC에서 HTTP 요청의 데이터를 객체로 바인딩할 때 사용하는 어노테이션입니다.

  • 주로 폼 데이터를 DTO(또는 VO) 객체로 매핑할 때 사용됩니다.
  • 컨트롤러 메서드의 매개변수모델에 추가할 객체에 적용할 수 있습니다.
  • HTTP 요청의 parameter(쿼리 스트링, 폼 데이터)를 자동으로 객체에 바인딩함.
  • GET/POST 요청 모두 지원.
  • 내부적으로 setName(), setAge() 같은 setter 메서드를 사용하여 값을 할당함.
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class UserController {

    @PostMapping("/user")
    public String submitForm(@ModelAttribute UserForm userForm) {
        System.out.println("이름: " + userForm.getName());
        System.out.println("나이: " + userForm.getAge());
        return "success";  // 성공 페이지 반환
    }
}

@ModelAttribute 생략 가능

@PostMapping("/user")
public String submitForm(UserForm userForm) {  // 생략 가능
    return "success";
}

기본값 설정하기

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class UserForm {
    private String name = "기본 이름";
    private int age = 20;
}

@ModelAttribute로 일부 필드만 바인딩 제외

특정 필드를 바인딩에서 제외하려면 @JsonIgnore 또는 @Transient를 사용

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class UserForm {
    private String name;
    private int age;

    @JsonIgnore  // 이 필드는 요청 데이터에서 매핑되지 않음
    private String hiddenField;
}

@ModelAttribute로 바인딩하는 객체에 데이터를 매핑하는 방식

Setter 방식 (기본 동작)

Spring은 기본적으로 기본 생성자로 객체를 만든 후, setter 메서드를 사용하여 필드 값을 설정합니다.

@Getter
@Setter
public class UserForm {
    private String name;
    private int age;
}

생성자 바인딩 (@AllArgsConstructor)

@AllArgsConstructor를 사용하면 모든 필드를 포함하는 생성자로 바인딩할 수 있습니다.

@AllArgsConstructor와 함께 필드를 final로 선언하면 setter 없이도 값이 주입됩니다.

@Getter
@AllArgsConstructor
public class UserForm {
    private final String name;
    private final int age;
}

Record 사용 (Java 14+)

record는 불변 객체이며, Spring이 자동으로 생성자를 사용하여 값을 주입합니다.

public record UserForm(String name, int age) {}

@AllArgsConstructor나 record를 사용할 때는 모든 필드가 필수값으로 요청

데이터 바인딩 방식이 생성자를 통해 값을 주입할 때, 모든 매개변수가 필요하기 때문입니다.

@AllArgsConstructor 사용 시 기본값 설정

기본값 설정

@Getter
public class UserForm {
    private final String name;
    private final int age;

    public UserForm(String name, Integer age) {
        this.name = name;
        this.age = (age != null) ? age : 20;  // 기본값 20 설정
    }
}

Optional 활용

public UserForm(String name, Optional<Integer> age) {
    this.name = name;
    this.age = age.orElse(20);
}

record 사용 시 기본값 설정

기본값이 있는 보조 생성자 사용

nullable 필드는 Integer 타입으로 변경

record는 불변 객체이므로, Optional<Integer> age 같은 방식은 사용 불가

public record UserForm(String name, Integer age) {
    public UserForm(String name, Integer age) {
        this.name = name;
        this.age = (age != null) ? age : 20;
    }
}

바인딩 객체 검증

@Valid를 사용한 DTO 객체 검증

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;

@Getter
@Setter
public class UserForm {
    
    @NotBlank(message = "이름은 필수 입력 값입니다.")
    private String name;

    @Min(value = 18, message = "나이는 18세 이상이어야 합니다.")
    private int age;
}
import jakarta.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class UserController {

    @PostMapping("/user")
    public String submitForm(@Valid @ModelAttribute UserForm userForm, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return "userForm"; // 에러 발생 시 다시 폼 페이지로 이동
        }
        
        System.out.println("이름: " + userForm.getName());
        System.out.println("나이: " + userForm.getAge());
        return "success"; // 성공 페이지 반환
    }
}

@Valid를 사용한 record객체 검증

public record UserForm(
    @NotBlank(message = "이름은 필수 입력 값입니다.") String name,
    @Max(value = 40, message = "나이는 40세 이하이어야 합니다.") Integer age
) {
    public UserForm(String name, Integer age) {
        this.name = name;
        this.age = (age != null) ? age : 20;  // 기본값 20 설정
    }
}
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/api")
public class UserApiController {

    @PostMapping("/user")
    public ResponseEntity<?> submitForm(@Valid @RequestBody UserForm userForm, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            Map<String, String> errors = new HashMap<>();
            for (FieldError error : bindingResult.getFieldErrors()) {
                errors.put(error.getField(), error.getDefaultMessage());
            }
            return ResponseEntity.badRequest().body(errors);
        }
        
        return ResponseEntity.ok("User created successfully");
    }
}

 

728x90
반응형
반응형
300x250