1.  등록 처리와 화면 개발

등록 처리 시에는 @Valid를 이용해서 서버에서도 검증한 후에 등록하는 방식을 적용

@Valid를 위해서 bulid.gradle에 validation 관련 라이브러리를 추가
implementation 'org.springframework.boot:spring-boot-starter-validation'

 

BoardDTO에는 제목이나 내용, 작성자 등이 비어 있지 않도록 어노테이션들을 처리
public class BoardDTO {

    private Long bno;

    @NotEmpty
    @Size(min = 3, max = 100)
    private String title;

    @NotEmpty
    private String content;

    @NotEmpty
    private String writer;

    private LocalDateTime regDate;
    private LocalDateTime modDate;

}

1)  BoardController 처리

  🚀  등록 처리는 GET 방식으로 화면을 보고 POST 방식으로 처리
  🚀  @Valid에서 문제가 발생했을 때 모든 에러를 errors라는 이름으로 RedirectAttributes에 추가해서 전송

@GetMapping("/register")
public void registerGet() {}

@PostMapping("/register")
public String registerPost(@Valid BoardDTO boardDTO,
                           BindingResult bindingResult, 
                           RedirectAttributes redirectAttributes) {
    log.info("board POST register...");

    if (bindingResult.hasErrors()) {
        log.info("has errors...");
        redirectAttributes.addFlashAttribute("errors", bindingResult.getAllErrors());

        return "redirect:/board/register";
    }

    log.info(boardDTO);
    Long bno = boardService.register(boardDTO);
    redirectAttributes.addFlashAttribute("result", bno);
    return "redirect:/board/list";
}
  💡  addFlashAttribute()
      -  Spring Framework에서 제공하는 메소드로, 주로 리다이렉트 시 데이터를 일시적으로 저장하는 데 사용
      -  Flash 속성은 리다이렉트 이후에만 유효하며, 한 번 읽히면 소멸하는 특성이 있음
      -  이를 통해 사용자는 브라우저를 새로고침하거나 직접 URL을 입력해도 해당 데이터가 유지되지 않도록 할 수 있음

 


2) register.html 처리

  🚀  templates/board 폴더에 register.html을 추가

<!DOCTYPE html>
<html lang="en"
    xmlns:th="http://www.thymeleaf.org"
    xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
    layout:decorate="~{layout/basic.html}">
<head>
    <meta charset="UTF-8">
    <title>Board Register</title>
</head>

<div layout:fragment="content">
    <div class="row mt-3">
        <div class="col">
            <div class="card">
                <div class="card-header">
                    Board Register
                </div>
                <div class="card-body">

                </div>
            </div>
        </div>
    </div>
</div>

<script layout:fragment="script" th:inline="javascript">
</script>

 

 

  🚀  <div class="card-body"> 부분에는 <form> 태그를 이용해서 입력할 수 있는 부분을 추가
  🚀  게시물 등록에는 제목 title, 내용 content, 작성자 writer가 추가

<div class="card-body">
<form action="/board/register" method="post">
    <div class="input-group mb-3">
        <span class="input-group-text">Title</span>
        <input type="text" name="title" class="form-control" placeholder="Title">
    </div>

    <div class="input-group mb-3">
        <span class="input-group-text">Content</span>
        <textarea class="form-control col-sm-5" rows="5" name="content"></textarea>
    </div>

    <div class="input-group mb-3">
        <span class="input-group-text">Writer</span>
        <input type="text" name="writer" class="form-control" placeholder="Writer">
    </div>

    <div class="my-4">
        <div class="float-end">
            <button type="submit" class="btn btn-primary">Submit</button>
            <button type="reset" class="btn btn-secondary">Reset</button>
        </div>
    </div>
</form>
</div><!-- end card body -->


3) @Valid의 에러 메서드 처리

  🚀  등록은 @Valid를 통해서 검증하므로 검증에 실패하면 다시 앞의 화면으로 이동
  🚀  이때 addFlashAttribute()를 통해서 'errors' 라는 이름으로 에러 메시지들이 전송됨

