Spring 입문 03

yuns·2022년 10월 3일
0

Spring

목록 보기
4/13
post-thumbnail

나만의 셀렉샵 프로젝트

프로젝트 준비

  1. spring initializer로 초기 프로젝트 생성
  2. settings - gradle / project structure에서 자바11버전으로 변경
  3. application.properties에 웹콘솔 설정 코드 작성

검색 API (Servlet vs Controller)

기능 : 키워드로 상품 검색
Method : GET
URL : /api/search?query=검색어
반환 : List< ItemDto >

Controller 사용 시 장점

  • HTTP request, response를 위한 중복 코드를 생략할 수 있다.

  • Servlet은 요청과 응답을 수행하는 코드를 직접 만들어줘야하지만, Controller는 annotation을 이용해 간편하게 처리할 수 있다.

  • api이름마다 파일을 만들 필요가 없다

ItemSearchController.java

package com.sparta.springcore.controller;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sparta.springcore.dto.ItemDto;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;

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

@Controller
public class ItemSearchController {

    // Controller 가 자동으로 해주는 일
    // 1. API Request 의 파라미터 값에서 검색어 추출 -> query 변수
    // 5. API Response 보내기
    //  5.1) response 의 header 설정
    //  5.2) response 의 body 설정
    @GetMapping("/api/search")
    @ResponseBody
    public List<ItemDto> getItems(@RequestParam String query) throws IOException {
        // 2. 네이버 쇼핑 API 호출에 필요한 Header, Body 정리
        RestTemplate rest = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.add("X-Naver-Client-Id", "zdqMoIkFaK8uKvC2oNY2");
        headers.add("X-Naver-Client-Secret", "LiZfsgtuD5");
        String body = "";
        HttpEntity<String> requestEntity = new HttpEntity<>(body, headers);

        // 3. 네이버 쇼핑 API 호출 결과 -> naverApiResponseJson (JSON 형태)
        ResponseEntity<String> responseEntity = rest.exchange("https://openapi.naver.com/v1/search/shop.json?query=" + query, HttpMethod.GET, requestEntity, String.class);
        String naverApiResponseJson = responseEntity.getBody();

        // 4. naverApiResponseJson (JSON 형태) -> itemDtoList (자바 객체 형태)
        //  - naverApiResponseJson 에서 우리가 사용할 데이터만 추출 -> List<ItemDto> 객체로 변환
        ObjectMapper objectMapper = new ObjectMapper()
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        JsonNode itemsNode = objectMapper.readTree(naverApiResponseJson).get("items");
        List<ItemDto> itemDtoList = objectMapper
                .readerFor(new TypeReference<List<ItemDto>>() {})
                .readValue(itemsNode);

        return itemDtoList;
    }
}

Spring MVC

Model - View - Controller 디자인 패턴 (어플리케이션 설계 개발 방법론)

동적 웹페이지의 controller

  • 클라이언트의 요청을 Model로 받아 처리
  • Template engine에게 View(HTML파일), Model 전달
  • Template engine은 View에 Model을 적용하여 동적 웹페이지 생성
  • 클라이언트에게 View 전달

HTTP 메시지

서버와 클라이언트 간의 Request, Response는 HTTP 메시지 규약을 따른다.

Spring으로 html파일 띄우기

@Controller
@RequestMapping("/hello/response")
public class HelloResponseController {
    // [Response header]
    //   Location: http://localhost:8080/hello.html
    @GetMapping("/html/redirect")
    public String htmlFile() {
        return "redirect:/hello.html";
    }

@RequestMapping 안에 @GetMapping이 있다.
모든 페이지는 RequestMapping에 적어진 /hello/response를 주소창에 달게 되고, 그 뒤에 /html/redirect같이 다른걸 더 붙여서 페이지를 이동하게된다.

Model의 이해 : 내가 요청한 데이터를 화면에 띄워 줄 수 있는 빈 공간이라고 생각하면 됨

@RestController annotation

@RestController = @Controller + @ResponseBody
controller의 역할을 하면서, 응답값으로 JSON을 받아올것을 알려줌


Controller과 Service, Repository를 분리해서 사용하는 이유

Service와 Repository를 따로 만들지 않아도, Controller에서 모든 응답을 처리 할 수 있다.
하지만 굳이 나눠서 사용해야 하는 이유는 무엇일까?

Controller에 몰아서 작성하면 안좋은 이유

  • 한 개의 클래스에 너무 많은 양의 코드 존재. 코드 이해가 어려워짐
  • 코드 추가 혹은 변경 요청이 생길 때, 모듈화가 되어있으면 변경이 쉬움
  • 객체지향 프로그래밍에 맞지 않음. controller는 controller만의 일을 하는 것이 중요

각각의 역할

