1. 모델과 3티어

  • 웹 MVC 구조에서 모델은 Controller에 필요한 기능이나 데이터를 처리해주는 존재
  • 모델은 두 영역으로 세분화 가능
    • 서비스(로직 처리) 계층: 비즈니스 로직 처리
    • 영속(데이터 처리) 계층: 데이터베이스 담당

  • 웹 MVC 구조를 제외하고 전체 시스템 구조를 정리하면 다음과 같은 계층 3티어 구조가 됨

 

 1) DTO(Data Transfer Object)

  • 3티어로 계층을 분리하는 경우 반드시 계층이나 객체 간 데이터 교환이 이루어짐
  • 한 개 이상의 데이터를 전달할 때가 많으므로 여러 데이터를 묶어 하나의 객체로 전달하는 것을 DTO라고 함

  • DTO에는 특별한 규격이나 제약은 없지만, 대부분 Java Beans 형태로 구성
  • Java Beans
    • 생성자가 없거나 반드시 파라미터가 없는 생성자 함수를 가지는 형태
    • 속성(멤버 변수)은 private로 작성
    • getter/ setter를 제공할 것
  • Controller는 DTO를 구성해서 서비스 계층을 호출하기도 하고, 서비스 계층에서 DTO를 받기도 함
  • 따라서, 서비스 계층 구성 전 DTO를 위한 클래스 구성
package org.zerock.w1.todo.dto;

import java.time.LocalDate;

public class TodoDTO {

  // 멤버 변수
  // 실수, 번호
  private Long tno;
  // 문자열, 제목
  private String title;
  // 날짜, 기간
  private LocalDate dueDate;
  // 참/ 거짓, 완료여부
  private boolean finished;

  // getter / setter
  public Long getTno() {
    return tno;
  }
  public void setTno(Long tno) {
    this.tno = tno;
  }
  public String getTitle() {
     return title;
  }
  public void setTitle(String title) {
    this.title = title;
  }
  public LocalDate getDueDate() {
    return dueDate;
  }
  public void setDueDate(LocalDate dueDate) {
    this.dueDate = dueDate;
  }
  public boolean isFinished() {
    return finished;
  }
  public void setFinished(boolean finished) {
    this.finished = finished;
  }
  
  @Override
  public String toString() {
     return "TodoDTO{" +
            "tno=" + tno +
            ", title='" + title + "\'" +
            ", dueDate=" + dueDate +
            ", finished=" + finished +
            "}";
  }
}

 

 2) 서비스 객체

  • DTO는 여러 개의 데이터를 묶어서 하나의 객체를 구성하며 서비스 객체 메서드의 파라미터나 return 타입으로 사용
  • 서비스 객체는 기능(로직)들의 묶음, 프로그램이 구현해야 하는 기능들의 실제 처리를 담당
  • 예를 들어, CRUD(Create, Read, Update, Delete) 기능들은 모두 서비스 객체에 모아서 구현됨

 


실습

 1) TodoService 클래스

package org.zerock.w1.todo.service;

import org.zerock.w1.todo.dto.TodoDTO;

import java.time.LocalDate;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

// enum 타입의 클래스는 정해진 수만큼의 객체를 생성할 수 있는 장점이 있음
public enum TodoService {
  // INSTANCE는 enum에서의 객체 개수를 결정하는 부분, 한 개만 지정되어 있으므로 하나의 객체만 생성해서 사용
  // 객체 사용시에는 TodoService.INSTANCE를 입력하여 사용
  /// 예제에서는 여러 Controller들이 TodoService 객체를 통해 원하는 데이터를 주고받는 구조로 구성하기 때문에 TodoService 객체를 하나만 생성하는 '싱글톤 패턴(Singleton Pattern)' 사용
  INSTANCE;

  // 등록 메서드(아직 구현x)
  public void register(TodoDTO todoDTO) {
    System.out.println("DEBUG.........." + todoDTO);
  }

  // 목록 메서드(10개의 TodoDTO 객체를 만들어서 반환)
  public List<TodoDTO> getList() {
    List<TodoDTO> todoDTOS = IntStream.range(0, 10).mapToObj(i -> {
      TodoDTO dto = new TodoDTO();
      dto.setTno((long)i);
      dto.setTitle("Todo.." + i);
      dto.setDueDate(LocalDate.now());

      return dto;
    }).collect(Collectors.toList());

    return todoDTOS;
  }
}

 

