backend
  • README
  • DOCS
    • Java Docs
    • Servlet Docs
    • JSP Docs
    • DB & SQL Docs
    • Spring Boot Docs
    • Spring Security Docs
    • AWS Docs
  • 설치하기
    • Intellij 설정
  • 자바
    • 01 Java란?
    • 02 자바 시작하기
    • 03 자료형과 연산자
    • 04 제어문
    • 05 메소드
    • 06 클래스 기초
      • Static 보충자료
      • 패키지 보충자료
    • 07 객체지향 프로그래밍
    • 08 클래스 더 알아보기
      • 열거형 ENUM 보충자료
    • 09 클래스와 자료형
      • 다형성 보충자료
      • 제네릭 보충자료
    • 10 컬렉션 프레임워크
      • 컬렉션 프레임워크 보충자료
    • 11 람다식과 함수형 프로그래밍
      • 람다식 보충자료
    • 12 오류 대비하기
      • 오류 보충자료
    • 13 멀티태스킹
      • 멀티태스킹 보충자료
    • 교재보충
      • java.lang
  • 스프링
    • 서블릿, JSP
      • 05 Servlet(서블릿)
        • 서블릿 보충자료
        • 서블릿 추가코드
        • XML, YAML, JSON
      • 06 JSP(자바 서버 페이지)
        • JSP 보충자료
      • 07 JSTL(JSP 스탠다드 태그 라이브러리)
        • JSTL 보충자료
      • 08 Cookie(쿠키), Session(세션)
      • 09 서블릿,필터,리스너
        • 서블릿,필터,리스너 보충자료
      • 11 도서관리 프로젝트 실습
    • Spring Boot
      • 01 스프링 등장 배경, 객체지향
        • 스프링 등장 배경, 객체지향 보충자료
      • 02 IOC(제어의 역전), DI(의존성 주입)
        • IOC 보충자료
        • DI 보충자료
      • 03 스프링 구조
        • 스프링 구조 보충설명
      • 04 테스트코드 실습
      • 05 스프링 빈 설정
        • 스프링 빈 설정 보충자료
      • 06 싱글톤
        • 싱글톤 보충 자료
      • 07 스프링 빈 자동설정
        • 스프링 빈 자동설정 보충자료
      • 08 빈 생명주기
        • 빈 생명주기 보충자료
      • 09 빈 스코프
        • 빈 스코프 보충자료
      • 10 스프링 MVC
        • 스프링 MVC 보충자료
        • 데이터베이스 연동에 필요한 부분
      • 11 Validation(검증)
        • Validation(검증) 보충자료
      • 12 Bean Validation(빈검증)
        • Bean Validation(빈검증) 보충자료
      • 13 예외처리
        • 예외처리 보충자료
      • 14 타입변환
      • 15 JDBC(Java Database Connectivity)
      • 16 커넥션풀
      • 17 트랜잭션
        • 트랜잭션 보충자료
      • 18 JDBC 템플릿 활용
      • 19 MyBatis
      • 20 JPA(Java Persistence API)
      • 22 게시판 프로젝트 실습
    • Spring Security
      • 보안(Security)
      • Spring Security
      • 2. Spring Security 알아보기
        • 보안 위협 실제 사례와 방어 전략
      • 3. Spring Security 기본 동작 흐름
      • 4. Spring Security로 인증 권한 추가하기
        • Spring Security의 인증 및 인가
      • 5. Spring Security에서 세션 관리하기
        • 세션(Session)과 쿠키(Cookie) 비교, 토큰(Token)과의 관계
        • 해싱 및 해싱알고리즘
        • base64
      • 6. Spring Security 악용 보호
        • SameSite
      • 7. Spring Security로 인가 권한 추가하기
      • 8. Bcrypt(비크립트) 암호화
      • OAuth2 적용하기
  • 네트워크
    • HTTP
    • OSI 7계층
  • DB&SQL
    • 01 Database(데이터베이스)와 SQL 개요
    • 02 관계형 모델
    • 03 집합
    • 04 JOIN 연산
    • 05 MySQL
      • 세이브포인트
      • DBeaver, Mysql 오토커밋 설정 관련
    • 06 SQL 기초
      • 예시데이터 쿼리문
    • 07 SQL 실습
      • 실습 스키마
    • 08 Join 활용
      • 실습스키마
    • 09 SQL 활용
      • 실습스키마
    • 10 정규화
      • 실습 스키마
    • 데이터타입
    • 예시 프로젝트 스키마 구성
  • AWS
    • SSL 연결하기
    • 보충설명