<script layout:fragment="script" th:inline="javascript">
    const errors = [[${errors}]];
    console.log(errors);

    let errorMsg = '';

    if(errors) {
        for (let i = 0; i < errors.length; i++) {
            errorMsg += `${errors[i].field}은(는) ${errors[i].code} \n`;
        }
        alert(errorMsg);
    }
</script>

 

  💡  Thymeleaf의 인라인 기능을 이용하면 'errors'를 자바 스크립트의 배열로 처리할 수 있기 때문에 이를 이용해서 앞의 메시지를 작성할 수 있음
  💡  브라우저를 통해서 아무것도 입력하지 않은 상태에서 submit이 실행되면 경고창이 보임
  💡  콘솔창에는 배열로 만들어진 에러 메시지를 출력

 


4)  정상적인 처리와 모달창

  🚀  등록 화면에서 필요한 내용들이 추가되면 정상적으로 '/board/list'로 이동하는 것을 확인
  🚀  목록 화면으로 이동했을 때 BoardController에서 RedirectAttributes의 addFlashAttribute()를 이용해서 'result'라는 데이터를 추가적으로 전달. addFlashAttribute()로 전달된 데이터는 쿼리 스트링으로 처리되지 않기 때문에 브라우저의 경로에는 보이지 않음
  🚀  list.html에서는 이렇게 전달된 result 변수를 이용해서 모달창으로 처리

 

list.html의 <script> 태그의 마지막 부분에 코드 추가
// show modal
const result = [[${result}]];

if(result) {
    modal.show();
}

경고창이 동작한 것을 확인후에 모달창을 추가
- 모달창의 코드는 부트스트랩의 Componets 메뉴를 통해서 확인

 

  ✓  list.html에 모달창 코드를 추가

  ✓  자바스크립트에서는 부트스트랩의 함수를 이용해서 모달창이 보이도록 처리

<div class="modal" tabindex="-1">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">Modal title</h5>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            <div class="modal-body">
                <p>Modal body text goes here.</p>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
                <button type="button" class="btn btn-primary">Save changes</button>
            </div>
        </div>
    </div>
</div>
// show modal
const result = [[${result}]];

const modal = new bootstrap.Modal(document.querySelector(".modal"));

if(result) {
    modal.show();
}

 

  ✓  변경된 코드가 적용되면 등록 후에는 모달창이 보이게 되고, 직접 '/borad/list'로 접근할 때는 모달창이 보이지 않게 됨
  ✓  이를 이용해서 사용자에게 어떠한 처리가 완료 되었는지에 대한 결과를 알려줄 수 있음

 

 

 

[ 내용 참고 : IT 학원 강의 ]


1.  Todo 기능 개발

등록 작업의 경우 TodoMapper ▶️ TodoService ▶️ TodoController ▶️ JSP의 순서로 처리


1) TodoMapper 개발 및 테스트

 

TodoMapper에는 TodoVO를 파라미터로 받는 insert()를 추가
public interface TodoMapper {

    String getTime();

    void insert(TodoVO todoVO);
    
}

 

TodoMapper.xml에 insert를 다음과 같이 구현
<insert id="insert">
        INSERT INTO tbl_todo (title, dueDate, writer) VALUES (#{title}, #{dueDate}, #{writer})
</insert>

 

Mybatis를 이용하면 "?" 대신에 "#{title}" 같이 파라미터를 처리, "#{title}" 부분은 PreparedStatement로 변경되면서 "?"로 처리되고, 주어진 객체의 getTitle()을 호출한 결과를 적용

 

테스트 코드를 이용해서 TodoVO의 입력을 확인
 @Test
    public void testInsert() {
        TodoVO todoVO = TodoVO.builder() // 빌더를 이용해서 TodoVO 객체를 생성
                .title("스프링 테스트")
                .dueDate(LocalDate.of(2022, 10, 10))
                .writer("user00")
                .build();
        todoMapper.insert(todoVO);
    }

 

  테스트 실행 후에 tbl_todo 테이블을 조회해서 insert가 완료되었는지 확인

 


2) TodoService와 TodoServiceImpl 클래스


