728x90

인프런 김영한 님의 강의를 참고했습니다.

 

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의

웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., -

www.inflearn.com

 

저번에 만들었던 서블릿들을 이제 스프링 부트에 맞게 바꾸어 보도록 하자.

스프링은 기존에 작성했던 서블릿과는 다르게 @annotation을 많이 활용한다.

 

우선 저번에 만들었던 Form을 이렇게 수정했다.

package hello.servlet.web.springmvc.version1;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class SpringStudentFormControllerV1 {

    @RequestMapping("/springmvc/v1/students/new-form")
    public ModelAndView process(){
        return new ModelAndView("new-form");
    }
}

@Controller를 달아서 스프링에서 컨트롤러로 인식하도록 한다, 그리고 메서드에 @RequestMapping으로 URL을 mapping 한다.

return은 저번에 작성했던 것과 비슷하게 ModelAndView 클래스에 생성자로 뷰 path를 넘겨주면 된다.

 

Save 부분도

package hello.servlet.web.springmvc.version1;

import hello.servlet.domain.student.Student;
import hello.servlet.domain.student.StudentRepository;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class SpringStudentSaveControllerV1 {

    private StudentRepository studentRepository = StudentRepository.getInstance();

    @RequestMapping("/springmvc/v1/students/save")
    public ModelAndView process(HttpServletRequest request, HttpServletResponse response){
        String studentName = request.getParameter("studentName");
        int year = Integer.parseInt(request.getParameter("year"));
        
        Student student = new Student(studentName, year);
        studentRepository.save(student);
        
        ModelAndView modelAndView = new ModelAndView("save-result");
        modelAndView.addObject("student", student);
        return modelAndView;
    }
}

이렇게 Mapping을 해주고 ModelAndView를 사용해서 Path 입력해주고 객체를 넣어주면 된다.

 

List도 같은 방식으로 작성한다.

package hello.servlet.web.springmvc.version1;

import hello.servlet.domain.student.Student;
import hello.servlet.domain.student.StudentRepository;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import java.util.List;

@Controller
public class SpringStudentListControllerV1 {

    private StudentRepository studentRepository = StudentRepository.getInstance();

    @RequestMapping("/springmvc/v1/students")
    public ModelAndView process(){
        List<Student> students = studentRepository.findAll();

        ModelAndView modelAndView = new ModelAndView("students");
        modelAndView.addObject("students", students);
        return modelAndView;
    }
}

 

이제 여기서 리펙토링을 해보도록 하자.

어차피 mapping은 메서드에 하나씩 들어가게 된다.

그러면 한 컨트롤러에 작성할 수도 있지 않을까?

 

물론 된다.

한 컨트롤러에 작성하고 다른 주소들을 Mapping 해주면 된다.

package hello.servlet.web.springmvc.version2;

import hello.servlet.domain.student.Student;
import hello.servlet.domain.student.StudentRepository;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import java.util.List;

@Controller
@RequestMapping("/springmvc/v2/students")
public class SpringStudentControllerV2 {

    private StudentRepository studentRepository = StudentRepository.getInstance();

    @RequestMapping("/new-form")
    public ModelAndView newForm(){
        return new ModelAndView("new-form");
    }

    @RequestMapping("/save")
    public ModelAndView save(HttpServletRequest request, HttpServletResponse response){
        String studentName = request.getParameter("studentName");
        int year = Integer.parseInt(request.getParameter("year"));
        
        Student student = new Student(studentName, year);
        studentRepository.save(student);
        
        ModelAndView modelAndView = new ModelAndView("save-result");
        modelAndView.addObject("student", student);
        return modelAndView;
    }
    
    @RequestMapping
    public ModelAndView students(){
        List<Student> students = studentRepository.findAll();
        
        ModelAndView modelAndView = new ModelAndView("students");
        modelAndView.addObject("students", students);
        return modelAndView;
    }
}

이렇게 메서드들의 이름만 분리해서 작성할 수 있다.

 

이제 마지막으로 @RequestParam까지 이용해보자.

이걸 이용하면 request에서 getParam을 쓸 필요가 없어진다.

바로 파라미터에서 가져올 수 있게 된다.

package hello.servlet.web.springmvc.version3;

import hello.servlet.domain.student.Student;
import hello.servlet.domain.student.StudentRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

@Controller
@RequestMapping("/springmvc/v3/students")
public class SpringStudentControllerV3 {

    private StudentRepository studentRepository = StudentRepository.getInstance();

    @GetMapping("/new-form")
    public String newForm(){
        return "new-form";
    }

    @PostMapping("/save")
    public String save(
            @RequestParam("studentName") String studentName,
            @RequestParam("year") int year,
            Model model){
        Student student = new Student(studentName, year);
        studentRepository.save(student);

        model.addAttribute("student", student);
        return "save-result";
    }

    @GetMapping
    public String students(Model model){
        List<Student> students = studentRepository.findAll();
        model.addAttribute("students", students);
        return "students";
    }
}

이렇게 ModelAndView를 리턴하는게 아니라 String을 리턴하면 자동으로 해당 Path를 찾아가게 된다.

Mapping에 Get, Post를 설정할 수 있으며 Model에 값을 추가하여 ModelAndView처럼 동작하게 할 수 있다.

'백엔드 > 스프링' 카테고리의 다른 글

스프링 16일차  (0) 2023.03.29
스프링 15일차  (0) 2023.03.28
스프링 14일차  (0) 2023.03.26
스프링 13일차  (0) 2023.03.25
스프링 12일차  (0) 2023.02.15
728x90

인프런 김영한 님의 강의를 참고했습니다.

 

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의

웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., -

www.inflearn.com

 

저번에 만들었던 서블릿에 컨트롤러를 추가해보자.

 

현재 만들어둔 컨트롤러는 공통된 부분이 많고 하는 일도 비슷하다.

그럴 때는 FrontController를 생성해준다.

이 FrontController가 요청을 받고 그 요청에 맞는 컨트롤러를 호출해주는 역할을 한다.

 

우선 프론트 컨트롤러 자체를 도입해보자.

우선 다형성을 이용해 컨트롤러 인터페이스를 만들어보자.

package hello.servlet.web.frontcontroller.version1;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

public interface ControllerV1 {
    void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}

이렇게 만들고 각 컨트롤러들이 이 인터페이스를 구현하게 해주면 요청에 따른 컨트롤러는 모두 준비가 된다.

 

이제 FrontController를 만들어준다.

package hello.servlet.web.frontcontroller.version1;

import hello.servlet.web.frontcontroller.version1.controller.StudentFormControllerV1;
import hello.servlet.web.frontcontroller.version1.controller.StudentListControllerV1;
import hello.servlet.web.frontcontroller.version1.controller.StudentSaveControllerV1;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@WebServlet(name = "frontControllerServletV1", urlPatterns = "/front-controller/v1/*")
public class FrontControllerServletV1 extends HttpServlet {
    private Map<String, ControllerV1> controllerMap = new HashMap<>();
    