Powered by GitBook
On this page
  1. 스프링
  2. Spring Boot

12 Bean Validation(빈검증)

p210 - build.gradle

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.3.3'
	id 'io.spring.dependency-management' version '1.1.6'
}

group = 'spring'
version = '0.0.1-SNAPSHOT'

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(21)
	}
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-validation'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks.named('test') {
	useJUnitPlatform()
}

p211 - src/main/java/spring/mvc/domain/Book

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Book {
    // 인덱스 번호
    private Long bookIdx;
    // 도서 이름
    @NotBlank(groups = {BookCreate.class})
    private String title;
    // 출판사
    @NotBlank(groups = {BookCreate.class})
    private String publisher;
    // 판매 가격
    @NotNull(groups = {BookCreate.class, BookUpdate.class})
    @Min(0)
    private int salePrice;
    // 대여 비용
    private int rentalPrice;
    // 대여자
    private String renter;
    // 등록일자
    private LocalDate registrationDate;
    // 수정일자
    private LocalDate updateDate;
}

p212 - src/test/java/spring/mvc/domain/BookBeanTest

class BookBeanTest {

    private Validator validator;

    @BeforeEach
    void setUp() {
        // Validator를 초기화합니다.
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
    }

    @Test
    void testValidBookBean() {
        // 모든 필드가 올바른 경우
        Book book = new Book(1L, "Effective Java", "Addison-Wesley", 50000, 3000, "John Doe", LocalDate.now(), LocalDate.now());

        Set<ConstraintViolation<Book>> violations = validator.validate(book);
        assertTrue(violations.isEmpty(), "유효한 Book 객체여야 합니다.");
    }

    @Test
    void testInvalidBookBean_NullBookIdx() {
        // bookIdx가 null인 경우
        Book book = new Book(null, "Effective Java", "Addison-Wesley", 50000, 3000, "John Doe", LocalDate.now(), LocalDate.now());

        Set<ConstraintViolation<Book>> violations = validator.validate(book);
        assertEquals(1, violations.size(), "bookIdx가 null일 때 검증 오류가 발생해야 합니다.");

        ConstraintViolation<Book> violation = violations.iterator().next();
        assertEquals("bookIdx", violation.getPropertyPath().toString(), "오류가 발생한 필드는 bookIdx이어야 합니다.");
    }

    @Test
    void testInvalidBookBean_NullTitle() {
        // title이 null인 경우
        Book book = new Book(1L, null, "Addison-Wesley", 50000, 3000, "John Doe", LocalDate.now(), LocalDate.now());

        Set<ConstraintViolation<Book>> violations = validator.validate(book);
        assertEquals(1, violations.size(), "title이 null일 때 검증 오류가 발생해야 합니다.");

        ConstraintViolation<Book> violation = violations.iterator().next();
        assertEquals("title", violation.getPropertyPath().toString(), "오류가 발생한 필드는 title이어야 합니다.");
    }

    @Test
    void testInvalidBookBean_BlankTitle() {
        // title이 공백인 경우
        Book book = new Book(1L, "   ", "Addison-Wesley", 50000, 3000, "John Doe", LocalDate.now(), LocalDate.now());

        Set<ConstraintViolation<Book>> violations = validator.validate(book);
        assertEquals(1, violations.size(), "title이 공백일 때 검증 오류가 발생해야 합니다.");

        ConstraintViolation<Book> violation = violations.iterator().next();
        assertEquals("title", violation.getPropertyPath().toString(), "오류가 발생한 필드는 title이어야 합니다.");
    }

    @Test
    void testInvalidBookBean_NullPublisher() {
        // publisher가 null인 경우
        Book book = new Book(1L, "Effective Java", null, 50000, 3000, "John Doe", LocalDate.now(), LocalDate.now());

        Set<ConstraintViolation<Book>> violations = validator.validate(book);
        assertEquals(1, violations.size(), "publisher가 null일 때 검증 오류가 발생해야 합니다.");

        ConstraintViolation<Book> violation = violations.iterator().next();
        assertEquals("publisher", violation.getPropertyPath().toString(), "오류가 발생한 필드는 publisher이어야 합니다.");
    }