TodoMapper와 TodoController 사이에는 서비스 계층을 설계해서 적용
TodoService 인터페이스를 먼저 추가하고, 이를 구현한 TodoServiceImpl을 스프링 빈으로 처리

public interface TodoService {
    void register(TodoDTO todoDTO);
}

 

  - TodoService 인터페이스에 추가한 register()는 여러 개의 파라미터 대신에 TodoDTO로 묶어서 전달 받도록 구성

 

 

TodoService 인터페이스를 구현하는 TodoServiceImpl에는 의존성 주입을 이용해서 데이터베이스 처리를 하는 TodoMapper와 DTO, VO의 변환을 처리하는 ModelMapper를 주입

 

    ⚡️  의존성 주입이 사용되는 방식은 의존성 주입이 필요한 객체의 타입을 final로 고정하고 @RequiredArgsConstructor를 이용해서 생성자를 생성하는 방식을 사용

    ⚡️  register() 에서는 주입된 ModelMapper를 이용해서 TodoDTO를 TodoVO로 변환하고 이를 TodoMapper를 통해서 insert 처리

@Log4j2
@Service
@RequiredArgsConstructor // 생성자 객체 주입. private final로 선언된 참조변수에 객체를 저장하는 생성자 작성.
public class TodoServiceImpl implements TodoService{
    private final TodoMapper todoMapper;
    private final ModelMapper modelMapper;

    @Override
    public void register(TodoDTO todoDTO) {
        // 1) todoDTO를 전달 받아 2) todoDTO를 todoVO로 변환 후 3) dao의 insert()호출
        log.info(todoDTO);
        TodoVO todoVO = modelMapper.map(todoDTO, TodoVO.class);
        log.info(todoVO);
        todoMapper.insert(todoVO);
    }
}

 

service 패키지는 root-context.xml에서 component-scan 패키지로 추가

<context:component-scan base-package="com.example.spring_ex_01_2404.service"/>

 


3)  TodoService 테스트


서비스 계층에서 DTO를 VO로 변환하는 작업을 처리하기 때문에 가능하면 테스트를 진행해서 문제가 없는지 확인하는 것이 좋음
test 폴더내에 service 관련 패키지를 생성하고 TodoServiceTest 클래스를 작성

@Log4j2
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "file:src/main/webapp/WEB-INF/root-context.xml")
class TodoServiceTests {
    @Autowired
    private TodoService todoService;

    @Test
    public void testRegister() {
        TodoDTO todoDTO = TodoDTO.builder()
                .title("test...")
                .dueDate(LocalDate.now())
                .writer("user1")
                .build();
        todoService.register(todoDTO);
    }
}


4)  TodoController의 GET / POST 처리


서비스 계층까지 문제 없이 동작하는 것을 확인했다면 스프링 MVC를 처리
입력할 수 있는 화면을 위해 controller 패키지의 TodoController를 확인

 

TodoController에 GET 방식으로 '/todo/register'가 가능한지 확인
@Log4j2
@Controller
@RequestMapping("/todo")
@RequiredArgsConstructor
public class TodoController {

    @GetMapping("/register")
    public void register() {
        log.info("todo register...");
    }
}

 

/WEB-INF/view/todo/ 폴더에 register.jsp는 test.html을 복사해서 구성. 상단에 JSP 관련 설정을 추가.
register.jsp에 class 속성이 "card-body"로 지정된 부분의 코드를 수정
입력하는 화면의 디자인은 https://getbootstrap.com/docs/5.1/forms/form-control/

 

Form controls

Give textual form controls like <input>s and <textarea>s an upgrade with custom styles, sizing, focus states, and more.

