Project MySelectShop - AllInOne

박영준·2022년 12월 4일
0

Java

목록 보기
20/112

Controller, Service, Repository 를 하나의 클래스에서 구현해보자

MySelectShop

필요한 기능 확인하기
1. 키워드로 상품 검색하고 그 결과를 목록으로 보여주기
2. 관심 상품 등록하기
3. 관심 상품 조회하기
4. 관심 상품 최저가 등록하기

1. Product 설계

키워드로 상품 검색하고 그 결과를 목록으로 보여주기: Project MySelectShop - Prepare 에서 이미 구현 완료

ResponseDto
: Entity를 그대로 반환해도 무방하지만, Entity에 있는 컬럼 값들 뿐만 아니라, 그 이외의 값들도 보내줘야할 경우 ResponseDto를 만들어서 여기 안에 담아서 값을 보내주는 경우가 있음

2. AllInOneController 만들기

AllInOneController

//JPA 를 사용한다면, 이런 반복되고 긴 코드를 사용할 필요가 없어진다
package com.sparta.myselectshopbeta.controller;

import com.sparta.myselectshopbeta.dto.ProductMypriceRequestDto;
import com.sparta.myselectshopbeta.dto.ProductRequestDto;
import com.sparta.myselectshopbeta.dto.ProductResponseDto;
import com.sparta.myselectshopbeta.entity.Product;
import org.springframework.web.bind.annotation.*;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/api")

public class AllInOneController {

    // 관심 상품 등록하기
    @PostMapping("/products")
    public ProductResponseDto createProduct(@RequestBody ProductRequestDto requestDto) throws SQLException {

        // 요청받은 DTO 로 DB에 저장할 객체 만들기
        Product product = new Product(requestDto);

        // DB 연결
        //저번(jpaRepository 를 사용했던)과는 다르게, Connection 과 DriverManager 를 이용해서 직접 DB 에 연결
        Connection connection = DriverManager.getConnection("jdbc:h2:mem:db", "sa", "");

        // DB Query 작성
        //직법 쿼리도 작성해야 했음
        PreparedStatement ps = connection.prepareStatement("select max(id) as id from product"); //JPA 에서는 Id 값을 찾아서 넣지 않아도, @GeneratedValue(strategy = GenerationType.IDENTITY) 에 의해 자동으로 생성된다
        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(?, ?, ?, ?, ?, ?)");
        //아래의 값들이 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();     //PreparedStatement 도 직접 닫아주기
        connection.close();     //Connection 도 직접 닫아주기

        // 응답 보내기
        return new ProductResponseDto(product);

    }

    // 관심 상품 조회하기
    @GetMapping("/products")
    public List<ProductResponseDto> getProducts() throws SQLException {
        List<ProductResponseDto> products = new ArrayList<>();

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

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

        // DB Query 결과를 상품 객체 리스트로 변환
        while (rs.next()) {     //List 라서 while 문을 돌려서 가져옴
            Product product = new Product();        //객체 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(new ProductResponseDto(product));  //products 는 ProductResponseDto 를 List 형식으로 만들어놓은 ArrayList 배열
        }

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

        // 응답 보내기
        return products;
    }

    // 관심 상품 최저가 등록하기
    @PutMapping("/products/{id}")
    public Long updateProduct(@PathVariable Long id, @RequestBody ProductMypriceRequestDto requestDto) throws SQLException {
        Product product = new Product();    //select * from product where id = ? 를 사용해서, product 객체 안에 넣음
        // DB 연결
        Connection connection = DriverManager.getConnection("jdbc:h2:mem:db", "sa", "");

        // DB Query 작성
        //where 문을 사용해서, 내가 등록한 product 의 id 도 같이 넣어줌
            //basic.js 에 보면, let targetId 가 전역으로 걸려있는데, 변수 targetId 안에 내가 선택한 product 의 id 가 들어가서 가져올 수 있는 것
            //@PathVariable Long id 로 가져옴
        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 작성
        //update 의 where 에 받아온 id 값을 넣음
        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();
    }
}

Product

package com.sparta.myselectshopbeta.entity;

import com.sparta.myselectshopbeta.dto.ProductRequestDto;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@Entity // DB 테이블 역할을 합니다.
@NoArgsConstructor
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // ID가 자동으로 생성 및 증가합니다.
    private Long id;

    @Column(nullable = false)
    private String title;

    @Column(nullable = false)
    private String image;

    @Column(nullable = false)
    private String link;

    @Column(nullable = false)
    private int lprice;

    @Column(nullable = false)
    private int myprice;

    public Product(ProductRequestDto requestDto) {
        this.title = requestDto.getTitle();
        this.image = requestDto.getImage();
        this.link = requestDto.getLink();
        this.lprice = requestDto.getLprice();
        this.myprice = 0;
    }
}