    public FrontControllerServletV1(){
        controllerMap.put("/front-controller/v1/students/new-form", new StudentFormControllerV1());
        controllerMap.put("/front-controller/v1/students/save", new StudentSaveControllerV1());
        controllerMap.put("/front-controller/v1/students", new StudentListControllerV1());
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ControllerV1 controller = controllerMap.get(req.getRequestURI());
        if(controller == null){
            resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        
        controller.process(req, resp);
    }
}

이렇게 Map에 컨트롤러들을 담아준 후 URI를 확인하여 거기에 맞는 컨트롤러로 보내준다.

 

이제 계속 리팩토링을 진행해보자.

모든 컨트롤러에서 중복되는 부분이 있다.

이 부분을 모아주어야 깔끔하고, 후에 수정하기도 쉽다.

이런 상태에서 중간에 MyView를 추가하여 dispatcher.forward()를 맡길 예정이다.

package hello.servlet.web.frontcontroller;

import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

public class MyView {

    private String viewPath;

    public MyView(String viewPath){
        this.viewPath = viewPath;
    }

    public void render(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        RequestDispatcher requestDispatcher = request.getRequestDispatcher(viewPath);
        requestDispatcher.forward(request, response);
    }
}

이렇게 생성자로 viewPath를 입력받아서 render를 이용하여 jsp로 넘겨준다.

 

나머지 코드들은 저 부분을 빼서 다시 작성한다고 생각하면 된다.

package hello.servlet.web.frontcontroller.version2;

import hello.servlet.web.frontcontroller.MyView;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

public interface ControllerV2 {

    MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
package hello.servlet.web.frontcontroller.version2.controller;

import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.version2.ControllerV2;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

public class StudentFormControllerV2 implements ControllerV2 {

    @Override
    public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        return new MyView("/WEB-INF/views/new-form.jsp");
    }
}
package hello.servlet.web.frontcontroller.version2.controller;

import hello.servlet.domain.student.Student;
import hello.servlet.domain.student.StudentRepository;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.version2.ControllerV2;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

public class StudentSaveControllerV2 implements ControllerV2 {

    private StudentRepository studentRepository = StudentRepository.getInstance();

    @Override
    public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String studentName = request.getParameter("studentName");
        int year = Integer.parseInt(request.getParameter("year"));

        Student student = new Student(studentName, year);
        studentRepository.save(student);

        request.setAttribute("student", student);

        return new MyView("/WEB-INF/views/save-result.jsp");
    }
}
package hello.servlet.web.frontcontroller.version2.controller;

import hello.servlet.domain.student.Student;
import hello.servlet.domain.student.StudentRepository;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.version2.ControllerV2;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.List;

public class StudentListControllerV2 implements ControllerV2 {

    private StudentRepository studentRepository = StudentRepository.getInstance();

    @Override
    public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        List<Student> students = studentRepository.findAll();
        request.setAttribute("students", students);

        return new MyView("/WEB-INF/views/students.jsp");
    }
}

그러고 FrontController는 이렇게 작성을 해준다.

package hello.servlet.web.frontcontroller.version2;

import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.version2.controller.StudentFormControllerV2;
import hello.servlet.web.frontcontroller.version2.controller.StudentListControllerV2;
import hello.servlet.web.frontcontroller.version2.controller.StudentSaveControllerV2;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@WebServlet(name = "frontControllerServletV2", urlPatterns = "/front-controller/v2/*")
public class FrontControllerServletV2 extends HttpServlet {

    private Map<String, ControllerV2> controllerMap = new HashMap<>();

    public FrontControllerServletV2(){
        controllerMap.put("/front-controller/v1/students/new-form", new StudentFormControllerV2());
        controllerMap.put("/front-controller/v1/students/save", new StudentSaveControllerV2());
        controllerMap.put("/front-controller/v1/students", new StudentListControllerV2());
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ControllerV2 controller = controllerMap.get(req.getRequestURI());
        if(controller == null){
            resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        MyView view = controller.process(req, resp);
        view.render(req, resp);
    }
}

request와 response에 데이터를 받고 view로 경로를 받은 후에 실행해주는 것이다.

 

다음 리펙토링이다.

컨트롤러에서는 request와 response가 꼭 필요하지 않다.

어차피 데이터를 넘길거면 담아서 넘기는 게 아니라 그냥 넘기면 되기 때문이다.

그렇기 때문에 컨트롤러에서 서블릿 기술을 제외해보자.

 

스프링처럼 Model을 만들어 데이터를 넘기도록 한다.

이렇게 Controller에서 Model에 데이터만 담아서 가져오는 것이다.

package hello.servlet.web.frontcontroller;

import lombok.Getter;
import lombok.Setter;

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

@Getter
@Setter
public class ModelView {
    
    private String viewName;
    private Map<String, Object> model = new HashMap<>();

    public ModelView(String viewName, Map<String, Object> model) {
        this.viewName = viewName;
    }
}

이렇게 Map으로 key, value로 데이터들을 저장하도록 만든다.

그러고 경로를 저장하기 위해서 viewName을 사용한다.

 

이제 ModelView를 리턴하도록 코드들을 바꾸어보자.

package hello.servlet.web.frontcontroller.version3;

import hello.servlet.web.frontcontroller.ModelView;

import java.util.Map;

public class StudentFormControllerV3 implements ControllerV3{

    @Override
    public ModelView process(Map<String, String> paramMap) {
        return new ModelView("new-form");
    }
}

이렇게 Model에 jsp의 경로와 데이터들을 넣어준다.

package hello.servlet.web.frontcontroller.version3;

import hello.servlet.domain.student.Student;
import hello.servlet.domain.student.StudentRepository;
import hello.servlet.web.frontcontroller.ModelView;

import java.util.Map;

public class StudentSaveControllerV3 implements ControllerV3{
    
    private StudentRepository studentRepository = StudentRepository.getInstance();

    @Override
    public ModelView process(Map<String, String> paramMap) {
        String studentName = paramMap.get("studentName");
        int year = Integer.parseInt(paramMap.get("year"));
        
        Student student = new Student(studentName, year);
        studentRepository.save(student);
        
        ModelView modelView = new ModelView("save-result");
        modelView.getModel().put("student", student);
        return modelView;
    }
}
package hello.servlet.web.frontcontroller.version3;

import hello.servlet.domain.student.Student;
import hello.servlet.domain.student.StudentRepository;
import hello.servlet.web.frontcontroller.ModelView;

import java.util.List;
import java.util.Map;

public class StudentListControllerV3 implements ControllerV3{

    private StudentRepository studentRepository = StudentRepository.getInstance();

