Spring Boot 3 & Spring Framework 6 - Section 8 :

이정수·2024년 7월 29일

Udemy학습-Spring & React

목록 보기
14/20
post-thumbnail

Todo 웹페이지 기능 구현하기

  • Description 추가 기능 구현하기
    。기존 static List에 입력된 내용을 바탕으로 새로운 Todo Class의 instance를 생성하여 추가하기.
    • Description을 추가하는 JSP page 생성
      <a>로 특정 url로 연결하는 버튼 생성해보기
      class="" 속성을 이용해 bootstrap 정의.
     <a href="add-todo" class="btn btn-secondary">Add Todo</a>

    • 다음처럼 클릭 시 add-todo의 url로 연결되는 버튼이 생성됨.

      bootstrap 버튼 색상 종류
      btn-primary : 파랑
      btn-secondary : 회색
      btn-success : 초록
      btn-danger : 빨강
      btn-warning : 노랑
      btn-info : 하늘
      btn-light : 흰색
      btn-dark : 검정



    • 다음 구문으로 연결되는 "addTodo"의 JSP Page( addTodo.jsp ) 생성하기
      。Textfield에 값을 입력 후 버튼을 누를경우 name=description 변수로 <form>의 data로서 POST로 전달됨.
      <!-- addTodo.jsp -->
      <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
    <!doctype html>
    <html lang="ko">
    <head>
        <link href="webjars/bootstrap/5.1.3/css/bootstrap.min.css" rel="stylesheet">
        <title>Document</title>
    </head>
    <body>
    <div class="container">
        <h1>Todo Details</h1>
        <form method="POST">
            <label for="textId">Description : </label>
            <input type="text" id="textId" name="description">
            <input type="submit" class="btn btn-secondary">
        </form>
    </div>
    <script src="webjars/bootstrap/5.1.3/js/bootstrap.min.js"></script>
    <script src="webjars/jquery/3.6.0/jquery.min.js"></script>
    </body>
    </html>

    • 다음 같은 결과 도출.

    • 최초 진입 시 JSP 페이지를 지시하는 HTTP 메소드(GET)과 값 입력 후 제출 시 다른 페이지로 값을 전달하면서 지시하는 HTTP 메소드(POST)로 구분하기.
      "redirect:url" 처리 기능 사용
      ▶ 동일한 jsp page를 다시 구현 하려면 List의 자료를 표현해야하는 등 많은 logic이 중복되서 구현해야하는 단점이 존재하므로, 해당 JSP Page가 return되는 Controller Method의 Mapping된 url을 호출하는 형식.
    @RequestMapping(value = "add-todo", method = RequestMethod.POST)
        public String addTodo(){
            return "redirect:list-todo";
    }
    • ListAllTodos() Controller Method에 Mapping된 URL을 호출하여 해당 Controller Method를 통해 listTodos.jsp를 호출.

      Spring에서의 redirect: 처리 방법
      。Spring Framework 사용 시 Controller Method에서 return type을 String으로 설정 후 return되는 문자열을 기존처럼 VIEW ( =jsp ) 파일명 이름이 아닌, "redirect:해당 JSP의 Controller Method로 Mapping된 url"로 문자열 반환 시 해당 url를 통해 Mapping된 Controller Method의 jsp를 return.



    • 사용자가 입력한 Description을 새로운 Todo Class의 instance를 생성하여 Static list에 추가하는 기능 구현하기.
      • 생성한 Todo instance를 Static list에 추가하는 addTodo() Method를 TodoService.java에 따로 구현하기.
        todosCount : primary key 역할.
        static 변수는 static block을 통해 초기화 수행.
          // TodoService.java
      import org.springframework.stereotype.Service;
      import java.time.LocalDate;
      import java.util.ArrayList;
      import java.util.List;
      @Service
      public class TodoService {
          private static int todosCount = 0;
          private static List<Todo> todos = new ArrayList<>();
          static {    // static List 초기값
              todos.add(new Todo(todosCount++,"wjdtn747","Learn AWS", LocalDate.now().plusYears(1),false));
              todos.add(new Todo(todosCount++,"wjdtn0619","Learn DevOps", LocalDate.now().plusYears(2),true));
              todos.add(new Todo(todosCount++,"wjdtn3902","Learn FullStackDevelopment", LocalDate.now().plusYears(3),false));
          }
          public List<Todo> findByUsername(String username) {
              return todos;
          }
          public static void addTodo( String username, String description, LocalDate targetDate, boolean completed){
              todos.add(new Todo(todosCount++,username,description,targetDate,completed));
          }
      }

      TodoController에서 TodoService class의 addTodo() method를 통해 todos에 추가하기위한 Todo Class의 instance를 생성하기위해 매개변수로 String username, String description, LocalDate targetDate, boolean completed 요구.

      • Controller Methoddescriptionusername 변수 가져오기
        descriptionaddTodo.jsp에서 POST로 전달된 값(name=description)을 @RequestParam String description으로 가져오기.

        ModelMap객체.get("JSP변수명")
        。로그인하여 JSP에 표현된 usernameModelSession을 통해 가져오기.
          // TodoController.java
        	@RequestMapping(value = "add-todo", method = RequestMethod.GET)
          public String addTodo(){
              return "addTodo";
          }
          @RequestMapping(value = "add-todo", method = RequestMethod.POST)
          public String addTodo(@RequestParam String description, ModelMap model){
              TodoService.addTodo((String)model.get("name"),description, LocalDate.now(),false);
              return "redirect:list-todo";
          }
        add-todo url은 GET, POST를 사용하므로 @RequestMappingmethod="" 속성으로 각각의 Controller Method를 구분하여 구현.

        (String)model.get("name")
        @SessionAttributes("name")을 통해 JSP로 전달된 변수( name )의 값을 Model을 통해 java로 가져온 objectString으로 casting하여 username로 활용.

        。이후 입력한 요소를 기반으로 Todo 객체를 static list에 추가하는 기능이 구현됨.

        Static List 특징 :

        • Static List는 서버가 재시작 될때마다 초기화됨.
          MySQL 등과 같은 DB를 활용.


      • JSP에 Validation을 구현하여 Description을 Todo list로 추가 시 입력값에 Null을 입력하지 못하게 하기
        。다음 구문을 JSP의 text를 담당하는 input에 required 속성 추가하여 검증기능을 구현.
      <label for="textId">Description : </label>
      <input type="text" id="textId" name="description" required="required">

      addTodo.jsp<input> 요소에 다음 required="required" 속성을 추가 시 null값을 제출하지 못하도록 설정됨.