    @Test
    void testInvalidBookBean_BlankPublisher() {
        // publisher가 공백인 경우
        Book book = new Book(1L, "Effective Java", "   ", 50000, 3000, "John Doe", LocalDate.now(), LocalDate.now());

        Set<ConstraintViolation<Book>> violations = validator.validate(book);
        assertEquals(1, violations.size(), "publisher가 공백일 때 검증 오류가 발생해야 합니다.");

        ConstraintViolation<Book> violation = violations.iterator().next();
        assertEquals("publisher", violation.getPropertyPath().toString(), "오류가 발생한 필드는 publisher이어야 합니다.");
    }

    @Test
    void testInvalidBookBean_NegativeSalePrice() {
        // salePrice가 음수인 경우
        Book book = new Book(1L, "Effective Java", "Addison-Wesley", -100, 3000, "John Doe", LocalDate.now(), LocalDate.now());

        Set<ConstraintViolation<Book>> violations = validator.validate(book);
        assertEquals(1, violations.size(), "salePrice가 음수일 때 검증 오류가 발생해야 합니다.");

        ConstraintViolation<Book> violation = violations.iterator().next();
        assertEquals("salePrice", violation.getPropertyPath().toString(), "오류가 발생한 필드는 salePrice이어야 합니다.");
    }
}

p213 - src/main/resources/templates/validation/bookBean.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <title>책 관리 폼</title>
</head>
<body>
<h1>책 관리</h1>

<!-- 에러 메시지 표시 -->
<div th:if="${error}" style="color: red;">
  <p th:text="${error}"></p>
</div>

<!-- 성공 메시지 표시 -->
<div th:if="${success}" style="color: green;">
  <p th:text="${success}"></p>
</div>

<form th:action="@{/validation/bean/books}" th:object="${book}" method="post">
  <label for="title">책 제목:</label>
  <input type="text" th:field="*{title}" id="title"><br>
  <div th:if="${#fields.hasErrors('title')}" style="color: red;">
    <p th:errors="*{title}"></p>
  </div>

  <label for="publisher">저자:</label>
  <input type="text" th:field="*{publisher}" id="publisher"><br>
  <div th:if="${#fields.hasErrors('publisher')}" style="color: red;">
    <p th:errors="*{publisher}"></p>
  </div>

  <label for="salePrice">가격:</label>
  <input type="text" th:field="*{salePrice}" id="salePrice"><br>
  <div th:if="${#fields.hasErrors('salePrice')}" style="color: red;">
    <p th:errors="*{salePrice}"></p>
  </div>

  <button type="submit">제출</button>
</form>
</body>
</html>

p213

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <title>책 상세 정보</title>
</head>
<body>
<h1>책 상세 정보</h1>

<!-- 에러 메시지 표시 -->
<div th:if="${error}" style="color: red;">
  <p th:text="${error}"></p>
</div>

<!-- 성공 메시지 표시 -->
<div th:if="${success}" style="color: green;">
  <p th:text="${success}"></p>
</div>

<!-- 책 상세 정보 표시 -->
<div th:if="${book}">
  <p><strong>책 제목:</strong> <span th:text="${book.title}"></span></p>
  <p><strong>저자:</strong> <span th:text="${book.publisher}"></span></p>
  <p><strong>가격:</strong> <span th:text="${book.salePrice}"></span></p>
</div>

<!-- 책 수정 폼 -->
<h2>책 정보 수정</h2>
<div th:if="${book}">
  <form th:action="@{/validation/bean/books/{bookIdx}/update(bookIdx=${book.bookIdx})}" th:object="${book}" method="post">
    <label for="title">책 제목:</label>
    <input type="text" id="title" name="title" th:value="*{title}"><br>

    <label for="publisher">저자:</label>
    <input type="text" id="publisher" name="publisher" th:value="*{publisher}"><br>

    <label for="salePrice">가격:</label>
    <input type="number" id="salePrice" name="salePrice" th:value="*{salePrice}"><br>

    <button type="submit">수정</button>
  </form>
</div>

</body>
</html>

p214,215 - src/main/java/spring/mvc/controller/ValidationBeanController

@Slf4j
@Controller
@RequestMapping("/validation/bean")
public class ValidationBeanController {

    private final BookService bookService;

    public ValidationBeanController(BookService bookService) {
        this.bookService = bookService;
    }

    @GetMapping("/books")
    public String showBookForm(Model model) {
        model.addAttribute("book", new Book());  // 빈 Book 객체를 모델에 추가
        return "validation/bookBean";
    }