    @Override
    public ModelView process(Map<String, String> paramMap) {
        List<Student> students = studentRepository.findAll();

        ModelView modelView = new ModelView("students");
        modelView.getModel().put("students", students);
        
        return modelView;
    }
}

 

이제 이렇게 작동할 FrontController를 작성한다.

package hello.servlet.web.frontcontroller.version2;

import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.version2.controller.StudentFormControllerV2;
import hello.servlet.web.frontcontroller.version2.controller.StudentListControllerV2;
import hello.servlet.web.frontcontroller.version2.controller.StudentSaveControllerV2;
import hello.servlet.web.frontcontroller.version3.ControllerV3;
import hello.servlet.web.frontcontroller.version3.StudentFormControllerV3;
import hello.servlet.web.frontcontroller.version3.StudentListControllerV3;
import hello.servlet.web.frontcontroller.version3.StudentSaveControllerV3;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@WebServlet(name = "frontControllerServletV3", urlPatterns = "/front-controller/v3/*")
public class FrontControllerServletV3 extends HttpServlet {

    private Map<String, ControllerV3> controllerMap = new HashMap<>();

    public FrontControllerServletV3(){
        controllerMap.put("/front-controller/v3/students/new-form", new StudentFormControllerV3());
        controllerMap.put("/front-controller/v3/students/save", new StudentSaveControllerV3());
        controllerMap.put("/front-controller/v3/students", new StudentListControllerV3());
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ControllerV3 controller = controllerMap.get(req.getRequestURI());
        if(controller == null){
            resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        Map<String, String> paramMap = createParamMap(req);
        ModelView modelView = controller.process(paramMap);

        String viewName = modelView.getViewName();
        MyView view = viewer(viewName);
        view.render(modelView.getModel(), req, resp);
    }

    private MyView viewer(String viewName) {
        return new MyView("/WEB-INF/views/" + viewName + ".jsp");
    }

    private Map<String, String> createParamMap(HttpServletRequest req) {
        Map<String, String> paramMap = new HashMap<>();
        req.getParameterNames().asIterator()
                .forEachRemaining(paramName -> paramMap.put(paramName, req.getParameter(paramName)));

        return paramMap;
    }
}

여기에 MyView에 파라미터가 3개 들어가는 생성자가 호출 되었으니, 그에 맞게 생성자를 추가해준다.

 

 

'백엔드 > 스프링' 카테고리의 다른 글

스프링 17일차  (0) 2023.03.31
스프링 15일차  (0) 2023.03.28
스프링 14일차  (0) 2023.03.26
스프링 13일차  (0) 2023.03.25
스프링 12일차  (0) 2023.02.15
728x90

인프런 김영한 님의 강의를 참고했습니다.

 

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의

웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., -

www.inflearn.com

 

간단한 학생 관리 웹을 만들어보자.

학생의 정보에는 학번, 이름, 입학년도가 있다.

 

우선 학생의 클래스를 작성해보자.

전에도 사용했던 Lombok을 사용한다.

package hello.servlet.domain.student;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class Student {
    private Long studentId;
    private String name;
    private String year;
}

학생들의 정보를 저장할 저장소이다.

 

package hello.servlet.domain.student;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class StudentRepository {
    private static Map<Long, Student> store = new HashMap<>();
    private static long STUDENT_ID_SEQUENCE = 0L;

    private static final StudentRepository instance = new StudentRepository();

    public static StudentRepository getInstance(){
        return instance;
    }

    private StudentRepository() {}

    public Student save(Student student){
        student.setStudentId(++STUDENT_ID_SEQUENCE);
        store.put(student.getStudentId(), student);
        return student;
    }

    public Student findById(Long id){
        return store.get(id);
    }

    public List<Student> findAll(){
        return new ArrayList<>(store.values());
    }
    
    public void clearStore(){
        store.clear();
    }
}

저장소는 싱글톤으로 생성했고, 저장과 검색 메서드들을 추가했다.

 

command + shift + T로 테스트 코드를 작성해보자.

package hello.servlet.domain.student;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

class StudentRepositoryTest {

    StudentRepository studentRepository = StudentRepository.getInstance();

    @AfterEach
    void afterEach(){
        studentRepository.clearStore();
    }

    @Test
    void save(){
        //given
        Student student = new Student("hyunttai", 2022);

        //when
        Student savedStudent = studentRepository.save(student);

        //then
        Student findStudent = studentRepository.findById(savedStudent.getStudentId());
        Assertions.assertEquals(savedStudent, findStudent);
    }

    @Test
    void findAll(){
        //given
        Student student1 = new Student("hyeontae", 2022);
        Student student2 = new Student("hyeonttai", 2022);

        //when
        studentRepository.save(student1);
        studentRepository.save(student2);
        List<Student> result = studentRepository.findAll();

        //then
        Assertions.assertEquals(result.size(), 2);
        org.assertj.core.api.Assertions.assertThat(result).contains(student1, student2);
    }
}

@AfterEach로 각 테스트마다 저장소를 초기화 해주었다.

문제없이 작성했다면 테스트를 통과 했을 것이다.

 

이제 servlet으로 HTML form을 응답하도록 코드를 작성해보자.

package hello.servlet.web.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(name = "studentFormServlet", urlPatterns = "/servlet/students/new-form")
public class StudentFormServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");
        resp.setCharacterEncoding("utf-8");

        PrintWriter writer = resp.getWriter();
        writer.write("<!DOCTYPE html>\n" +
                "<html>\n" +
                "<head>\n" +
                "    <meta charset=\"UTF-8\">\n" +
                "    <title>Title</title>\n" +
                "</head>\n" +
                "<body>\n" +
                "<form action=\"/servlet/students/save\" method=\"post\">\n" +
                "    studentName: <input type=\"text\" name=\"studentName\" />\n" +
                "           year:      <input type=\"text\" name=\"year\" />\n" +
                " <button type=\"submit\">전송</button>\n" + "</form>\n" +
                "</body>\n" +
                "</html>\n");
    }
}

response 객체에 ContentType을 html로해서 html로 응답한 모습이다.

 

action에 save를 달아주었기 때문에 이 요청을 받을 save도 작성을 해야한다.

package hello.servlet.web.servlet;

import hello.servlet.domain.student.Student;
import hello.servlet.domain.student.StudentRepository;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(name = "studentSaveServlet", urlPatterns = "/servlet/students/save")
public class StudentSaveServlet extends HttpServlet {

    private StudentRepository studentRepository = StudentRepository.getInstance();

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String studentName = req.getParameter("studentName");
        int year = Integer.parseInt(req.getParameter("year"));

        Student student = new Student(studentName, year);
        studentRepository.save(student);

        PrintWriter writer = resp.getWriter();

        writer.write("<html>\n" +
                "<head>\n" +
                " <meta charset=\"UTF-8\">\n" + "</head>\n" +
                "<body>\n" +
                "<ul>\n" +
                "    <li>studentId="+student.getStudentId()+"</li>\n" +
                "    <li>name="+student.getStudentName()+"</li>\n" +
                " <li>year="+student.getYear()+"</li>\n" + "</ul>\n" +
                "<a href=\"/index.html\">메인</a>\n" + "</body>\n" +
                "</html>");
    }
}

그러고 http://localhost:8080/servlet/students/new-form 해당 페이지에 접근하면 잘 작동하는 것을 볼 수 있다.

 

