관리 메뉴

CASSIE'S BLOG

95-2강 스프링부트 Service Layer 살펴보기 본문

PROGRAMMING/슈퍼코딩 강의 정리

95-2강 스프링부트 Service Layer 살펴보기

ITSCASSIE1107 2024. 1. 26. 18:35

 

 

 

 

흐름 중요하다. service layer -> data access layer

트랜잭션을 어떻게 구현하냐면

Bean를 먼저 등록하고 (=나 이제 트랜잭션 할건데 인식해줘)

트랜잭션매니저라는 빈을 등록하고

이건 또 dataSource에 영향이 가요

데이터소스에 트랜잭션을 할거다 이런 식으로 구현하면 된다.

 

적용하는건 쉽다고함

 

 

 

그냥 쓸 메소드 밑에 정의하는데

위에다가 Transactional 어노테이션 하나만 붙이면 된다고함. -> 하나의 작업단계로 묶인다고함

 

전에꺼는 너무 간단해서 하나 더 추가한다고함.

 

 

 

mySQL 워크벤치에 먼저 들어감.

 

SQL추가

 

CREATE DATABASE chapter_96;
USE chapter_96;

CREATE TABLE store_sales(
id INT AUTO_INCREMENT PRIMARY KEY,
store_name VARCHAR(30),
    amount INT NOT NULL DEFAULT 0 check(amount >= 0)
);

CREATE TABLE item (
id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) NOT NULL UNIQUE,
    type VARCHAR(20) NOT NULL,
    price INT,
    store_id INT,
    stock INT NOT NULL DEFAULT 0 check(stock >= 0) ,
    cpu VARCHAR(30),
    capacity VARCHAR(30),
    
    FOREIGN KEY (store_id) REFERENCES store_sales(id)
);

 

 

SQL 추가2:

 

INSERT INTO store_sales (store_name, amount)
VALUES
    ('Store A', 10000),
    ('Store B', 15000),
    ('Store C', 20000),
    ('Store D', 12000),
    ('Store E', 18000);

INSERT INTO item (name, type, price, store_id, stock, cpu, capacity)
VALUES
    ('iPhone 12 Pro Max', '스마트폰', 1490000, 1, 100, 'A14 Bionic', '512GB'),
    ('Galaxy S21 Ultra', '스마트폰', 1690000, 2, 80, 'Exynos 2100', '256GB'),
    ('Pixel 6 Pro', '스마트폰', 1290000, 3, 120, 'Google Tensor', '128GB'),
    ('Dell XPS 15', '노트북', 2290000, 4, 50, 'Intel Core i9', '1TB SSD'),
    ('Sony Alpha 7 III', '미러리스 카메라', 2590000, 5, 60, 'BIONZ X', '내부 저장소 없음'),
    ('Xbox Series X', '게임 콘솔', 499000, 1, 30, 'Custom AMD Zen 2', '1TB SSD'),
    ('iPad Air', '태블릿', 849000, 2, 70, 'A14 Bionic', '64GB'),
    ('MacBook Pro', '노트북', 1790000, 3, 40, 'Apple M1 Max', '512GB'),
    ('Sony WH-1000XM4', '헤드폰', 349000, 4, 90, 'Sony HD Nois QN1', '내부 저장소 없음'),
    ('LG OLED CX', 'TV', 2290000, 5, 20, 'LG α9 Gen 3 AI 4K', '128GB');

 

 

 

매상에 구매된게 추가가되야함.

 

    FOREIGN KEY (store_id) REFERENCES store_sales(id) 

 

이게 키가 됨. 

 

스키마를 바꿨으면 MYSQL에서는 

jdbcConfig에서 url를 chapter96으로 바꿔야함.

 

before

package com.github.supercoding.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import javax.sql.DataSource;

@Configuration
public class JdbcConfig {

@Bean
public DataSource dataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUsername("root");
dataSource.setPassword("12341234");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); //이 스트링 값 넣어줘야 MySQL 드라이버 등록됨
dataSource.setUrl("jdbc:mysql://localhost:3306/chapter_95");
return dataSource;
}

@Bean
public JdbcTemplate jdbcTemplate(){return new JdbcTemplate(dataSource());};


}

 

after

package com.github.supercoding.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import javax.sql.DataSource;

@Configuration
public class JdbcConfig {

@Bean
public DataSource dataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUsername("root");
dataSource.setPassword("12341234");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); //이 스트링 값 넣어줘야 MySQL 드라이버 등록됨
dataSource.setUrl("jdbc:mysql://localhost:3306/chapter_96");
return dataSource;
}

