Controller, Service, Repository 를 하나의 클래스에서 구현해보자
필요한 기능 확인하기
1. 키워드로 상품 검색하고 그 결과를 목록으로 보여주기
2. 관심 상품 등록하기
3. 관심 상품 조회하기
4. 관심 상품 최저가 등록하기
키워드로 상품 검색하고 그 결과를 목록으로 보여주기: Project MySelectShop - Prepare 에서 이미 구현 완료
ResponseDto
: Entity를 그대로 반환해도 무방하지만, Entity에 있는 컬럼 값들 뿐만 아니라, 그 이외의 값들도 보내줘야할 경우 ResponseDto를 만들어서 여기 안에 담아서 값을 보내주는 경우가 있음
//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();
}
}
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;
}
}
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;
}
package com.sparta.myselectshopbeta.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class ProductMypriceRequestDto {
private int myprice;
}
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();
}
}
초기 프로그래밍 방식
컴퓨터가 해야할 일들을 쭈~욱 순차적으로 나열해 놓는 코딩 방식
예시 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. 역할별로 코드 분리!!
3. 코드를 좀 더 읽기 편하게!!!