이번엔 하나씩 조회하는 것이 아니라 모든 학생들을 조회하는 기능을 만들어보자.

package hello.servlet.web.servlet;

import hello.servlet.domain.student.Student;
import hello.servlet.domain.student.StudentRepository;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

@WebServlet(name = "studentListServlet", urlPatterns = "/servlet/students")
public class StudentListServlet extends HttpServlet {

    private StudentRepository studentRepository = StudentRepository.getInstance();

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");
        resp.setCharacterEncoding("utf-8");

        List<Student> students = studentRepository.findAll();

        PrintWriter writer = resp.getWriter();

        writer.write("<html>");
        writer.write("<head>");
        writer.write("    <meta charset=\"UTF-8\">");
        writer.write("    <title>Title</title>");
        writer.write("</head>");
        writer.write("<body>");
        writer.write("<a href=\"/index.html\">메인</a>");
        writer.write("<table>");
        writer.write("    <thead>");
        writer.write("    <th>studentId</th>");
        writer.write("    <th>studentName</th>");
        writer.write("    <th>year</th>");
        writer.write("    </thead>");
        writer.write("    <tbody>");
        for (Student student : students) {
            writer.write("    <tr>");
            writer.write("        <td>" + student.getStudentId() + "</td>");
            writer.write("        <td>" + student.getStudentName() + "</td>");
            writer.write("        <td>" + student.getYear() + "</td>");
            writer.write("    </tr>");
        }
        writer.write("    </tbody>");
        writer.write("</table>");
        writer.write("</body>");
        writer.write("</html>");
    }
}

이렇게 for-each문을 이용해서 반복되는 html들을 찍어주었다.

이렇게 잘 출력되는 것을 볼 수 있다.

하지만 이렇게 자바코드 안에 HTML을 넣어서 작업하는 방법은 너무 힘들다.

 

그렇기에 HTML에 자바코드를 작성하는 JSP로 페이지를 만들어보자.

신입생을 등록하는 jsp는 html과 크게 다르지 않다.

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>신입생 등록</title>
</head>
<body>
<form action = "/jsp/students/save.jsp" method="post">
    studentName: <input type = "text" name="studentName">
    year: <input type="text" name="age">
    <button type="submit">전송</button>
</form>

</body>
</html>

 

여기서 응답을 하는 save.jsp는

<%@ page import="hello.servlet.domain.student.StudentRepository" %>
<%@ page import="hello.servlet.domain.student.Student" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
  StudentRepository studentRepository = StudentRepository.getInstance();

  String studentName = request.getParameter("studentName");
  int year = Integer.parseInt(request.getParameter("year"));

  Student student = new Student(studentName, year);
  studentRepository.save(student);

%>
<html>
<head>
    <title>신입생 등록 완료</title>
</head>
<body>
<ul>
  <li>studentId=<%=student.getStudentId()%></li>
  <li>studentName=<%=student.getStudentName()%></li>
  <li>year=<%=student.getYear()%></li>
</ul>

<a href="/index.html">메인</a>

</body>
</html>

이렇게 자바코드와 html이 합쳐져 있다.

윗 부분에 자바 코드들을 작성하고 html의 body에서 데이터들을 가져오는 것이다.

이렇게 보면 기존과 크게 차이가 없어 보이지만, html에 자바코드를 사용해서 반복문으로 값을 출력하면 훨씬 작업하기 편해진다.

 

학생들의 목록을 출력해주는 JSP이다.

<%@ page import="hello.servlet.domain.student.StudentRepository" %>
<%@ page import="hello.servlet.domain.student.Student" %>
<%@ page import="java.util.List" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    StudentRepository studentRepository = StudentRepository.getInstance();
    List<Student> students = studentRepository.findAll();
%>
<html>
<head>
    <title>학생 조회</title>
    <meta charset="UTF-8">
</head>
<body>
<a href="/index.html">메인</a>
<table>
    <thead>
    <th>studentId</th>
    <th>studentName</th>
    <th>year</th>
    </thead>
    <tbody>
    <%
        for(Student student: students){
            out.write("<tr>\n");
            out.write("<td>" + student.getStudentId() + "</td>\n");
            out.write("<td>" + student.getStudentName() + "</td>\n");
            out.write("<td>" + student.getYear() + "</td>\n");
            out.write("</tr>\n");
        }
    %>
    </tbody>
</table>

</body>
</html>

이렇게 html 안에서 자바코드를 사용하여 html 코드들을 반복해서 출력하는 것으로 코드가 훨씬 보기 좋고 편해졌다.

하지만 현재의 JSP는 너무 많은 역할을 담당하고 있다.

그렇기 때문에 Model, View, Controll의 MVC 패턴에 맞추어 작성하도록 해야한다.

Model은 컨트롤러에서 뷰로 넘기는 데이터로 뷰가 출력하는 데에 필요한 데이터들을 가지고 있다.

View는 Model에 있는 데이터를 사용해서 화면을 보여주는 역할을 한다.

Controller는 HTTP 요청을 받아 파라미터를 보고, 로직을 실행한다. 그러고 Model에 데이터를 담아 View에 넘긴다.

 

여기서 Controller에 로직들을 둘 수 있지만, 역할을 나누기위해 서비스를 만들어 그 서비스를 호출하여 실행하는 역할을 한다.

요청부터 응답까지의 과정을 살펴보면

이렇게 MVC를 이용하여 요청에 응답을 한다.

그러면 여기서 로직을 수행하는 부분은 서블릿으로, 뷰를 보여주는 부분은 JSP로 작성을 한다.

 

package hello.servlet.web.servletmvc;

import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet(name = "mvcStudentFormServlet", urlPatterns = "servlet-mvc/students/new-form")
public class MvcStudentFormServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String viewPath="/WEB-INF/views/new-form.jsp";
        RequestDispatcher dispatcher = req.getRequestDispatcher(viewPath);
        dispatcher.forward(req, resp);
    }
}

 

이렇게 작성을 하면 urlPattern으로 mapping 한 주소에 접속할 때 dispatcher.forward를 통해 viewPath에 있는 JSP를 열어주게 된다.

여기서 /WEB-INF를 만들어야 하는데, 이 경로 안에 있는 JSP는 외부에서 호출할 수 없고 서버 내부에서만 호출이 가능하다.

 

이제 새로 열릴 new-form.jsp를 만들어준다.

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>신입생 등록</title>
    <meta charset="UTF-8">
</head>
<body>
<form action="save" method="post">
    studentName: <input type="text" name="studentName"/>
    year: <input type="text" name="year"/>
    <button type="submit">전송</button>
</form>
</body>
</html>

action을 save로 잡아주었으니 이제 save를 작성한다.

전송을 하면 아래의 서블릿에서 로직이 실행이 된다.

 

request를 보고 파라미터들을 이용하여 값을 가져온 후 다시 request.setAttribute()를 이용하여 데이터를 넣어준 후 jsp로 넘겨준다.