@Bean
public JdbcTemplate jdbcTemplate(){return new JdbcTemplate(dataSource());};


}

 

chapter 95에서 96 item 부분이 달라서 로직을 바꿔야함.

 

ItemEntity 맞춰야함.

 

 

ItemEntity before

 

package com.github.supercoding.repository;

import java.util.Objects;

public class ItemEntity {
private Integer id;
private String name;
private String type;
private Integer price;
private String cpu;
private String capacity;

public ItemEntity(Integer id, String name, String type, Integer price, String cpu, String capacity) {
this.id = id;
this.name = name;
this.type = type;
this.price = price;
this.cpu = cpu;
this.capacity = capacity;
}

public Integer getId() {
return id;
}

public String getName() {
return name;
}

public String getType() {
return type;
}

public Integer getPrice() {
return price;
}

public String getCpu() {
return cpu;
}

public String getCapacity() {
return capacity;
}

public void setId(Integer id) {
this.id = id;
}

public void setName(String name) {
this.name = name;
}

public void setType(String type) {
this.type = type;
}

public void setPrice(Integer price) {
this.price = price;
}

public void setCpu(String cpu) {
this.cpu = cpu;
}

public void setCapacity(String capacity) {
this.capacity = capacity;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ItemEntity that = (ItemEntity) o;
return Objects.equals(id, that.id);
}

@Override
public int hashCode() {
return Objects.hash(id);
}


}

 

ItemEntity After

이걸하면 RowMapper 도 자동으로 업데이트 됨.

 

 

 

storeId, stock 추가하고, getter, setter 추가하고

위와 같이 생성자 호출 시 매개변수 2개를 생략하여 호출하면 됩니다. 생성자 내부에서는 매개변수의 디폴트 값을 사용하게 됩니다. 따라서 매개변수가 필요 없는 경우에는 이러한 방식을 사용하여 객체를 생성할 수 있습니다.

즉, 생성자의 매개변수 기본값을 통해 해당 매개변수를 생략할 수 있습니다. 이는 Java의 기본적인 기능 중 하나입니다.

 

기본값설정하고, 생성자에서 근데, 매개변수에 없다.

package com.github.supercoding.repository;

import java.util.Objects;

public class ItemEntity {
private Integer id;
private String name;
private String type;
private Integer price;

private Integer storeId;

private Integer stock;


private String cpu;
private String capacity;

public ItemEntity(Integer id, String name, String type, Integer price, String cpu, String capacity) {
this.id = id;
this.name = name;
this.type = type;
this.price = price;
this.storeId = null;
this.stock = 0;
this.cpu = cpu;
this.capacity = capacity;
}

public Integer getId() {
return id;
}

public String getName() {
return name;
}

public String getType() {
return type;
}

public Integer getPrice() {
return price;
}

public String getCpu() {
return cpu;
}

public String getCapacity() {
return capacity;
}

public void setId(Integer id) {
this.id = id;
}

public void setName(String name) {
this.name = name;
}

public void setType(String type) {
this.type = type;
}

public void setPrice(Integer price) {
this.price = price;
}

public Integer getStoreId() {
return storeId;
}

public void setStoreId(Integer storeId) {
this.storeId = storeId;
}

public Integer getStock() {
return stock;
}

public void setStock(Integer stock) {
this.stock = stock;
}

public void setCpu(String cpu) {
this.cpu = cpu;
}

public void setCapacity(String capacity) {
this.capacity = capacity;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ItemEntity that = (ItemEntity) o;
return Objects.equals(id, that.id);
}

@Override
public int hashCode() {
return Objects.hash(id);
}


}

 

repository폴더내에 store_sales와 관련된 거 추가해야함.

 

 

지금 이 부분 path나 query로 들어오는것이 아니라 requestBody로 들어온다.

 

지금 이 부분 path나 query로 들어오는것이 아니라 requestBody로 들어온다. 이게 무슨 뜻이야?

api 명세표는 
HTTP 요청
POST http://localhost:8080/v3/apii/items/buy
Content-Type: application/json 

{
 "itemId": 22,
 "itemNums": 4
}

 

"requestBody"는 HTTP 요청에서 데이터를 전송하는 데 사용되는 부분을 가리킵니다. 일반적으로 클라이언트가 서버에 데이터를 보내는 경우에 해당합니다.

API 명세표에서 "requestBody"로 나타난 부분은 클라이언트가 서버에게 전달할 데이터를 정의하는 부분입니다. 이 경우에는 JSON 형식으로 데이터가 전송되며, "itemId"와 "itemNums" 두 가지 데이터가 서버에 전송됩니다. 요청의 목적은 아이템을 구매하는 것으로 추정됩니다.