ProductRequestDto

package com.sparta.myselectshopbeta.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class ProductRequestDto {
    // 관심상품명
    private String title;
    // 관심상품 썸네일 image URL
    private String image;
    // 관심상품 구매링크 URL
    private String link;
    // 관심상품의 최저가
    private int lprice;
}

ProductMypriceRequestDto

package com.sparta.myselectshopbeta.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class ProductMypriceRequestDto {
    private int myprice;
}

ProductResponseDto

package com.sparta.myselectshopbeta.dto;

import com.sparta.myselectshopbeta.entity.Product;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class ProductResponseDto {
    private Long id;
    private String title;
    private String link;
    private String image;
    private int lprice;
    private int myprice;

    public ProductResponseDto(Product product) {
        this.id = product.getId();
        this.title = product.getTitle();
        this.link = product.getLink();
        this.image = product.getImage();
        this.lprice = product.getLprice();
        this.myprice = product.getMyprice();
    }
}

3. AllInOneController의 한계점

웹 서비스 구성도

API

한계점

  • 한 개의 클래스에 너무 많은 양의 코드가 존재
    - 코드 이해가 어려움: 처음부터 끝까지 다 읽어야 코드 내용을 이해할 수 있음
  • 현업에서는 코드 추가 혹은 변경 요청이 계속 생김
    (예시)
    1. 관심 상품 등록 시 Client 에게 응답 (Response) 하는 값 변경
      1. 등록된 Product 전체 정보 → 등록된 Product 의 id
    2. 최저가 (Myprice) 업데이트 조건 변경
      1. Client 가 최저가를 0원 이하로 입력 → 에러 발생
    3. DB 테이블 이름 변경
      1. Product 테이블의 lpricelowprice 변경

절차적 프로그래밍 vs 객체지향 프로그래밍

절차적 프로그래밍

  • 초기 프로그래밍 방식

  • 컴퓨터가 해야할 일들을 쭈~욱 순차적으로 나열해 놓는 코딩 방식

  • 예시 1) AllInOneController 클래스의 각 API 처리내용

  • 예시 2) 메모앱 (ex. Evernote, Notion 등) 예제를 통한 이해
    - 아무 메모장을 띄워서, 생각나는데로 사고의 흐름을 적는다 (퀵메모)
    - 장점: 메모를 작성하기에 편함 (직관적)
    - 단점: 메모양이 많아지면 정리가 어려움, 내가 원하는 메모 내용을 찾기 어려움

객체지향 프로그래밍

  • 소프트웨어의 규모가 점점 커지면서 필요성이 부각이 됨

  • 대부분의 사람들은 한 번에 여러가지 다른 생각을 하는데 취약

  • 하나의 사물 (객체) 에 하나의 의미를 부여하는 것처럼 프로그래밍하게 됨

  • 예시 1)
    - 뭔가 자를 것이 필요하면 '✂️' 를 떠올림 (class Sciccors)
    - 종이에 적을 게 필요하면 '✏️' 을 떠올림 (class Pen)
    - "하나의 역할" → 객체
    (Controller는 HTTP request를 받는 역할, Service는 비지니스 로직을 수행하는 역할, Repository는 DB에 연결해서 데이터를 받아오는 역할)

  • 예시 2) 메모앱 (ex. Evernote, Notion 등) 예제를 통한 이해

  • "주제별"로 정리해서 메모한다. (ex. 주식 투자, 바리스타 공부)

  • 장점: 메모를 관리하기 쉬움

  • 단점
    - 주제를 어떻게 나눠야 할지, 고민이 필요
    - 중복된 메모 내용이 생길 수 있음 (ex. "나중에 읽을 책", "도움이 되는 책")
    - 하나의 주제에 너무 적거나, 너무 많은 메모 내용이 들어갈 수 있음 (ex. "88 올림픽 한국 순위와 금메달 개수", "공부")
    - 주기적으로 주제별 (카테고리별) 정리가 필요할 수 있음
    - 떠오르는 생각들을 적을때 주제가 다른 메모내용들을 적기 어려움

추천 프로그래밍 방식

  1. 처음엔 "절차적 프로그래밍"
  2. "객체지향 프로그래밍"으로 리팩토링

리팩토링
: 기능 상의 변경 없이 프로그래밍 구조를 개선하는 것
1. 하나의 파일에 너무 많은 코드가 들어가지 않게!
2. 역할별로 코드 분리!!
3. 코드를 좀 더 읽기 편하게!!!

profile
개발자로 거듭나기!

0개의 댓글