package hello.servlet.web.servletmvc;

import hello.servlet.domain.student.Student;
import hello.servlet.domain.student.StudentRepository;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet(name = "mvcStudentSaveServlet", urlPatterns = "/servlet-mvc/students/save")
public class MvcStudentSaveServlet extends HttpServlet {
    private StudentRepository studentRepository = StudentRepository.getInstance();

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String studentName = req.getParameter("studentName");
        int year = Integer.parseInt(req.getParameter("year"));
        
        Student student = new Student(studentName, year);
        studentRepository.save(student);
        
        req.setAttribute("student", student);
        
        String viewPath = "/WEB-INF/views/save-result.jsp";
        RequestDispatcher dispatcher = req.getRequestDispatcher(viewPath);
        dispatcher.forward(req, resp);
    }
}

 

이제 결과를 출력할 jsp를 작성해준다.

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>신입생 등록 완료</title>
    <meta charset="UTF-8">
</head>
<body>

<ul>
  <li>studentId=${student.studentId}</li>
  <li>studentName=${student.studentName}</li>
  <li>year=${student.year}</li>
</ul>

<a href="/index.html">메인</a>

</body>
</html>

${}를 이용하면 attribute에 있는 데이터를 조회할 수 있다.

 

 

'백엔드 > 스프링' 카테고리의 다른 글

스프링 17일차  (0) 2023.03.31
스프링 16일차  (0) 2023.03.29
스프링 14일차  (0) 2023.03.26
스프링 13일차  (0) 2023.03.25
스프링 12일차  (0) 2023.02.15
728x90

인프런 김영한 님의 강의를 참고했습니다.

 

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의

웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., -

www.inflearn.com

 

스프링 부트를 설정해서 다운로드하여 오고, 추가 설정들을 해준다.

저번에 워낙 많이 해서 그냥 넘어가도록 하겠다.

 

이번에는 서블릿을 사용하기 때문에 스프링 메인에 @ServletComponentScan annotation을 달아준다.

그러고 같은 패키지 내에 이런 자바 코드를 작성해 보자.

package hello.servlet.basic;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(name = "hiServlet", urlPatterns = "/hi")
public class HiServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        resp.setContentType("text/plain");
        resp.setCharacterEncoding("utf-8");


        PrintWriter writer = resp.getWriter();
        writer.println("HiServlet");
        writer.println("request = " + req);
        writer.println("response = " + resp);

        writer.println("name = " + req.getParameter("name"));

    }
}

실행을 하고 url에 접속한 결과이다.

 

@WebServlet annotation은 서블릿 어노테이션으로 name에 들어가는 값은 서블릿의 이름이고, urlPatterns에 들어가는 값은 mapping 되는 url이다.

저번에 서블릿 공부 할 때 공부했을 것이다.

그리고 서블릿을 사용하기 위해서는 HttpServlet을 extends 받아야 한다.

 

메서드 내의 코드를 잠깐 설명해 보자면, 응답의 ContentType을 text/plain으로 설정하고 CharacterEncoding을 utf-8로 설정을 한 후 response에 getWriter로 request와 response 객체를 출력해 보는 것이다.

 

  • HTTPServletRequest 객체

객체의 이름을 보면 알겠지만, HTTP의 Request를 Servlet이 다루기 편하게 만들어주는 객체이다.

이곳을 보면 HTTP 요청 메시지에 대한 정보를 얻어 올 수 있다.

당연히 직접 HTTP 요청 메시지를 사용할 수 있겠지만, 난이도가 매우 어렵다.

 

HTTPServletRequest 객체를 이용해서 안의 내용들을 출력해 보자.

우선 시작 줄부터이다.

주석으로 설명을 달지 않아도 시작줄의 어떤 내용을 출력하려 하는 것인지 알 수 있을 것이다.

package hello.servlet.basic.request;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(name = "requestHeaderServlet", urlPatterns = "/requestHeader")
public class RequestHeader extends HttpServlet {

    private void printStart(HttpServletResponse httpServletResponse, String string) throws IOException {
        httpServletResponse.getWriter().printf("--- %s-Line start ---\n", string);
    }

    private void printEnd(HttpServletResponse httpServletResponse, String string) throws IOException {
        httpServletResponse.getWriter().printf("--- %s-Line end ---\n", string);
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        StartLine(req, resp);
    }

    private void StartLine(HttpServletRequest request,HttpServletResponse response)throws IOException{

        response.setContentType("text/plain");
        response.setCharacterEncoding("utf-8");

        PrintWriter writer = response.getWriter();

        printStart(response, "Request");

        writer.println("Method = " + request.getMethod());
        writer.println("Protocol = " + request.getProtocol());
        writer.println("Scheme = " + request.getScheme());
        writer.println("RequestURL = " + request.getRequestURL());
        writer.println("RequestURI = " + request.getRequestURI());
        writer.println("QueryString = " + request.getQueryString());
        writer.println("isSecure = " + request.isSecure());
        printEnd(response, "Request");

        writer.println();
    }
}

실행을 한 후에 접속해 보면

이렇게 요청 메서드에 관한 정보들이 나오게 된다.

 

이번엔 헤더이다.

package hello.servlet.basic.request;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(name = "requestHeaderServlet", urlPatterns = "/requestHeader")
public class RequestHeader extends HttpServlet {

    private void printStart(HttpServletResponse httpServletResponse, String string) throws IOException {
        httpServletResponse.getWriter().printf("--- %s-Line start ---\n", string);
    }

    private void printEnd(HttpServletResponse httpServletResponse, String string) throws IOException {
        httpServletResponse.getWriter().printf("--- %s-Line end ---\n", string);
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        StartLine(req, resp);
        Headers(req, resp);
    }

    private void Headers(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("text/plain");
        response.setCharacterEncoding("utf-8");

        PrintWriter writer = response.getWriter();

        printStart(response, "Headers");

        request.getHeaderNames().asIterator()
                .forEachRemaining(headerName -> writer.println(headerName + " = " + request.getHeader(headerName)));

        printEnd(response, "Headers");
        writer.println();
    }
}

이번엔 Iterator로 빠르게 출력했다.

이렇게 이어서 잘 출력이 되는 것을 볼 수 있다.

나머지 부분들도 request를 보면 찾을 수 있지만, 이 정도만 하고 넘어가 보겠다.

 

  • HTTP request - GET

데이터를 서버로 전송해 보자.

서버로 데이터를 전송할 때는 Postman을 사용하면 된다.

package hello.servlet.basic.request;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(name = "requestParam", urlPatterns = "/requestParam")
public class RequestParam extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        System.out.println("[파리미터 조회] - start");
        req.getParameterNames().asIterator()
                .forEachRemaining(paramName -> System.out.println(paramName + " = " + req.getParameter(paramName)));

        System.out.println("[파리미터 조회] - end");
        System.out.println();

        System.out.println("[개별 파라미터 조회]");
        System.out.println("name : " + req.getParameter("name"));
        System.out.println("age: " + req.getParameter("age"));

        System.out.println();
    }
}