  • Controller : 클라이언트의 요청을 받음, 클라이언트에 응답
  • Service : 사용자의 요구사항 처리 (비즈니스 로직), db정보 필요 시 Repository에 요청
  • Repository : db관리, db CRUD 작업

노랑 : Controller / 빨강 : Service / 파랑 : Repository

Controller로 모든 기능을 담았을 때

AllInOneController.java

package com.sparta.springcore.controller;

import com.sparta.springcore.dto.ProductMypriceRequestDto;
import com.sparta.springcore.dto.ProductRequestDto;
import com.sparta.springcore.entity.Product;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

@RequiredArgsConstructor // final로 선언된 멤버 변수를 자동으로 생성합니다.
@RestController // JSON으로 데이터를 주고받음을 선언합니다.
public class AllInOneController {

    // 신규 상품 등록
    @PostMapping("/api/products")
    public Product createProduct(@RequestBody ProductRequestDto requestDto) throws SQLException {
        // 요청받은 DTO 로 DB에 저장할 객체 만들기
        Product product = new Product(requestDto);

        // DB 연결
        Connection connection = DriverManager.getConnection("jdbc:h2:mem:springcoredb", "sa", "");

        // DB Query 작성
        PreparedStatement ps = connection.prepareStatement("select max(id) as id from product");
        ResultSet rs = ps.executeQuery();
        if (rs.next()) {
            // product id 설정 = product 테이블의 마지막 id + 1
            product.setId(rs.getLong("id") + 1);
        } else {
            throw new SQLException("product 테이블의 마지막 id 값을 찾아오지 못했습니다.");
        }
        ps = connection.prepareStatement("insert into product(id, title, image, link, lprice, myprice) values(?, ?, ?, ?, ?, ?)");
        ps.setLong(1, product.getId());
        ps.setString(2, product.getTitle());
        ps.setString(3, product.getImage());
        ps.setString(4, product.getLink());
        ps.setInt(5, product.getLprice());
        ps.setInt(6, product.getMyprice());

        // DB Query 실행
        ps.executeUpdate();

        // DB 연결 해제
        ps.close();
        connection.close();

        // 응답 보내기
        return product;
    }

    // 설정 가격 변경
    @PutMapping("/api/products/{id}")
    public Long updateProduct(@PathVariable Long id, @RequestBody ProductMypriceRequestDto requestDto) throws SQLException {
        Product product = new Product();

        // DB 연결
        Connection connection = DriverManager.getConnection("jdbc:h2:mem:springcoredb", "sa", "");

        // DB Query 작성
        PreparedStatement ps = connection.prepareStatement("select * from product where id = ?");
        ps.setLong(1, id);

        // DB Query 실행
        ResultSet rs = ps.executeQuery();
        if (rs.next()) {
            product.setId(rs.getLong("id"));
            product.setImage(rs.getString("image"));
            product.setLink(rs.getString("link"));
            product.setLprice(rs.getInt("lprice"));
            product.setMyprice(rs.getInt("myprice"));
            product.setTitle(rs.getString("title"));
        } else {
            throw new NullPointerException("해당 아이디가 존재하지 않습니다.");
        }

        // DB Query 작성
        ps = connection.prepareStatement("update product set myprice = ? where id = ?");
        ps.setInt(1, requestDto.getMyprice());
        ps.setLong(2, product.getId());

        // DB Query 실행
        ps.executeUpdate();

        // DB 연결 해제
        rs.close();
        ps.close();
        connection.close();

        // 응답 보내기 (업데이트된 상품 id)
        return product.getId();
    }

    // 등록된 전체 상품 목록 조회
    @GetMapping("/api/products")
    public List<Product> getProducts() throws SQLException {
        List<Product> products = new ArrayList<>();

        // DB 연결
        Connection connection = DriverManager.getConnection("jdbc:h2:mem:springcoredb", "sa", "");

        // DB Query 작성 및 실행
        Statement stmt = connection.createStatement();
        ResultSet rs = stmt.executeQuery("select * from product");

        // DB Query 결과를 상품 객체 리스트로 변환
        while (rs.next()) {
            Product product = new Product();
            product.setId(rs.getLong("id"));
            product.setImage(rs.getString("image"));
            product.setLink(rs.getString("link"));
            product.setLprice(rs.getInt("lprice"));
            product.setMyprice(rs.getInt("myprice"));
            product.setTitle(rs.getString("title"));
            products.add(product);
        }

        // DB 연결 해제
        rs.close();
        connection.close();

        // 응답 보내기
        return products;
    }
}

0개의 댓글