getbootstrap.com

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<div class="card-body">
    <form  method="post">
        <div class="input-group mb-3">
            <span class="input-group-text">Title</span>
            <input type="text" name="title" class="form-control" placeholder="Title">
        </div>
        <div class="input-group mb-3">
            <span class="input-group-text">DueDate</span>
            <input type="date" name="dueDate" class="form-control">
        </div>
        <div class="input-group mb-3">
            <span class="input-group-text">Writer</span>
            <input type="text" name="writer" class="form-control" placeholder="Writer">
        </div>
        <div class="my-4">
            <div class="float-end">
                <button type="submit" class="btn btn-primary">Submit</button>
                <button type="reset" class="btn btn-secondary">Reset</button>
            </div>
        </div>
    </form>
</div>

 

 POST 방식의 처리


    1.  register.jsp의 <form method="post"> 태그에 의해서 submit 버튼을 클릭하면 POST 방식으로 "title, dueDate, writer"를 전송하게 됨
    2.  TodoController에서는 TodoDTO로 바로 전달된 파라미터의 값들을 수집
    3.  POST 방식으로 처리된 후에는 "/register/list"로 이동해야 하므로 "redirect:/todo/list"로 이동할 수 있도록 문자열을 반환

@PostMapping("/register")
public String registerPOST(TodoDTO todoDTO) {
    log.info("POST todo register");
    log.info(todoDTO);
    return "redirect:/todo/list";
}

 

  ✓  한글 문제가 있기는 하지만 브라우저에서 입력한 데이터들이 수집되고 /todo/list로 이동하는 기능에 문제가 없는 것을 확인

 


2.  한글 처리를 위한 필터 설정

서버의 한글 처리에 대한 설정은 스프링 MVC에서 제공하는 필터로 쉽게 처리할 수 있음

 

web.xml에 필터에 대한 설정을 추가
    <filter>
        <filter-name>encoding</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encoding</filter-name>
        <servlet-name>appServlet</servlet-name>
    </filter-mapping>

 

  ✓  web.xml의 설정을 서버를 재시작해야 올바르게 반영되므로 톰캣을 재시작하고 한글 처리를 확인


3.  @Valid를 이용한 서버사이드 유효성 검증

유효성 검증


과거의 웹 개발에는 자바 스크립트를 이용하여 브라우저에서만 유효성 검사를 진행하는 방식이 많았지만, 모바일과 같이 다양한 환경에서 서버를 이용하는 현재에는 브라우저를 사용하는 프론트쪽에서의 검증과 더불어 서버에서도 입력되는 값들을 검증하는 것이 좋음. 이러한 검증 작업은 컨트롤러에서 진행하는데 스프링MVC의 경우 @Valid와 BindingResult라는 존재를 이용해서 간단하게 처리할 수 있음


  ⚡️  스프링 MVC에서 검증을 처리하기 위해서는 hibernate-validate 라이브러리가 필요 (build.gradle 에 추가)

// https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator
implementation 'org.hibernate.validator:hibernate-validator:6.2.1.Final'

 

hibernate-validate를 이용해서 사용하는 대표적인 어노테이션
어노테이션 설명 어노테이션 설명
@NotNull Null 불가 @Null Null만 입력 가능
@NotEmpty Null 빈 문자열 불가 @NotBlank Null 빈 문자열, 스페이스만 있는 문자열 불가
@SIze(min=, max=) 문자열, 배열 등의 크기가
만족하는가
@Pattern(regex=) 정규식을 만족하는가
@Max(num) 지정 값 이하인가 @Min(num) 지정 값 이상인가
@Future 현재 보다 미래인가 @Past 현재 보다 과거인가
@Positive 양수만 가능 @PositiveOrZero 양수와 0만 가능
@Negative  음수만 가능 @NegativeOrZero 음수와 0만 가능

 

 

1) TodoDTO 검증하기

 