이렇게 Iterator를 이용하여 전체를 조회할 수 있지만, 보통 getParameter()를 이용해서 개별 조회를 한다.

Postman으로 데이터를 전송하면

이렇게 출력되게 된다.

 

  • HTTP request - POST

이번엔 POST 방식이다.

저번 GET 방식은 URL로 데이터를 보낼 수도 있긴 했지만, 이번 POST 방식은 Postman을 이용해야 할 것이다.

근데 코드는 수정할 필요가 없다.

POST 방식도 GET과 쿼리 파라미터 형식이 같기 때문에 조회 메서드는 동일하게 사용할 수 있다.

이렇게 동일한 결과가 나오는 것을 볼 수 있다.

 

  • HTTP request - 텍스트
package hello.servlet.basic.request;

import jakarta.servlet.ServletException;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

@WebServlet(name = "requestBodyString", urlPatterns = "/requestBodyString")
public class RequestBodyString extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletInputStream inputStream = req.getInputStream();

        String message = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);

        System.out.println("message = " + message);

    }
}

InputStream은 바이트 코드를 반환하기 때문에 UTF_8로 지정을 해주어 바꿔줘야 한다.

 

Postman으로 POST에 raw로 그냥 메시지를 보내면

이렇게 잘 출력이 되는 것을 볼 수 있다.

 

  • HTTP request - JSON

요즘 많이 사용하는 JSON 형식이다.

우선 Postman에서 JSON으로 데이터를 보내는 방법부터 알아보겠다.

Body에서 raw, JSON을 선택하고 형식에 맞게 작성을 하면 된다.

 

그러고 JSON 형식으로 데이터를 가져올 수 있게 그에 맞는 객체를 생성한다.

package hello.servlet.basic;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

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

깔끔하게 Lombok을 사용한다.

 

package hello.servlet.basic.request;

import com.fasterxml.jackson.databind.ObjectMapper;
import hello.servlet.basic.JsonData;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

@WebServlet(name = "requestBodyJson", urlPatterns = "/requestBodyJson")
public class RequestBodyJson extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletInputStream inputStream = req.getInputStream();
        String message = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
        
        JsonData jsonData = objectMapper.readValue(message, JsonData.class);

        System.out.println(jsonData);
    }
}

ObjectMapper를 이용해서 가져온 데이터를 해당 객체로 변환해 준다.

이렇게 잘 출력되는 것을 볼 수 있다.

 

  • HTTP response

HttpServletResponse를 사용하여 응답 메시지를 작성할 수 있다.

package hello.servlet.basic.response;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet(name = "responseHeader", urlPatterns = "/response-header")
public class ResponseHeaderServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //상태 라인
        resp.setStatus(HttpServletResponse.SC_OK);
        
        //response header
        resp.setContentType("text/plain");
        resp.setCharacterEncoding("utf-8");
        resp.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
        resp.setHeader("Pragma", "no-cache");
        resp.setHeader("my-header", "hi");
        
    }
    
    private void redirect(HttpServletResponse response) throws IOException{
        response.sendRedirect("/basic/hi-form.html");
    }
    
    private void cookie(HttpServletResponse response){
        Cookie cookie = new Cookie("myCookie", "good!");
        cookie.setMaxAge(60);
        response.addCookie(cookie);
    }
}

이렇게 response 객체를 이용해서 편하게 응답 메시지를 만들 수 있다.

 

이번에는 중요한 응답 메시지로 JSON을 작성하는 방법이다.

여기서도 ObjectMapper를 사용한다.

package hello.servlet.basic.response;

import com.fasterxml.jackson.databind.ObjectMapper;
import hello.servlet.basic.JsonData;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet(name = "responseJson", urlPatterns = "/responseJson")
public class ResponseJson extends HttpServlet {
    
    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("application/json");
        resp.setCharacterEncoding("utf-8");
        
        JsonData jsonData = new JsonData();
        jsonData.setName("hi");
        jsonData.setAge(20);
        
        String result = objectMapper.writeValueAsString(jsonData);
        
        resp.getWriter().write(result);
    }
}

이렇게 객체를 만들고 ObjectMapper를 이용하여 문자열로 바꿔준 후 write로 작성을 해주면 된다.

그러면 이렇게 잘 전달이 되는 것을 볼 수 있다.

'백엔드 > 스프링' 카테고리의 다른 글

스프링 16일차  (0) 2023.03.29
스프링 15일차  (0) 2023.03.28
스프링 13일차  (0) 2023.03.25
스프링 12일차  (0) 2023.02.15
스프링 11일차  (0) 2023.02.12
728x90

인프런 김영한님의 강의를 참고했습니다.

 

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의

웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., -

www.inflearn.com

HTTP와 JPA 공부를 어느정도 끝내고 다시 스프링으로 돌아왔다.

 

HTTP의 기본도 공부를 했으니

오늘은 스프링이 어떻게 돌아가는지 알아보기 위해 WAS와 그 기원인 tomcat, Servlet & JSP등을 알아보고 온다.

 

  • Web Server, WAS

Web Server

HTTP 기반으로 동작을 하며, 정적 리소스를 제공한다.

정적 리소스라하면 단순한 파일들인 HTML, CSS, JPG 등을 말한다.

 

WAS(Web Application Server)

이 친구도 마찬가지로 HTTP 기반으로 동작한다.

정적 리소스도 제공은 가능하기 때문에 Web Server의 기능을 수행할 수도 있기는 하다.

그러고도 프로그램 코드를 실행 가능하기 때문에 애플리케이션 로직을 수행할 수 있다.

예를 들면 저번에 공부했던 tomcat이 있다.

 

그렇기 때문에 웹 시스템을 WAS와 DB만으로 구성이 가능하다.

WAS는 웹 서버의 기능을 수행할 수 있기 때문이다.

 

하지만 이렇게 만들면 WAS가 너무 많은 역할을 담당한다.

WAS는 복잡한 로직들을 수행하기 때문에 장애가 많이 일어나고, 이 때문에 정적 리소스조차 수행 불가능 할 수 있다.

그렇기 때문에 정적 리소스는 웹 서버가 처리하고 로직은 WAS가 수행하도록 섞어서 사용하게 된다.

 

Web Server는 WAS에 비해 장애가 발생하지 않기 때문에 WAS에 장애가 생기면 Web Server를 통해 오류 화면을 제공할 수도 있다.

 

  • Servlet

서블릿은 저번에 공부 했던 내용이다.

서블릿을 사용하면 HTTP 송수신 과정에서 애플리케이션 로직들만 신경 쓸 수 있게 해준다.

HTTP를 요청하면 WAS는 Request, Response 객체를 만들어서 서블릿 객체를 호출한다.

그러면 그 Request, Response 객체를 받아서 Request를 보고 HTTP 요청 정보를 확인하고, Response에 응답 정보를 입력한다.

 