    // CREATE: 책 생성 - BindingResult 사용
    @PostMapping("/books")
    public String createBook(@Validated(BookCreate.class) @ModelAttribute Book book, BindingResult bindingResult, Model model) {
        // 검증 실패 시, 다시 폼 페이지로 이동
        if (bindingResult.hasErrors()) {
            log.info("errors = {}", bindingResult);
            return "validation/bookBean";
        }

        // 검증이 통과된 경우 책 생성
        Book createdBook = bookService.createBook(book);
        model.addAttribute("book", createdBook);
        model.addAttribute("success", "책이 성공적으로 생성되었습니다!");
        return "validation/bookBean";
    }

    // READ: ID로 책 검색 - 수동 검증
    @GetMapping("/books/{bookIdx}")
    public String getBookById(@Validated @PathVariable Long bookIdx, Model model) {
        if (bookIdx <= 0) {
            model.addAttribute("error", "책 ID는 0보다 큰 값이어야 합니다.");
            return "validation/bookDetailBean";
        }

        Optional<Book> book = bookService.getBookById(bookIdx);
        if (book.isPresent()) {
            model.addAttribute("book", book.get());
        } else {
            model.addAttribute("error", "책을 찾을 수 없습니다.");
        }
        return "validation/bookDetailBean";
    }

    // UPDATE: 책 정보 수정 - BindingResult 사용
    @PostMapping("/books/{bookIdx}/update")
    public String updateBook(@PathVariable Long bookIdx, @Validated(BookUpdate.class) @ModelAttribute Book updatedBook, BindingResult bindingResult, Model model) {
        // 검증 실패 시, 다시 폼 페이지로 이동
        if (bindingResult.hasErrors()) {
            return "validation/bookBean";
        }
        Optional<Book> book = bookService.updateBook(bookIdx, updatedBook);
        if (book.isPresent()) {
            model.addAttribute("book", book.get());
            model.addAttribute("success", "책 정보가 성공적으로 수정되었습니다!");
        } else {
            model.addAttribute("error", "책을 찾을 수 없습니다.");
        }
        return "validation/bookBean";
    }
}

p217, 219 - src/main/resources/messages.properties

typeMismatch=올바른 형식의 값을 입력해주세요.
typeMismatch.int=숫자를 입력해주세요.
NotBlank.book.title = 책 제목은 공백일 수 없습니다.
NotBlank={0} 공백일 수 없습니다
Min={0}, 최소 {1}

p218, 219 - src/main/resources/templates/validation/bookFormBinding.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <title>책 관리 폼</title>
</head>
<body>
<h1>책 관리</h1>

<!-- 에러 메시지 표시 -->
<div th:if="${error}" style="color: red;">
  <p th:text="${error}"></p>
</div>

<!-- 성공 메시지 표시 -->
<div th:if="${success}" style="color: green;">
  <p th:text="${success}"></p>
</div>

<form th:action="@{/validation/form/binding/books}" th:object="${book}" method="post">
  <label for="title">책 제목:</label>
  <input type="text" th:field="*{title}" id="title"><br>
  <div th:if="${#fields.hasErrors('title')}" style="color: red;">
    <p th:errors="*{title}"></p>
  </div>

  <label for="publisher">저자:</label>
  <input type="text" th:field="*{publisher}" id="publisher"><br>
  <div th:if="${#fields.hasErrors('publisher')}" style="color: red;">
    <p th:errors="*{publisher}"></p>
  </div>

  <label for="salePrice">가격:</label>
  <input type="text" th:field="*{salePrice}" id="salePrice"><br>
  <div th:if="${#fields.hasErrors('salePrice')}" style="color: red;">
    <p th:errors="*{salePrice}"></p>
  </div>

  <button type="submit">제출</button>
</form>
</body>
</html>

p221, 222 - src/main/java/spring/mvc/domain/BookCreate, BookUpdate

package spring.mvc.domain;

public interface BookCreate {
}
package spring.mvc.domain;

public interface BookUpdate {
}