"requestBody"를 사용하여 데이터를 전송하는 경우에는 보통 HTTP POST 메서드와 함께 사용됩니다. 클라이언트는 요청 본문에 데이터를 담아 서버에게 보내고, 서버는 이 데이터를 사용하여 요청을 처리합니다.

따라서 "requestBody"로 데이터를 전송하는 것은 경로나 쿼리 매개변수를 사용하여 데이터를 전달하는 것과는 다른 방식으로 데이터를 전송하는 것을 의미합니다.

 

 

DTO: 

 

DTO는 Data Transfer Object의 약자로, 데이터 전송을 위해 사용되는 객체를 가리킵니다. 주로 데이터베이스나 외부 시스템과의 데이터 교환에 사용됩니다. DTO는 데이터를 담고 있는 클래스로서, 일반적으로 데이터의 속성을 나타내는 필드와 해당 필드에 접근할 수 있는 getter 및 setter 메서드로 구성됩니다.

예를 들어, 위의 코드에서 BuyOrder는 BuyOrder에 관련된 데이터를 담는 DTO 클래스일 것입니다. 메서드 buyItem()은 HTTP 요청으로부터 받은 JSON 형식의 데이터를 BuyOrder 객체로 변환하여 처리할 것입니다.

따라서 DTO를 만들어줘야 하는 이유는 다음과 같습니다:

데이터 전송 및 구조화: DTO는 데이터를 전송하기 쉽고 구조화된 형태로 만들어줍니다. HTTP 요청으로부터 받은 데이터를 DTO 객체로 변환하면, 데이터를 쉽게 다룰 수 있습니다.

의미 부여: DTO는 해당 데이터의 의미를 명확히 전달할 수 있습니다. 예를 들어, BuyOrder DTO는 구매 주문과 관련된 데이터를 담고 있으므로, 해당 DTO를 사용하면 구매 주문과 관련된 데이터임을 명확히 알 수 있습니다.

코드 유지보수성 향상: DTO를 사용하면 코드의 유지보수성이 향상됩니다. 데이터의 구조가 변경되더라도 DTO만 수정하면 되므로, 관련된 코드에 영향을 덜 줄 수 있습니다.

따라서 위의 코드에서 BuyOrder DTO를 만들어서 사용하는 것은 HTTP 요청으로부터 받은 데이터를 구조화하고 처리하기 위한 목적으로 필요합니다.

 

package com.github.supercoding.web.dto;

public class BuyOrder {
}

 

이 부분은 밑에 사진 json이랑 1:1 매핑이 되야한다.

 

 

 

DTO 클래스에 빈 생성자를 추가하는 이유는 다음과 같습니다:

  1. 기본 생성자의 필요성: 대부분의 자바 프레임워크는 기본 생성자를 요구합니다. 예를 들어, JSON 형식의 데이터를 객체로 변환할 때 Jackson 라이브러리를 사용하는 경우, 기본 생성자가 필요합니다. 또한, 프레임워크나 라이브러리에서 리플렉션을 사용하여 객체를 생성할 때도 기본 생성자가 필요합니다.

빈 생성자 빼먹지 말 것

package com.github.supercoding.web.dto;

public class BuyOrder {

private Integer itemId;
private Integer itemNums;


public BuyOrder(Integer itemId, Integer itemNums) {
this.itemId = itemId;
this.itemNums = itemNums;
}

public Integer getItemId() {
return itemId;
}

public void setItemId(Integer itemId) {
this.itemId = itemId;
}

public Integer getItemNums() {
return itemNums;
}

public void setItemNums(Integer itemNums) {
this.itemNums = itemNums;
}
}

 

이게 아니라 빈생성자 만들어주기 

rowMapper 만들 때 이거를 체크해줘야함

find하려면 RowMapper 필요

 

 

 

 

sql 문법문제 많이 나오는 에러래. 

 

 

뒤에 에러 예외 던지는거 사례 있는데 힘없어서 넘어감.. 나중에 할 것

 

 

Repository 패턴을 사용하여 데이터 액세스 계층을 구현하고 있습니다. Repository 패턴은 데이터 액세스 로직을 비즈니스 로직과 분리하여 유지보수성을 향상시키고 코드를 더 견고하게 만드는 데 도움이 됩니다.