위 그림처럼 서블릿을 지원하는 WAS를 서블릿 컨테이너라고 한다.

저 컨테이너는 서블릿 객체들을 생성하고 객체들의 생명주기를 관리한다.

객체들은 Spring과 같이 계속 생성하는 비효율적이기 때문에 싱글톤으로 관리한다.

이 싱글톤 객체들을 어떻게 사용하는 것이 좋으며, 주의해야 하는 부분은 무엇인지 알고 있을 것이다.

 

  • 멀티 쓰레드

만약 서버가 단일 쓰레드라면?

먼저 들어온 요청이 모두 끝난 후에 다음 요청이 쓰레드를 사용하여 응답을 받을 수 있을 것이다.

이렇게되면 다음에 들어온 요청은 대기 시간이 굉장히 길어질 것이다.

여기서 먼저 들어온 요청에 장애가 생겨서 쓰레드를 반환하지 않는다면 다음 요청은 평생 응답을 받을 수 없을 것이다.

 

그렇다고 해서 요청마다 쓰레드를 생성한다면 쓰레드를 생성할 때마다 굉장히 오랜 시간이 걸리며, 고객의 요청이 너무 많이 오면 서버의 CPU와 메모리가 다운될 수 있다.

 

그렇기 때문에 미리 쓰레드들을 만들어놓고 도서관에서 책 빌리는 것 처럼 하나씩 쓰레드를 빌리고 반납하는 형식으로 사용한다.

만약 쓰레드 풀에 남은 쓰레드가 없다면 쓰레드를 대기하게 된다.

이렇게하면 쓰레드가 미리 생성되어 있기 때문에 생성하는 시간을 줄일 수 있다.

 

  • HTML, HTTP API, 렌더링

위에서 말했듯이 고정된 HTML 파일들은 웹 브라우저를 사용하여 제공하면 된다.

사용자에 따라 달라지는 동적 HTML 페이지는 WAS를 사용하여 제공한다.

 

만약 HTML이 아닌 데이터만을 전달하는 것이 목적이라면 굳이 HTML에 담아서 전달할 필요가 없다.

WAS에서 주로 JSON 형식으로 주고 받는다.

 

그러면 여기서 렌더링 방식에 따라 서버사이드 렌더링, 클라이언트 사이드 렌더링으로 나뉜다.

SSR(서버 사이드 렌더링) CSR(클라이언트 사이드 렌더링)
HTML 최종 결과를 서버에서 만들어서 클라이언트에 전달 HTML 결과를 자바스크립트를 사용해 동적으로 생성해서 적용
보통 정적인 화면에 사용한다. 보통 동적인 화면에 사용
JSP, Thymeleaf React, Vue.js

SSR은 서버에서 다 받아오기 때문에 바로 이해할 수 있을 것 같고

CSR은 이런 과정을 거쳐 렌더링이 된다.

 

'백엔드 > 스프링' 카테고리의 다른 글

스프링 15일차  (0) 2023.03.28
스프링 14일차  (0) 2023.03.26
스프링 12일차  (0) 2023.02.15
스프링 11일차  (0) 2023.02.12
스프링 10일차  (0) 2023.02.11
728x90

초반부에 빠르게 넘어갔던 영속 컨텍스트와 LifeCycle에 대해 좀 더 자세히 알아보자.

 

  • 영속 컨텍스트

영속 엔티티는 DB 데이터에 매핑되는 메모리상의 객체이다.

엔티티라고 하면 우리가 무수히 만들었던 Entity가 생각이 날 것이다.

 

영속 컨텍스트는 메모리 저장소로 엔티티들을 보관하고 있다.

EntityManager로 관리를 하며, EntityManager.close()를 호출하면 영속 컨텍스트를 제거한다.

영속 컨텍스트는 동일 식별자를 가지면 그 엔티티들은 동일한 엔티티로 판단한다.

 

그렇기 때문에

System.out.println("first find");
User first = entityManager.find(User.class, email1);
System.out.println("second find");
User second = entityManager.find(User.class, email2);
logger.info("same object: {}", (first == second));

이렇게 동일 식별자를 가진 데이터를 2번 조회하게 되면

first find
select로 해당 데이터 조회
second find
//select 쿼리가 실행되지 않음
same object: true

이렇게 조회하는 쿼리가 한 번만 실행되는데, 같은 엔티티라고 판단해서 다시 조회하지 않고 영속 컨테이너에서 가져오기 때문이다.

 

  • 영속 객체 LifeCycle

조회(find)나 persist()를 통해서 영속 컨텍스트에 저장이 된 객체는 관리 대상이 된다.

관리 대상에서 제외하고 싶으면 detach()를 사용하면 된다.

close()를 사용해도 manager가 닫히기 때문에 더 이상 관리되지 않는다.

 

'백엔드 > JPA' 카테고리의 다른 글

JPA 9장 (Map collection mapping)  (0) 2023.03.21
JPA 8장 (List collection mapping)  (0) 2023.03.18
JPA 7장 (Set collection mapping)  (0) 2023.03.18
JPA 6장 (@Embeddable)  (0) 2023.03.17
JPA 5장 (Entity 식별자 생성 방식)  (0) 2023.03.16
728x90

이번엔 Map을 mapping 해본다.

 

이런 테이블이 있다고 해보자.

import jakarta.persistence.*;

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

@Entity
@Table(name = "book")
public class book {
    @Id
    private String id;
    private String title;
    private String content;
    @ElementCollection
    @CollectionTable(
            name = "book_prop",
            joinColumns = @JoinColumn(name="book_id")
    )
    @MapKeyColumn(name = "name")
    @Column(name = "value")
    private Map<String, String> props = new HashMap<>();

    protected book(){}

    public book(String id, String title, String content, Map<String, String> props) {
        this.id = id;
        this.title = title;
        this.content = content;
        this.props = props;
    }
    
    //getter... setter...
}

이렇게 @CollectionTable로 저장할 테이블을 지정을 해주고

Map에 들어간 데이터를 어떤 column에 넣어줄지 PK는 @MapKeyColumn에, 그냥 데이터는 @Column으로 지정을 해준다.

 

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.Persistence;

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

public class book_main {

    public static void main(String[] args) {
        EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("jpabegin");
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        EntityTransaction entityTransaction = entityManager.getTransaction();

        Map<String, String> props1 = new HashMap<>();
        Map<String, String> props2 = new HashMap<>();

        props1.put("name1", "value1");
        props1.put("name2", "value2");
        book book1 = new book("1", "이름", "내용", props1);
        book book2;

        try{
            entityTransaction.begin();
            entityManager.persist(book1);
            entityTransaction.commit();
        }catch(NullPointerException exception){
            throw new NullPointerException();
        }

        try{
            book2 = entityManager.find(book.class, "1");
        }catch(NullPointerException exception){
            throw new NullPointerException();
        }

        System.out.println("book: " + book2);
        System.out.println("props: " + book2.getProps());
    }
}

이렇게 추가를 하고 조회를 하면