p221, 222 - src/main/java/mvc/domain/Book

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Book {
    // 인덱스 번호
    private Long bookIdx;
    // 도서 이름
    @NotBlank(groups = {BookCreate.class})
    private String title;
    // 출판사
    @NotBlank(groups = {BookCreate.class})
    private String publisher;
    // 판매 가격
    @NotNull(groups = {BookCreate.class, BookUpdate.class})
    @Min(0)
    private int salePrice;
    // 대여 비용
    private int rentalPrice;
    // 대여자
    private String renter;
    // 등록일자
    private LocalDate registrationDate;
    // 수정일자
    private LocalDate updateDate;
}

p223 - src/main/java/mvc/dto/BookCreateForm, BookUpdateForm

@Data
public class BookCreateForm {
    // 도서 이름
    @NotBlank
    private String title;
    // 출판사
    @NotBlank
    private String publisher;
    // 판매 가격
    @NotNull
    @Min(0)
    private int salePrice;
}
@Data
public class BookUpdateForm {
    // 도서 이름
    @NotBlank
    private String title;
    // 출판사
    @NotBlank
    private String publisher;
}

p224, 225 - src/main/java/mvc/controller/ValidationBeanFormSeperateController

@Slf4j
@Controller
@RequestMapping("/validation/bean/seperate")
public class ValidationBeanFormSeperateController {

    private final BookService bookService;

    public ValidationBeanFormSeperateController(BookService bookService) {
        this.bookService = bookService;
    }

    @GetMapping("/books")
    public String showBookForm(Model model) {
        model.addAttribute("book", new Book());  // 빈 Book 객체를 모델에 추가
        return "validation/bookBean";
    }

    // CREATE: 책 생성 - BindingResult 사용
    @PostMapping("/books")
    public String createBook(@Validated @ModelAttribute("book") BookCreateForm book, BindingResult bindingResult, Model model) {
        // 검증 실패 시, 다시 폼 페이지로 이동
        if (bindingResult.hasErrors()) {
            log.info("errors = {}", bindingResult);
            return "validation/bookBean";
        }

        Book newBook = new Book();
        newBook.setTitle(book.getTitle());
        newBook.setPublisher(book.getPublisher());
        newBook.setSalePrice(book.getSalePrice());

        // 검증이 통과된 경우 책 생성
        Book createdBook = bookService.createBook(newBook);
        model.addAttribute("book", createdBook);
        model.addAttribute("success", "책이 성공적으로 생성되었습니다!");
        return "validation/bookBean";
    }

    // READ: ID로 책 검색 - 수동 검증
    @GetMapping("/books/{bookIdx}")
    public String getBookById(@Validated @PathVariable Long bookIdx, Model model) {
        if (bookIdx <= 0) {
            model.addAttribute("error", "책 ID는 0보다 큰 값이어야 합니다.");
            return "validation/bookDetailBean";
        }

        Optional<Book> book = bookService.getBookById(bookIdx);
        if (book.isPresent()) {
            model.addAttribute("book", book.get());
        } else {
            model.addAttribute("error", "책을 찾을 수 없습니다.");
        }
        return "validation/bookDetailBean";
    }

    // UPDATE: 책 정보 수정 - BindingResult 사용
    @PostMapping("/books/{bookIdx}/update")
    public String updateBook(@PathVariable Long bookIdx, @Validated @ModelAttribute("book") BookUpdateForm bookUpdateForm, BindingResult bindingResult, Model model) {
        // 검증 실패 시, 다시 폼 페이지로 이동
        if (bindingResult.hasErrors()) {
            return "validation/bookBean";
        }

        // 기존 책 정보 가져오기
        Optional<Book> existingBook = bookService.getBookById(bookIdx);
        if (existingBook.isEmpty()) {
            model.addAttribute("error", "책을 찾을 수 없습니다.");
            return "validation/bookBean";
        }

        // 기존 책 정보 업데이트
        Book bookToUpdate = existingBook.get();
        bookToUpdate.setTitle(bookUpdateForm.getTitle());
        bookToUpdate.setPublisher(bookUpdateForm.getPublisher());

        // 업데이트된 책 정보 저장
        Optional<Book> updatedBook = bookService.updateBook(bookIdx, bookToUpdate);
        if (updatedBook.isPresent()) {
            model.addAttribute("book", updatedBook.get());
            model.addAttribute("success", "책 정보가 성공적으로 수정되었습니다!");
        } else {
            model.addAttribute("error", "책 정보 업데이트에 실패했습니다.");
        }

        return "validation/bookBean";
    }
}

PreviousValidation(검증) 보충자료NextBean Validation(빈검증) 보충자료

Last updated 6 months ago