여기서 인터페이스와 구현 클래스를 사용하는 이유는 다음과 같습니다:

  1. 추상화: 인터페이스를 통해 구현 클래스를 참조하는 것은 코드를 더 추상화할 수 있습니다. 즉, 코드가 클래스의 구체적인 구현에 의존하지 않고 인터페이스에만 의존하게 됩니다. 이는 유연성을 높여주고 향후 변경이 발생할 때 영향을 최소화하는 데 도움이 됩니다.
  2. 확장성: 인터페이스를 사용하면 여러 가지 구현을 만들어 사용할 수 있습니다. 예를 들어, 데이터베이스 액세스에는 JDBC, JPA, MyBatis 등 다양한 기술이 있을 수 있습니다. 인터페이스를 사용하여 구현을 감추면, 향후에 다른 데이터 액세스 기술로 쉽게 전환할 수 있습니다.
  3. 테스트 용이성: 인터페이스를 사용하면 테스트하기 쉽습니다. Mockito와 같은 목 라이브러리를 사용하여 인터페이스를 구현하는 가짜(Mock) 객체를 만들어 테스트할 수 있습니다.
  4. 의존성 역전(Dependency Inversion) 원칙: 인터페이스와 구현 클래스를 사용하는 것은 의존성 역전 원칙을 따르는 것입니다. 즉, 고수준 모듈(비즈니스 로직)은 저수준 모듈(데이터 액세스)에 의존해서는 안 되며, 둘 모두 추상화(인터페이스)에 의존해야 합니다.

따라서 Repository 인터페이스와 그것을 구현하는 클래스를 사용하여 데이터 액세스 계층을 설계하는 것은 코드의 유지보수성을 향상시키고, 확장성을 확보하는 데 도움이 됩니다.

 

ElectronicStoreItemJdbcDao JDBC를 사용하여 전자 상점 항목을 처리하는 DAO라는 것을 명확히 알려줍니다.

 

ElectronicStoreItemRepository는 전자 상점 항목과 관련된 데이터 액세스 작업을 수행하는 인터페이스를 나타냅니다. "Repository"라는 용어는 데이터 액세스 객체의 역할을 명확히 나타내며, 이 인터페이스가 전자 상점 항목과 관련된 CRUD(Create, Read, Update, Delete) 작업을 수행한다는 것을 암시합니다.

전형적으로 Spring Framework에서는 Repository 인터페이스를 사용하여 데이터 액세스 계층을 추상화하고 비즈니스 로직과의 결합도를 줄입니다. 이 인터페이스를 구현하는 클래스는 실제 데이터베이스와의 상호작용을 담당하며, Spring Data와 같은 기술을 사용하여 Repository 인터페이스의 구현을 자동화할 수 있습니다.

따라서 ElectronicStoreItemRepository는 전자 상점 항목과 관련된 데이터 액세스 작업을 추상화한 인터페이스라는 것을 나타내며, 해당 인터페이스를 구현하는 클래스는 실제 데이터 액세스 로직을 담당합니다.

 

RowMapper:

 

RowMapper 인터페이스는 JDBC 결과 세트(ResultSet)에서 데이터를 추출하여 객체로 매핑하는 역할을 합니다.

 

여기서 RowMapper를 구현한 익명 내부 클래스가 있는데, 이 익명 내부 클래스는 RowMapper의 mapRow() 메서드를 오버라이드하여 ResultSet에서 데이터를 추출하고 이를 객체로 매핑합니다.

 

mapRow() 메서드는 ResultSet의 각 행(row)에 대해 호출됩니다. 이 메서드는 ResultSet의 컬럼(column)을 사용하여 매핑하려는 객체의 속성에 값을 설정합니다. ResultSet에서 값을 추출할 때는 ResultSet의 getXXX() 메서드를 사용하여 해당 컬럼의 데이터를 가져옵니다.

 

여기서 rs.getInt("id"), rs.getNString("name") 등의 코드는 ResultSet에서 컬럼 이름을 사용하여 해당 컬럼의 값을 가져오는 것입니다. 즉, ResultSet에서 id, name, type, price 등의 컬럼을 순회하면서 해당 컬럼의 값을 가져와서 ItemEntity 객체의 속성에 설정합니다.

 

그리고 이렇게 매핑된 객체는 최종적으로 리스트(List)나 단일 객체로 반환됩니다.

 

따라서 RowMapper를 사용하면 ResultSet에서 데이터를 추출하여 객체로 매핑할 수 있으며, 이를 통해 데이터베이스에서 가져온 데이터를 자바 객체로 변환하는 작업을 수행할 수 있습니다.

 

 

반응형