2. Controller에서 모델 처리

  • 웹 MVC 구조에서 Controller는 화면에 필요한 데이터를 서비스 객체를 통해 처리
  • 예를 들어, 화면에 목록 데이터가 필요하다면 TodoServie의 getList()의 결과를 받아서 JSP에 전달하고, JSP에서 보여주는 방식
  • 이 과정에서 HttpServletRequest의 setAttribure() 메서드 사용(키(key)-값(value) 형식으로 HttpServletRequest에 데이터 보관, 보관된 데이터를 JSP에서 꺼내어 사용)

 


실습

 2) TodoListController의 처리

  - TodoListController의 TodoService에서 제공하는 List<TodoDTO>를 가져와서 JSP로 전달

package org.zerock.w1.todo;

import org.zerock.w1.todo.dto.TodoDTO;
import org.zerock.w1.todo.service.TodoService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

@WebServlet(name = "todoListController", urlPatterns = "/todo/list")
public class TodoListController extends HttpServlet{

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    System.out.println("/todo/list");

    List<TodoDTO> dtoList = TodoService.INSTANCE.getList();

    // setAttribute()를 이용햐서 "list"라는 이름으로 List<TodoDTO> 객체를 보관
    // list.jsp에서 EL(${}안에 작성한 Expression Language)로 확인 가능
    req.setAttribute("list", dtoList);

    req.getRequestDispatcher("/WEB-INF/todo/list.jsp").forward(req, resp);
  }
}
// list.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>List Page</h1>

${list}

</body>
</html>

 

3. JSP - EL(Expression Language)

  • JSP 코드에서 '${}'안에 작성한 코드는 EL 표현식
  • JSP는 주로 HTML, 자바스크립트, CSS 등으로 코드 작성하므로, 자바를 모르는 사람도 간단히 사용 가능한 출력용 언어로 EL 개발
  • EL를 통해 자바 코드를 몰라도 getter / setter 호출, 연산도 가능

 

 1) EL을 이용한 출력

  • EL을 이용하면 자동으로 getter 호출
  • 예시
<!-- 첫번째 TodoDTO의 tno와 title을 출력하고 싶다면 -->
${list[0].tno} --- ${list[0].title}

<!-- 출력 결과 -->
0 --- Todo..0
    • TodoDTO 클래스에서 tno, title등 변수는 private 처리되어 있어 자바 코드 외부에서 바로 접근 불가능
    • EL은  getTno(), getTitle()을 자동으로 호출

 

  • EL은 '표현식'이기 때문에, '${}' 내부에서 표현식이 결과를 만들어 낼 수 있다면 얼마든지 사용 가능
<h3>${ 1 + 2 + 3 }</h3>
<!-- 출력 결과 -->
6

<h3>${ "AAA" += "BBB" }</h3>
<!-- 출력 결과 -->
AAABBB

<h3>${ "AAA".equals("AAA") }</h3>
<!-- 출력 결과 -->
true

 

  • EL에서 자바 코드를 그대로 이용하는 것도 가능
  • 아래의 두 줄은 같은 결과를 출력
<h4>${list[0].title}</h4>
<h4>${list[0].getTitle()}</h4>

 

  • EL은 JSP에서 간단한 표현식을 출력만 하는 용도로, 반복문이나 제어문 같은 '식'이 아닌 '문'을 처리하려면 JSTL 라이브러리 필요

 

 

4. JSTL(JavaServer Pages Standard Tag Library)

  • JSP에서 동작하는 새로운 태그들의 묶음
  • 자바 문법보다 조금 더 간결하게 제어문이나 반복문, 선언문 등 처리 가능, 확장 가능
  • JSTL 이용을 위해 build.gradle 파일에 의존성 라이브러리 추가(추가하고 톰캣 중지하고 gradle 새로고침)