TodoDTO에 간단한 어노테이션 적용
@ToString
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class TodoDTO {
    private Long tno;
    
    @NotEmpty
    private String title;
    
    @Future
    private LocalDate dueDate;
    
    private boolean finished;
    
    @NotEmpty
    private String writer;
}

 

 

TodoController에서 POST 방식으로 처리할 때 이를 반영하도록 BindingResult와 @Valid 어노테이션을 적용
 @PostMapping("/register")
    public String registerPOST(@Valid TodoDTO todoDTO, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
        log.info("POST todo register");

        if (bindingResult.hasErrors()) {
            log.info("has error...");
            redirectAttributes.addFlashAttribute("errors", bindingResult.getAllErrors());
            return "redirect:/todo/register";
        }

        log.info(todoDTO);
        return "redirect:/todo/list";
    }

 

  ✓  TodoDTO 에는 @Valid를 적용하고, BindingResult 타입을 파라미터로 새롭게 추가

    ➡️  BindingResult는  스프링이 제공하는 검증 오류를 보관하는 객체

  ✓  registerPOST() 에서는 hasErrors()을 이용해서 검증에 문제가 있다면 다시 입력화면으로 리다이렉트되도록 처리


  ✓  처리 과정에서 잘못된 결과는 RedirectAttributes의 addFlashAttribute()를 이용해서 전달

    ➡️ flash 속성에 객체를 저장, 요청 매개 변수(requestparameters)로 값을 전달하지않고 객체로 값을 그대로 전달. 일회성으로 한번 사용하면 Redirect후 값이 소멸


  ✓  TodoDTO의 writer는 @NotEmpty가 적용되어 있으므로 항목을 입력하지 않고 submit을 하면 다시 입력화면으로 돌아감

 

 

 

 

 


 

2) JSP에서 검증 에러 메시지 확인하기

 

register.jsp에 검증된 결과를 확인하기 위해 상단에 태그 라이브러리 추가
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

 

자바스크립트 객체를 생성해 둔 후 필요할 때 화면에서 처리
<form>태그가 끝난 후에 <script> 태그를 추가
<script>
    const serverValidResult = {};
    <c:forEach items="${errors}" var="error">
    serverValidResult['${error.getField()}'] = '${error.defaultMessage}';
    </c:forEach>
    console.log(serverValidResult);
</script>

 

  ✓  자바스크립트를 이용해서 오류 객체를 생성하면 나중에 화면에서 자유롭게 처리할 수 있다는 장점이 있음

  ✓  아무것도 입력하지 않은 상태에서 submit을 하면 다음과 같은 코드가 출력

아무 것도 입력하지 않았을 때와 과거 날짜를 입력했을 때 콘솔 창 출력 결과

 


4.  Todo 등록 기능 완성

입력값의 검증까지 끝났다면 최종적으로 TodoService를 주입하고, 연동하도록 구성

 

TodoController의 클래스 선언부에서 TodoService를 주입
@Log4j2
@Controller
@RequestMapping("/todo")
@RequiredArgsConstructor
public class TodoController {
    private final TodoService todoService;

 

registerPOST()에서는 TodoService의 기능을 호출하도록 구성
 @PostMapping("/register")
    public String registerPOST(@Valid TodoDTO todoDTO, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
        log.info("POST todo register");

        if (bindingResult.hasErrors()) {
            log.info("has error...");
            redirectAttributes.addFlashAttribute("errors", bindingResult.getAllErrors());
            return "redirect:/todo/register";
        }

        log.info(todoDTO);
        todoService.register(todoDTO); // 호출 코드 생성
        return "redirect:/todo/list";
    }

 

 

 

 

모든 기능의 개발이 완료 되었다면 등록 후에 "/todo/list"로 이동하게 됨. 아직 "/todo/list"의 개발은 완료되지 않았으니 데이터베이스를 이용해서 최종 확인

 

 

 

 

 

 

 

 

 

 

 

 

 

 

[ 내용 참고 : IT 학원 강의 ]

+ Recent posts