Spring Boot Starter Validation을 이용한 검증 기능(Validation) 구현
。 프론트엔드(Client-side)에서 HTML이나 JS로 구현한 Authentication의 경우 쉽게 해킹이 가능.
▶ 최선의 방식은 Server-side Validation.

。Java 코드를 이용한 검증 구현이 가능하지만, Spring Boot를 활용하여 더욱 용이하게 Validation 구현이 가능.

  • Spring Validation
    。Client로부터 POST Request로 전송된 <form> 객체에 대한 유효성 검증을 수행.
    validation : 어떤 데이터( 주로 사용자 또는 다른서버의 request )의 값이 유효한지, 잘못된 내용이 있는지 확인하는 단계.

  • Form Backing Object :
    。Web Application에서 <form>을 통한 사용자의 입력을 담는 개체로서, <form>의 데이터를 검증 및 Controller에서 처리하는데 활용.
    <form>과 관련된 Validation logic을 포함 가능.
    Spring MVC에서 Client-side에서 HTML의 <form>과 Server-side에서 Model 객체( = Spring Bean )간 Data Binding을 수행
    ex ) JSP의 <form>의 요소에 상관 없이 Todo Class를 Model객체로서 Data Binding 하여 사용.

    Form Backing Object의 역할

    • <form>의 data 저장
      。사용자가 입력한 Form data를 저장.

    • Data Binding
      HTTP Request를 Java 객체로 변환하는 기능

    • Validation
      。사용자가 입력한 Data의 유효성 검증

    • Data Transfer
      。Controller에서 View로 Data를 전달하는 역할


  • Spring Boot를 활용한 검증기능 구현
    • Validation과 관련된 Spring Boot Starter의 dependency를 정의
      spring-boot-starter-validationpom.xml에 추가.
        <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-validation</artifactId>
         </dependency>
          .
    • Form Backing Object를 활용해서 <form><input>Controller Method를 통해 Spring Bean을 Binding.
      todo.jspTodoController.java 간의 양방향(2-way) Binding을 제공.
      <form>을 Spring Bean으로 Binding 할 경우, <form>의 요소(<input>)를 Model 객체( = Spring Bean) 의 field로 쉽게 mapping 가능.
      • <form><input>에 값이 입력될 경우, 이를 Mapping하는 Controller method와 Binding을 수행하기
        <input>이 10개 존재할 경우, Controller method에서 각각의 <input>요소의 name속성에 대해 매개변수로 @RequestParams를 각각 10개로 지정해야하므로 코드의 복잡성 증가.
        Form Backing Object 를 통해 Todo class ( = Spring Bean )에 직접 Binding하기.

        Form Backing Object 활용 시 Controller method의 첫번째 매개변수는 Model로 설정 및 두번째 매개변수는 직접 Binding될 Spring Bean을 Model 객체로서 설정.
                  // TodoController.java
      @RequestMapping(value = "add-todo", method = RequestMethod.POST)
      public String addTodo( ModelMap model, Todo todo ){
              TodoService.addTodo((String)model.get("name"),todo.getDescription(), LocalDate.now(),false);
              return "redirect:list-todo";
          }

      addTodo()의 첫번째 매개변수는 model , 두번째 매개변수에 Binding될 Spring Bean으로 설정.
      Spring MVC에 의해 <form>과 Controller Method의 매개변수 todo Spring Bean instance를 Model 객체로서 Data Binding.

      <form><input name="description">과 Data Binding된 tododescription field를 todo.getDescription()을 통해 가져온다.
      。해당 Todo class의 todo instance는 Spring Bean instance로서 Form Backing ObjectModel 객체 역할을 수행
      Form Backing Object으로서 Binding이 완료 된 경우 나중에 추가적으로 다른 변수(targetDate, done... etc)의 <input>를 추가하더라도 자동으로 Binding이 수행됨.

      • GETController Method 구현
        。 최초 add-todo로 url 연결 시 addTodo.jsp로 연결하는 GETController method에도 JSPspring form taglibmodelAttribute="Model객체명"속성을 통해 binding할 Model 객체( = todo )이 필요하므로, 초기화된 Model 객체를 JSP에 전달.
        。초기화된 Model 객체는 하드코딩으로 정의.
        @RequestMapping(value = "add-todo", method= RequestMethod.GET)
          public String ShowNewTodoPage(ModelMap model){
              // 하드코딩으로 초기화된 todo를 생성하여 model 객체로 사용.
              Todo todo = new Todo(0,(String)model.get("name"),"",LocalDate.now(),false);
              model.put("todo",todo);
              return "Todo";
          }


    • JSP에서 Form Backing Object 기능 활용하기.
      • form tag library를 사용하여 Data Binding을 수행.
        참조
        。JSP 문서 상단에 다음 taglib 추가하여 Spring form tag library를 import.
          <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

      。이를 추가할 경우, 접두사로 form을 사용하면서 <form:>이라는 접두사를 사용 가능.
      。JSP의 <form> 태그를 <form:form>로 수정 후 <form>Todo Class와 mapping하기 위해 modelAttribute="model객체이름" 속성을 추가하여 정의.

      <form:form method="post" modelAttribute="todo">

      ▶ 이때 반드시 modelAttribute의 값은 Mapping할 Controller method의 매개변수 Model 객체의 이름과 동일해야됨!

      。기존 <input><form:input>으로 수정 후 해당 tag를 Mapping된 Model 객체 tododescription field와 mapping하기 위해 path="Model객체의Field명" 속성을 선언.

        <form:input type="text" path="description" required="required"/>

      path=""속성은 Todo Class의 Mapping할 field명과 동일해야한다.

      。이후 다음처럼 작성한 후 Mapping URL localhost:8080/add-todo로 접속.

      <div class="container">
          <h1>Todo Details</h1>
          <form:form method="post" modelAttribute="todo">
              <label for="textId">Description : </label>
              <form:input type="text" path="description" id="textId" required="required"/>
              <form:input type="text" path="id"/>
              <form:input type="text" path="completed"/>
              <input type="submit" class="btn btn-secondary">
          </form:form>
      </div>

      <form:form> : modelAttribute="todo"를 통해 model객체 todo와 Data Binding.
      <form:input> : Todo Class에 구현된 field와 path="Model객체field명"으로 Data binding

      Form Binding Object에 의해 GETController MethodaddTodo()에서 생성된 초기 Model 객체 todo<form:form>에 mapping 되고, todo의 각 Field가 각 JSP의 <form:input>에 Mapping되어 표현됨.
      Model객체 todo의 각 Field의 초기값이 <form:input>에 표현되면서 <form:form>과 Model 객체(todo)가 자동으로 Binding되었음을 확인 가능.

      。Application 실행 후 <form:form>을 제출 시, Spring Validation을 통해 Model 객체 todo의 각 field가 null값을 인 것을 검출가능.
      <form:input type="text" path="description" id="textId" required="required"/>의 내용을 비운채로 제출이 불가능.

      • <form:input>을 "type=hidden"으로 설정하여 태그요소를 숨김처리가능.
      <form:form method="post" modelAttribute="todo">
              <label for="textId">Description : </label>
              <form:input type="text" path="description" id="textId" required="required"/>
              <form:input type="hidden" path="id"/>
              <form:input type="hidden" path="completed"/>
              <input type="submit" class="btn btn-secondary">
      </form:form>

      • 2-way Binding :
        Controller methodModel 객체 데이터로 JSP의 <form>에서 표시되는 단방향 Binding.
        <form><input>에 문자열을 입력 후 POST 요청 request를 통해 문자열이 todo instance의 해당되는 field로 단방향 Binding.

      • <%@ taglib %> JSP 태그 종류
        taglib : 사용자가 만든 tag library를 사용하기위해 사용하는 JSP 지시자
        ex) spring form taglib :
        <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

        uri="tag library 위치"
        prefix="tag를 지칭하는 이름"

      • Spring form taglib :
        Spring FrameworkSpring MVC Module에서 제공하는 library.
        modelAttibute 속성을 통해 JSP의 <form:form>Spring Bean( = Model 객체)을 Binding 수행 가능.
        ▶ Error message 처리가 간단해진다.

        。해당 taglib을 정의할 경우 <form:태그>이라는 접두사를 활용하여 사용 가능.

        <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
        form taglib을 정의할 JSP 문서 상단에 다음 구문을 정의.

        <form:errors>
        。JSP에서 Spring form taglib<form:form>의 하위 특정 <form:태그>요소에서 발생하는 Validation Error에 대하여 Validation Annotation에서 정의한 Error Message를 표현하는 태그요소.
        path="Model객체Field명"을 통해 Validation Error이 발생할 Binding된 Model 객체의 Field 이름을 지정.
        <form:errors path="description" cssClass="text-warning"/>

        <form:label> :
        spring form taglib의 form의 내부 요소(tag)의 label을 표기.
        path="model객체field명" : Form Backing Object에 의해 Data binding될 Model객체의 field명과 동일해야한다.
        <form:label path="description">Description : </form:label>

        • modelAttribute="model객체이름" :
          <form:form method="post" modelAttribute="todo">
          <form:form> tag의 속성.
          。JSP의 <form>Controller Method의 Model 객체와 Binding 시 사용.
          ▶ form data를 Java 객체로 변환.
          Spring MVC에서 ViewController 간 Data Binding을 수행.

          。Mapping할 Controller Method의 매개변수 Model 객체의 이름과 동일해야한다.
          <form> 제출 시 해당 model 객체와 mapping.

        • path="model객체의field명" :
          <form:input type="text" path="description" required="required"/>
          <form:form>의 내부 태그요소( ex. <form:errors> )를 Controller Method에 Binding된 Model객체의 특정 field( ex. description )와 Data Binding을 수행.


      • form taglib CSS 관련
        。기존 HTML의 tag에서 CSS 활용 시 <form class=""> 속성을 사용하지만, Spring taglib 태그의 경우 cssClass="" 속성 사용!
        Spring taglib에서 사용되는 cssClassBootstrap에서 사용되는 class를 사용가능.
        <form:errors path="description" cssClass="text-warning"/>


    • Spring Bean에 Validation을 추가하기
      。Spring Bean( = Todo Class )의 field에 Annotation을 선언하여 Validation을 정의.
      ▶ 이후 Form Backing Object에 의해 Mapping된 <form:input>의 태그요소에 Validation 기능이 적용됨.
      spring-boot-starter-validation의 dependency를 통해 설치되는 jakarta.validation.constraints.jar에서 최소 길이, 과거, 미래의 날짜 등에 대한 확인을 수행하는 Class가 존재.
      ▶ 구현할 수 있는 다량의 Validation이 해당 starter에 포함됨.

      Spring Bean의 Field 최소길이 10 이하인 검증기능 구현하기

      • <form:form>과 Mapping된 Model 객체의 원본 Spring Bean에서 검증을 수행할 Field앞에 @Size 선언
            @Size(min = 10, message="Enter at least 10 characters")
          private String description;
      • Controller Method의 매개변수에서 Binding되는 Model 객체 앞에 @Valid 선언.
      public String addTodoPage(ModelMap model, @Valid Todo todo)

      @Valid 선언 시 Form과 Bean간의 Binding이 이뤄지기 전 Bean에 대해 Validation을 먼저 수행하게됨.
      ▶ 10개 미만으로 문자열을 입력하여 제출 시 Validation error page 도출.

      Validation Annotation
      jakarta.validation.constraints package
      ID 혹은 PW에 작성 제한(글자수, 한글, 특수문자 등)을 둘때 사용하는 유효성검사 Annotation Class를 포함하는 pacakge.

      • @Past :
        。선언된 field의 Data Binding된 <input>값이 현재시점을 기준으로 과거시점이어야 한다.
        message="에러메세지" : 검증 실패 시 표현될 메시지.

      • @Size :
        。선언된 field의 Data Binding된 <input>으로 입력된 값의 크기가 min과 max 사이인 경우에만 값을 저장하도록 유효성 검증.
        JPA 혹은 Hibernate로부터 독립적인 Spring Bean 생성.
        。위반 시 Validation Error이 발생하면 이는 BindingResult의 객체에 의해 검출될 수 있음.

        @Size의 속성
        min="최솟값" : 입력값의 최솟값 설정
        max="최솟값" : 입력값의 최댓값 설정
        message="에러메세지" : 검증 실패 시 표현될 메시지.

        @Size(min=2, message = "Enter at least 2 characters")



      • @Valid :
        JSP Form 또는 RequestBody와 Model객체( = Spring Bean )간의 Binding이 되기 전 Spring Bean에 들어오는 값에 대하여 Validation을 먼저 수행 후 Binding.
        。주로 RequestBody를 검증 시 사용됨.


    • Validaition Error를 View에 표시.
      。검증 이후 발생한 Validation Error를 JSP에 표시.
      • Controller method의 매개변수에서 Validation Error를 검출 시 JSP로 return하는 기능 구현.
        BindingResult의 객체를 생성해서 Validation Error를 검출.
        @RequestMapping(value = "add-todo", method= RequestMethod.POST)
          public String addTodoPage(ModelMap model, @Valid Todo todo, BindingResult result){
              if(result.hasErrors()){
                  // Validation Error 등장 시.
                  // Todo.jsp를 계속 표현.
                  return "Todo";
              }
              TodoService.addTodo((String)model.get("name"),todo.getDescription(), LocalDate.now().plusYears(1),false);
              return "redirect:list-todo";
          }

      。문자열을 10자 이하로 적을 경우, @Size를 통한 Validation Error이 발생 및 BindingResult에서 검출하여 Todo.jsp를 계속 반환.

      • spring form taglib을 활용해서 error message도 같이 도출하게 하기.
        <form:errors>
        。JSP에서 Spring form taglib<form:form>의 하위 <form:>요소에서 발생하는 Validation Error의 Message를 표현.
        path="Model객체Field명"을 통해 Validation Error이 발생할 Binding된 Model 객체의 Field 이름을 지정.
      <form:errors path="description" cssClass="text-warning"/>


      ▶ Spring Bean Todo Class의 @Size가 선언된 description field에서 Validation Error 발생 시 Controller methodBindingResult에 의해 검출되어 addTodo.jsp를 return하면서 @Size에 정의된 Error Message를 path속성으로 Binding된 <form:errors path="description"> 태그요소로 표현.

      cssClass="" 속성으로 spring form taglib의 style을 지정가능.
      ▶ 일반 HTML 태그의 class="" 속성과 동일.

      • BindingResult : BindingResult br
        。 Spring MVC에서 <form> data를 Data Binding 하는 중 발생하는 Validation Error를 수집 및 처리하는 객체.
        ▶ Spring이 제공하는 Validation Error을 보관.
        • BindingResult객체.hasError() :
          。Spring에서 Validation Error 발생 시 true 반환하는 method


  • 기존 Static List에서 Todo instance를 삭제하는 기능 넣기.

    삭제 기능

    • 웹 페이지에서 각각의 Todo list 옆에 삭제 버튼을 생성
      listTodos.jsp 파일의 <table>에서 foreach를 이용해 <tr> 내부에서 열 역할을 수행하는 <td><a>를 이용해서 DELETE 버튼을 추가.
      。 각각의 DELETE 버튼의 href 링크에 @RequestMapping으로 Mapping하는 url( "delete-todo" )을 지정 및 해당 버튼의 todo의 id를 Query parameter( "?id=${todo.id}" )로 @RequestParam를 통해 Controller method에 전달.
    <td><a href="delete-todo?id=${todo.id}" class="btn btn-danger">DELETE</a></td>


    id=1의 delete button을 inspection하면 href를 통해 Query Parameter가 id=1인것을 확인 가능.

    • Todo instance를 Static List에서 삭제하기 위한 method를 TodoService.java로 따로 생성하기
        public static void deleteTodo(int id){
            Predicate<? super Todo> predicate = todo ->  todo.getId() == id ;
            todos.removeIf(predicate);
        }
    • predicate : Predicate를 통해 입력값( todo instance )를 받으면 todo.getId()의 getter method로 얻은 todo의 id와 매개변수로 전달된 id를 비교하여 true 또는 false 반환.
      List객체.removeIf(predicate객체)에 predicate를 전달하여 Static List 내부의 Todo instance 가 각각 Predicate에 전달되어 람다식에서 true를 반환 시 해당 Todo instance를 Static List에서 삭제.

    • @RequestParam를 통해 Query Parameter인 id를 받아 Todo instance를 Static List에서 삭제 후 list-todo로 redirect 해주는 Controller Method 생성하기
        @RequestMapping("delete-todo")
        public String DeleteTodo(@RequestParam int id){
            TodoService.deleteTodo(id);
            return "redirect:list-todo";
        }

    • predicate<T> :
      Predicate<Type> predicate = 람다식
      。입력 값을 받아서 boolean 값을 반환하는 함수형 Interface
      。전달식을 요구하는 특정 List객체의 Method에 predicate를 사용 시 값을 input 받아 람다식 구문을 수행 후 Boolean을 반환.
      ▶ 조건이 참이면 true, 거짓이면 false를 return.

      。주로 Stream.filter(Predicate객체)로 조건으로서 활용된다.

      Predicate<? super 클래스타입> :
      。해당 Class type뿐만 아닌 부모 Class Type까지 적용할 수 있는 predicate를 의미.

      활용례 )
    private static List<Todo> todos = new ArrayList<>();
        static {
            todos.add(new Todo(todosCount++,"wjdtn747","Learn AWS", LocalDate.now().plusYears(1),false));
            todos.add(new Todo(todosCount++,"wjdtn0619","Learn DevOps", LocalDate.now().plusYears(2),true));
            todos.add(new Todo(todosCount++,"wjdtn3902","Learn FullStackDevelopment", LocalDate.now().plusYears(3),false));
        }
    Predicate<? super Todo> predicate = todo -> todo.getId() == 3;
    todos.removeIf(predicate);

    List객체.removeIf(predicate객체)에 predicate를 전달하여 Static List 내부의 Todo instance 가 각각 Predicate에 전달되어 람다식에서 true를 반환 시 해당 Todo instance를 Static List에서 삭제.

    • list객체.removeif(조건) :
      。해당 List객체의 모든 각 요소에대해 predicate를 실시한 후 해당 조건에 대해 true이면 remove.
    • list객체.stream().filter(predicate).findFirst().get();


  • 기존 Static List에서 Todo instance를 UPDATE 하는 기능 넣기.
    • UPDATE button 구현하기
      <td><a href="update-todo?id=${todo.id}" class="btn btn-secondary">UPDATE</a></td>
      ▶ DELETE 버튼과 동일하게 query parameter를 통해 id를 전달.

    • UPDATE Controller Method 구현하기.
      • Static List에서 Update할 Todo instance를 찾아서 return 하기 위한 method를 TodoService.java에서 생성하기. Stream 관련
      public static Todo findById(int id){
              Predicate<? super Todo> predicate = todo -> todo.getId() == id;
              return todos.stream().filter(predicate).findFirst().get();
          }

      Predicate를 만족하는 stream.filter를 통해 생성된 Stream의 첫번째 todo instance를 추출

      • iddescription을 입력받아 Static List의 todo를 수정하는 method를 TodoService.java에서 생성하기.
      public static void updateById(int id, String description){
              Predicate<? super Todo> predicate = todo -> todo.getId() == id;
              todos.stream().filter(predicate).findFirst().get().setDescription(description);
          }

      Predicate를 만족하는 stream.filter를 통해 생성된 Stream의 첫번째 todo instance를 추출하여 setter를 통해 description을 수정.

      • 특정 Spring Bean을 불러와서 Update를 수행할 JSP page를 보여주기 위한 Controller Method
      @RequestMapping(value="update-todo",method=RequestMethod.GET)
          public String UpdateTodo(@RequestParam int id, ModelMap model){
              model.put("todo",TodoService.findById(id));
              return "addTodo";
          }

      。Update를 수행할 page를 보여주기 위한 method이므로, GET으로 설정.
      Todo.jsp<form:form method="post" modelAttribute="todo">을 통해 binding될 Todo instance를 요구하므로, 특정 Id에 해당하는 Todo instance를 찾아서 Model로 put하여 자동으로 Binding 되도록 설정.

      • 선택된 Id와 새로 입력된 description으로 URL를 전송해서 변경사항을 저장하기 위한 Controller method 생성하기
      @RequestMapping(value="update-todo",method=RequestMethod.POST)
          public String UpdateTodo(@RequestParam int id,@RequestParam String description,ModelMap model, @Valid Todo todo, BindingResult result){
              if (result.hasErrors()) {
                  return "addTodo";
              }else{
                  TodoService.updateById(id,description);
                  return "redirect:list-todo";
              }
          }

      addTodo.jsp에서 Id와 새로 입력된 description를 POST<form> data로서 해당 Controller Method로 전달되어 Static List의 기존 todo를 수정하는 method를 실행.
      @Valid를 통해 Spring Validation 검증을 수행.
      。이후 수정작업이 마친 경우 list-todo로 redirect.

      Java Stream Stream

      • 함수형 프로그래밍
      • 데이터의 흐름으로서 Array, Collection instance에 대해 함수 여러개를 조합해서 원하는 결과를 필터링해서 얻음.


  • JSP에 공통된 field의 tag에 대해 fieldset 생성하기.
    • <fieldset> :
      。공통된 path를 가진 관련된 모든 field의 tag를 한개의 FieldSet으로 그룹화하기.
      fieldset간 여백을 만드는법
      class를 이용해서 mb-1 , mb-2 , mb-3 , mb-4의 값을 선언.
      ▶ 설정 시 fieldset 하단에 여백이 생성됨.
    <!--Descripition FieldSet-->
                    <fieldset class="mb-3"> <!--class 속성의 mb-3은 fieldset간 여백 생성.-->
                        <form:label path="description">Description</form:label>
                        <form:input type="text" path="description" required="required"/>
                        <form:errors path="description" cssClass="text-warning"/>
                    </fieldset>


  • Todo를 Delete 또는 Update 할 경우 설정할 Target Date 정의하기.
    • Target Date의 <form:input>JSP에서 정의하기.
    <!--TargetDate FieldSet-->
                    <fieldset class="mb-3">
                        <form:label path="targetDate">Target Date</form:label>
                        <form:input type="text" path="targetDate" required="required"/>
                        <form:errors path="targetmDate" cssClass="text-warning"/>
                    </fieldset>
    • Controller method addTodo()에서 Target Date 입력 시 Static List에 반영되도록 설정
    @RequestMapping(value = "add-todo", method= RequestMethod.POST)
        public String addTodoPage(ModelMap model ,@Valid Todo todo, BindingResult result){
            if(result.hasErrors()){
                return "Todo";
            }
            TodoService.addTodo((String)model.get("name"),todo.getDescription(), todo.getTargetDate(),false);
            return "redirect:list-todo";
        }

    Form Backing Object 활용 시 Controller method의 첫번째 매개변수는 Model로 설정 및 두번째 매개변수는 직접 Binding될 Spring BeanModel 객체로서 설정.
    。Validation 수행 시 Controller Method의 매개변수에서 Binding되는 Model 객체 앞에 @Valid 선언.

    • Controller method updateTodo()에서 Target Date 입력 시 Static List에 반영되도록 설정.
      TodoService.java의 Update method
    public static void updateById(int id, String description, LocalDate targetDate){
            Predicate<? super Todo> predicate = todo -> todo.getId() == id;
            todos.stream().filter(predicate).findFirst().get().setDescription(description);
            todos.stream().filter(predicate).findFirst().get().setTargetDate(targetDate);
        }

    Controller method

    @RequestMapping(value="update-todo",method=RequestMethod.POST)
        public String UpdateTodo(@RequestParam int id,@RequestParam String description,@RequestParam LocalDate targetDate,ModelMap model, @Valid Todo todo, BindingResult result){
            if (result.hasErrors()) {
                return "addTodo";
            }else{
                TodoService.updateById(id,description,targetDate);
                return "redirect:list-todo";
            }
        }

    ▶ 이후 Update시 form의 data가 자동으로 Bean의 field에 Auto binding.

    • 입력 field에 특정 양식의 data(= LocalDate) 입력 시 표현되는 form의 양식을 통일하기.
      application.properties에서 다음 구문을 입력.
    spring.mvc.format.date=yyyy-MM-dd

    。spring에서 "m"은 minute을 의미하므로, Month를 표시하려면 "M"을 사용.


    ▶ 아래 사진처럼 변경!

    • 날짜선택팝업이 도출되어 Target Date를 설정할 수 있게끔하기.
      Bootstrap Datepicker :
      날짜 관련 팝업을 추가하는 Bootstrap framework.
      • Pom.xml에 Bootstrap Datepicker을 추가.
      <dependency>
                  <groupId>org.webjars</groupId>
                  <artifactId>bootstrap-datepicker</artifactId>
                  <version>1.9.0</version>
      </dependency>
      • 다음 경로의 다음 항목의 경로를 복사 후 jsp 파일에 정의하기.

        bootstrap-datepicker.standalone.min.css
        bootstrap-datepicker.min.js
      webjars/bootstrap-datepicker/1.9.0/css/bootstrap-datepicker.standalone.min.css
      webjars/bootstrap-datepicker/1.9.0/js/bootstrap-datepicker.min.js

      。webjars를 이용해 css 파일 사용 시 /META-INF/resources/ 를 지정할 필요가 없음.
      。html 문서에 css 활용 시 <head> 부분에 를 이용해 css 파일 연결.

      <link href="webjars/bootstrap-datepicker/1.9.0/css/bootstrap-datepicker.standalone.min.css" rel="stylesheet">

      。html 문서에서 javascript 활용 시 body의 종료태그 </body> 앞에 <script>를 통해 파일 연결.

      <script src="webjars/bootstrap-datepicker/1.9.0/js/bootstrap-datepicker.min.js"></script>

      .

      $('.datepicker').datepicker({
          format: 'mm/dd/yyyy',
          startDate: '-3d'
      });

      。작성된 datepicker를 jsp에 <script type="text/javascript">구문으로 추가하기.
      ▶ 해당 구문은 jquery <script>의 하단에 위치해야한다.

      <script src="webjars/jquery/3.6.0/jquery.min.js"></script>
      <script type="text/javascript">
                  $('#targetDate').datepicker({
                      format: 'yyyy-mm-dd'
                  });
      </script>

      。소괄호 안 $(#tagID명)는 jsp의 정의할 <form:input path="targetDate">의 id(= #targetDate)를 입력.

      $('#elementID') : JSP Page 내 특정 ID의 element를 객체로서 참조.
      # : id 앞에 선언.
      。startDate는 필요없으므로 제외 가능.
      。format은 위에서 정의했던 양식 "yyyy-mm-dd"으로 설정.
      ▶ spring과 다르게 datepicker framework"m"은 월을 의미하므로, 소문자로 작성.

      <!-- addTodo.jsp -->
      <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
      <!doctype html>
      <html lang="ko">
      <head>
          <link href="webjars/bootstrap/5.1.3/css/bootstrap.min.css" rel="stylesheet">
          <link href="webjars/bootstrap-datepicker/1.9.0/css/bootstrap-datepicker.standalone.min.css" rel="stylesheet">
          <title>Document</title>
      </head>
      <body>
      <div class="container">
          <h1>Todo Details</h1>
          <form:form method="post" modelAttribute="todo">
              <fieldset class="mb-3">
                  <form:label path="description">Description : </form:label>
                  <form:input type="text" path="description" required="required"/>
                  <form:errors path="description" cssClass="text-warning"/>
              </fieldset>
              <fieldset class="mb-3">
                  <form:label path="targetDate">Target Date</form:label>
                  <form:input type="text" id="targetDate" path="targetDate" required="required"/>
                  <form:errors path="targetDate" cssClass="text-warning"/>
              </fieldset>
              <input type="submit" class="btn btn-secondary">
          </form:form>
      </div>
      <script src="webjars/bootstrap/5.1.3/js/bootstrap.min.js"></script>
      <script src="webjars/jquery/3.6.0/jquery.min.js"></script>
      <script src="webjars/bootstrap-datepicker/1.9.0/js/bootstrap-datepicker.min.js"></script>
      <script type="text/javascript">
          $('#targetDate').datepicker({
              format : 'yyyy-mm-dd'
          });
      </script>
      </body>
      </html>
      

JSP Page에서 JSPF를 통해 Navigation Bar 생성.

<!-- navigation bar -->
<nav class="navbar navbar-expand-md navbar-light bg-light mb-3 p-1">
    <a class="navbar-brand m-1" href="https://velog.io/@wjdtn747/posts">wjdtn747</a>
    <div class="collapse navbar-collapse">
        <ul class="navbar-nav">
            <li class="nav-item"><a class="nav-link" href="/login">Home</a></li>
            <li class="nav-item"><a class="nav-link" href="/list-todo">Todos</a></li>
        </ul>
    </div>
    <ul class="navbar-nav">
        <li class="nav-item"><a class="nav-link" href="/logout">Logout</a></li>
    </ul>
</nav>

<a>로 정의된 각각의 버튼은 href를 통해 연결되는 url로 설정.
。Home, Todos는 <div>collapse되는 구획이며, wjdtn747과 Logout 버튼과 독립적인 list로 구분되어있음.
。해당 html코드를 nav-bar을 생성할 jsp page의 <body>태그에 생성.
▶ 다음 사진처럼 상단에 navigation bar가 생성됨!

이때 , 모든 JSP Page에 대해서 navigation bar를 정의할 경우, 중복된 코드를 계속 반복해서 활용( WET )하게된다.
JSPF 사용!

JSPF(Java Server Page Fragment) :
。JSP 코드의 일부분으로서 .jspf 확장자명으로 정의.

。중복되는 코드를 분리하여 JSPF 생성 후 JSP마다 코드를 붙여주는 역할을 수행
JSP의 코드의 중복 해소 ( WET 방지 )

。 각각의 JSP Page에서 중복정의되는 부분에 대해서 JSPF 정의.

  • JSP의 공통적으로 중복되는 부분의 코드를 JSPF로 정리하여 JSP에 적용하기.
    JSPheader, navigation, footer의 코드 공통부분에 대해 각각 JSPF를 생성하기.

    <%@ Include %> : include 지시자 JSP 태그 종류
    。별도의 페이지를 현재 페이지에 삽입.
    。특정 JSP의 코드내용을 다른 JSP 파일 내에서 원하는 공간에 포함시키는 기능.
    file="JSP상대경로" 속성을 지정하여 JSP 기준 JSPF의 상대경로를 설정.
    <%@ include file="JSP상대경로" %>
    • Navigation bar를 구현 할 JSP 파일에 위치한 디렉토리경로 기준 common 폴더 생성 후 navigation.jspf를 생성하여 다음 코드를 입력
    <!-- navigation.jspf -->
    <nav class="navbar navbar-expand-md navbar-light bg-light mb-3 p-1">
        <a class="navbar-brand m-1" href="https://velog.io/@wjdtn747/posts">wjdtn747</a>
        <div class="collapse navbar-collapse">
            <ul class="navbar-nav">
                <li class="nav-item"><a class="nav-link" href="/login">Home</a></li>
                <li class="nav-item"><a class="nav-link" href="/list-todo">Todos</a></li>
            </ul>
        </div>
        <ul class="navbar-nav">
            <li class="nav-item"><a class="nav-link" href="/logout">Logout</a></li>
        </ul>
    </nav>
    • JSP에서 <%@ include file="JSP상대경로" %>JSPF 파일을 포함시키기.
      file="" 속성으로 JSP파일 기준 상대경로JSPF 파일의 경로를 설정하기.
      。해당 구문은 JSP<body> 태그 상단에서 선언.
      <!-- JSPF 불러오기.-->
      <%@ include file="common/navigation.jspf" %>

    ▶ 모든 JSP Page에 대하여 동일한 JSPF를 사용 가능!

    • JSP<head> 부분에 대해서도 공통된 중복되는 부분을 묶어서 header.jspf를 생성하여 입력 후 jsp 파일에서 불러와서 사용
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
    <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
    <html lang="kor">
    <head>
        <link href="webjars/bootstrap/5.1.3/css/bootstrap.min.css" rel="stylesheet">
        <link href="webjars/bootstrap-datepicker/1.9.0/css/bootstrap-datepicker.standalone.min.css" rel="stylesheet">
        <!-- 웹페이지 태그 제목 -->
        <title>Manage Your Todo</title>
    </head>
      <%@ include file="common/header.jspf" %>

    JSPfooter 부분도 동일하게 footer.jspf 구현 후 적용하기!

        <!-- script 파일 설정 -->
    <script src="webjars/bootstrap/5.1.3/js/bootstrap.min.js"></script>
    <script src="webjars/jquery/3.6.0/jquery.min.js"></script>
    <script src="webjars/bootstrap-datepicker/1.9.0/js/bootstrap-datepicker.min.js"></script>
    <script type="text/javascript">
        $('#targetDate').datepicker({
            format: 'yyyy-mm-dd'
        });
    </script>
    </body>
    </html>
    <%@ include file="common/footer.jspf" %>
    • 사용 례
      shift + tab 할 경우 선택된 행의 tab이 한 칸 삭제됨.
    <%@ include file="common/header.jspf" %>
        <body>
        <%@ include file="common/navigation.jspf" %>
        <div class="container">
            <h1>Your name : ${name}</h1>
            <a href="list-todo">Manage</a> your todos
        </div>
    <%@ include file="common/footer.jspf" %>
profile
공부기록 블로그

0개의 댓글