dependencies {
    compileOnly('javax.servlet:javax.servlet-api:4.0.1')

    testImplementation("org.junit.jupiter:junit-jupiter-api:${junitVersion}")
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitVersion}")
    
    // ---------이 부분 추가---------
    implementation group: 'jstl', name : 'jstl', version:' 1.2'
    // ---------이 부분 추가---------
}

 

 1) JSP 파일에서 JSTL 사용하기

  • JSP에서 JSTL을 사용하기 위해 다음 태그 관련 설정 추가
    <%@ taglib uri = "http://java.sun.com/jsp/jstl/core" prefix = "c" %>
  • <%@ %>로 시작하는 코드는 JSP에서 '지시자'의 역할, 위의 추가한 설정은 '태그 라이브러리 지시자'

 

  • <c:forEach>: 배열이나 리스트의 반복문 처리
    • var: EL에서 사용될 변수 이름
    • items: List, Set, Map, Enumeration, Iterator 등의 컬렉션
    • begin / end: 반복의 시작 / 끝 값
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri ="http://java.sun.com/jsp/jstl/core" prefix ="c" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>List Page</h1>

<ul>
  <c:forEach var = "dto" items = "${list}">
    <li>${dto}</li>
  </c:forEach>
</ul>

</body>
</html>

 

  • <c:if>: test라는 속성값에 true / false로 나올 수 있는 식이나 변수 등이 들어갈 수 있음
<c:if test = "${list.size() % 2 == 0}">
  짝수
</c:if>
<c:if test = "${list.size() % 2 != 0}">
  홀수
</c:if>
  • <c:choose>: 자바의 switch와 비슷, <c:when test=..>과 <c:otherwise>를 이용하여 if~else 처리 가능
<c:choose>
  <c:when test = "${list.size() % 2 == 0}">
    짝수
  </c when>
  <c:otherwise>
    홀수
  </c:otherwise>
</c:choose>
  • <c:set>: 새로운 변수 생성, var 속성으로 변수명 지정, value 속성으로 값 지정
<c:set var = "target" value = "5"></c:set>

<ul>
  <c:forEach var = "num" begin = "1" end = "10">
    <c:if test = "${num == target}">
      num is target
    </c:if>
  </c:forEach>
</ul>

 


실습

 3) Todo 조회

  - TodoService에 특정 번호의 조회 기능 추가

// TodoService

...

  // 특정 번호 조회 기능
  public TodoDTO get(Long tno) {
  
    TodoDTO dto = new TodoDTO();
    dto.setTno(tno);
    dto.setTitle("Sample Todo");
    dto.setDueDate(LocalDate.now());
    dto.setFinished(true);
    
    return dto;
  }
}

 

  - GET 방식으로 조회 기능 동작하도록 TodoReadController 작성

// TodoReadController
package org.zerock.w1.todo;

import org.zerock.w1.todo.dto.TodoDTO;
import org.zerock.w1.todo.service.TodoService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "todoReadController", urlPatterns = "/todo/read")
public class TodoReadController extends HttpServlet {
  
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    System.out.println("/todo/read");
    
    // 브라우저의 주소창에 전달되는 tno(게시물 번호)라는 이름의 파라미터를 처리하도록 작성
    // HttpServletRequest의 getParameter()는 항상 문자열로 결과가 나오므로 Long 타입으로 처리하기 위해 변환 필요
    Long tno = Long.parseLong(req.getParameter("tno"));
    
    // TodoService의 get()을 통해 나온 TodoDTO 객체는 dto라는 이름으로 JSP에 전달
    TodoDTO dto = TodoService.INSTANCE.get(tno);
    
    req.setAttribute("dto", dto);
    
    req.getRequestDispatcher("/WEB-INF/todo/read.jsp").forward(req, resp);
  }
}

 

  - TodoReadController의 결과를 받을 WEB-INF/todo/read.jsp 파일 작성

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <div>${dto.tno}</div>
    <div>${dto.title}</div>
    <div>${dto.dueDate}</div>
    <div>${dto.finished}</div>
</body>
</html>

 

  - 그 결과, 다음과 같이 주소창에서 /todo/read 경로에 쿼리 스트링을 통해 ?tno=번호를 파라미터로 전달하면 TodoDTO 객체의 내용 확인 가능

+ Recent posts