정상 작동하는 것을 볼 수 있다.

 

늘 그렇듯이 이번엔 Embeddable 타입이다.

이런 테이블이 있다고 하고, 여기서 PropValue를 Embeddable로 만들 생각이다.

우선 Embeddable 타입을 먼저 작성한다.

import jakarta.persistence.Access;
import jakarta.persistence.AccessType;
import jakarta.persistence.Embeddable;

@Embeddable
@Access(AccessType.FIELD)
public class PropValue {
    private String value;
    private boolean enabled;

    public PropValue(String value, boolean enabled) {
        this.value = value;
        this.enabled = enabled;
    }

    protected PropValue () {}
	
    //getter...setter...
    
    @Override
    public String toString() {
        return "PropValue{" +
                "value='" + value + '\'' +
                ", enabled=" + enabled +
                '}';
    }
}

이렇게 항상 작성하던 @Embeddable을 작성하고

import jakarta.persistence.*;

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

@Entity
@Table(name = "book")
public class book2 {
    @Id
    private String id;
    private String title;
    private String content;
    @ElementCollection
    @CollectionTable(
            name = "book_prop",
            joinColumns = @JoinColumn(name = "book_id")
    )
    @MapKeyColumn(name = "name")
    private Map<String, PropValue> props = new HashMap<>();

	//getter...setter...
    
    @Override
    public String toString() {
        return "book2{" +
                "id='" + id + '\'' +
                ", title='" + title + '\'' +
                ", content='" + content + '\'' +
                ", props=" + props +
                '}';
    }
}

@MapKeyColumn을 이용해서 그랬던 것처럼 작성을 해준다.

여기에 Map의 타입에 <PK, @Embeddable>을 넣어서 작성해준다.

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.Persistence;

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

public class book_main2 {

    public static void main(String[] args) {
        EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("jpabegin");
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        EntityTransaction entityTransaction = entityManager.getTransaction();

        Map<String, PropValue> Propvalues = new HashMap<>();

        PropValue propValue1 = new PropValue("value1", true);
        PropValue propValue2 = new PropValue("value2", true);

        Propvalues.put("String1", propValue1);
        Propvalues.put("String2", propValue2);

        book2 book1 = new book2("id1", "title1", "content1", Propvalues);
        book2 book2;

        try{
            entityTransaction.begin();
            entityManager.persist(book1);
            entityTransaction.commit();
        }catch (NullPointerException exception){
            throw new NullPointerException();
        }

        try{
            book2 = entityManager.find(book2.class, "id1");
            System.out.println(book2);
        }catch(NullPointerException exception){
            throw new NullPointerException();
        }finally {
            entityManager.close();
        }

        entityManagerFactory.close();

    }
}

이렇게 동작하는 것을 볼 수 있다.

'백엔드 > JPA' 카테고리의 다른 글

JPA 10장 (영속 컨텍스트와 LifeCycle)  (0) 2023.03.22
JPA 8장 (List collection mapping)  (0) 2023.03.18
JPA 7장 (Set collection mapping)  (0) 2023.03.18
JPA 6장 (@Embeddable)  (0) 2023.03.17
JPA 5장 (Entity 식별자 생성 방식)  (0) 2023.03.16
728x90

이번엔 List이다.

문제에 관련된 DB가 있다고 하자.

테이블은

question
id varchar(10)
text varchar(20)
question_choice
question_id varchar(10)
idx integer(10)
text varchar(20)

그러면 question안에 객관식 보기인 question_choice들이 순서대로 들어가 있어야 할 것이다.

이런 경우에는 순서가 존재하기 때문에 저번에 사용한 Set을 사용할 수 없고, List를 사용해야 한다.

import jakarta.persistence.*;

import java.util.List;

@Entity
@Table(name = "question")
public class question {
    @Id
    private String id;
    private String text;

    @ElementCollection
    @CollectionTable(
            name = "question_choice",
            joinColumns = @JoinColumn(name = "question_id")
    )
    @OrderColumn(name = "idx")
    @Column(name = "text")
    private List<String> choices;

    protected question () {}

    public question(String id, String text, List<String> choices) {
        this.id = id;
        this.text = text;
        this.choices = choices;
    }
    //getter... settter
}
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.Persistence;

import java.util.List;

public class questionMain {

    public static void main(String[] args) {
        EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("jpabegin");
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        EntityTransaction entityTransaction = entityManager.getTransaction();

        try{
            entityTransaction.begin();
            question question1 = new question("1", "문제1", List.of("1번", "2번", "3번", "4번", "5번"));
            entityManager.persist(question1);
            entityTransaction.commit();
        }catch (Exception ignored){

        }

        try{
            question question2 = entityManager.find(question.class,"1");
            for(String choice : question2.getChoices()){
                System.out.println(choice);
            }
        }finally {
            entityManager.close();
        }

        entityManagerFactory.close();
    }
}

이렇게 Set과 똑같이 작성하지만, @OrderColumn을 추가해준다.

당연히 @Embedded도 사용이 가능하다.

import jakarta.persistence.Embeddable;

@Embeddable
public class choice {
    private String text;
    private boolean input;

    protected choice() {}

    public choice(String text, boolean input) {
        this.text = text;
        this.input = input;
    }

	//getter... setter...
}
import jakarta.persistence.*;

import java.util.List;

@Entity
@Table(name = "question")
public class question2 {
    @Id
    private String id;
    private String text;

    @ElementCollection
    @CollectionTable(
            name = "question_choice",
            joinColumns = @JoinColumn(name = "question_id")
    )
    @OrderColumn(name = "idx")
    private List<choice> choiceList;

    protected question2 (){}

    public question2(String id, String text, List<choice> choiceList) {
        this.id = id;
        this.text = text;
        this.choiceList = choiceList;
    }
	//getter... setter...
}
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.Persistence;

import java.util.List;

public class questionMain2 {

    public static void main(String[] args) {
        EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("jpabegin");
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        EntityTransaction entityTransaction = entityManager.getTransaction();

        try{
            entityTransaction.begin();
            question2 question1 = new question2("1", "문제1", List.of(new choice("1", false),
                    new choice("2", true)));
            entityManager.persist(question1);
            entityTransaction.commit();
        }catch (Exception ignored){

        }

        try{
            question2 question2 = entityManager.find(question2.class,"1");
            for(choice choice : question2.getChoiceList()){
                System.out.println(choice.getInput() + ", " + choice.getText());
            }
        }finally {
            entityManager.close();
        }

        entityManagerFactory.close();
    }
}

'백엔드 > JPA' 카테고리의 다른 글

JPA 10장 (영속 컨텍스트와 LifeCycle)  (0) 2023.03.22
JPA 9장 (Map collection mapping)  (0) 2023.03.21
JPA 7장 (Set collection mapping)  (0) 2023.03.18
JPA 6장 (@Embeddable)  (0) 2023.03.17
JPA 5장 (Entity 식별자 생성 방식)  (0) 2023.03.16

+ Recent posts