팀 회고

 

 

 

추가로 학습해볼 내용

1. SQLDelete 어노테이션과 SQLRestriction 어노테이션 

 findByOOOAndIsDeletedFalse 같은 느낌으로 soft delete를 고려한 검색이나 조회를 하고 계신 것 같습니다. 대부분의 API에서 삭제된 데이터는 조회하려 하지 않으므로, 일괄적으로 이를 적용할 수 있는 방법 등을 고려해보시면 좋을 것 같습니다. 예시로 SQLDelete 어노테이션과 SQLRestriction 어노테이션 등이 있습니다.
(→ deleteOOO 메소드에서도 isDeleted를 한번 더 체크할 필요 없이 not found로 끝나게 되겠죠? )
https://velog.io/@max9106/JPA-soft-delete

 

[JPA] soft delete 자동으로 처리하기

데이터를 삭제하는 방법에는 hard delete, soft delete 2가지 종류가 있습니다. hard delete는 delete 쿼리를 날려서 데이터베이스에서 실제로 삭제하는 방법을 말합니다.soft delete는 실제로 데이터베이스에

velog.io

 

 

 

2. DTO의 함수 중 of, from 메소드를 만들어서 DTO를 생성하는 전략

  • of 메소드: 보통 여러 인자를 받아 DTO를 생성하는 경우에 사용합니다. 주로 DTO의 필드에 필요한 여러 매개변수를 전달받아 객체를 생성합니다.
  • from 메소드: 일반적으로 엔티티(Entity)나 다른 객체를 인자로 받아 해당 객체를 기반으로 DTO를 생성할 때 사용합니다.

두 메서드 모두 보통 DTO 클래스 내부에 정적 메소드(static method)로 작성됩니다.

public class UserDto {
    private Long id;
    private String username;
    private String email;
    private String role;

    // 기본 생성자, 게터, 세터 생략

    // of 메소드: 여러 매개변수를 받아 DTO를 생성
    public static UserDto of(Long id, String username, String email, String role) {
        UserDto dto = new UserDto();
        dto.id = id;
        dto.username = username;
        dto.email = email;
        dto.role = role;
        return dto;
    }

    // from 메소드: 엔티티를 받아 DTO를 생성
    public static UserDto from(UserEntity entity) {
        UserDto dto = new UserDto();
        dto.id = entity.getId();
        dto.username = entity.getUsername();
        dto.email = entity.getEmail();
        dto.role = entity.getRole();
        return dto;
    }
}

 

 

 

 

3. exception을 Exception Handler로 일괄적으로 처리하는 법

'스프링 심화 TIL' 카테고리의 다른 글

삭제된 유저 필터링 방법  (0) 2024.08.26
S.A 튜터님 피드백  (0) 2024.08.23
배달앱 프로젝트 11조 S.A  (0) 2024.08.22
DB 복제 지연  (0) 2024.08.21
Docker 네트워킹 기본 개념  (0) 2024.08.18

1. 리포지토리에서 필터링

public interface UserRepository extends JpaRepository<User, Long> {
    // 삭제되지 않은 유저만 조회
    List<User> findAllByIsDeletedFalse();
    
    // 또는 @Query를 사용할 수 있습니다.
    @Query("SELECT u FROM User u WHERE u.isDeleted = false")
    List<User> findActiveUsers();
}

 

리포지토리에서 필터링하는 방법은 데이터베이스 쿼리 단계에서 필요한 데이터만을 가져오는 방식입니다.

장점:

  • 성능: 필터링이 데이터베이스 쿼리에서 이루어지므로, 애플리케이션에 불필요한 데이터를 가져오지 않습니다. 이는 특히 대량의 데이터를 다룰 때 성능상 이점이 있습니다.
  • 간결함: 서비스 레이어에서 별도로 필터링 로직을 구현할 필요가 없으므로 코드가 간결해집니다.
  • 트래픽 감소: 불필요한 데이터를 애플리케이션으로 가져오지 않기 때문에 네트워크 트래픽이 줄어듭니다.

단점:

  • 복잡한 쿼리: 필터링 로직이 복잡해질 경우, JPA 쿼리 메서드나 JPQL이 복잡해지고 관리하기 어려워질 수 있습니다.
  • 유연성 부족: 리포지토리에서 필터링 조건을 변경하려면 리포지토리 메서드를 수정해야 하므로, 필터링 로직이 자주 바뀔 경우 관리가 번거로울 수 있습니다.

 

 

 

2. 서비스 레이어에서 필터링

@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;

    public List<UserInfoDto> getAllUserInfos() {
        // 전체 유저를 조회한 후 필터링
        return userRepository.findAll().stream()
                .filter(user -> !user.isDeleted())
                .map(this::convertToUserInfoDto)
                .collect(Collectors.toList());
    }

    private UserInfoDto convertToUserInfoDto(User user) {
        // 유저 엔티티를 UserInfoDto로 변환하는 로직
        return new UserInfoDto(user.getId(), user.getUsername(), user.getRole());
    }
}

 

서비스 레이어에서 필터링하는 방법은 모든 데이터를 애플리케이션으로 가져온 뒤, 메모리 내에서 필요한 데이터만 추려내는 방식입니다.

장점:

  • 유연성: 필터링 로직을 쉽게 변경할 수 있습니다. 특정 조건에 따라 필터링을 동적으로 적용하기 쉽습니다.
  • 간단한 리포지토리: 리포지토리의 쿼리가 단순해지고, 재사용성이 높아집니다. 복잡한 조건이 필요 없을 때 유리합니다.

단점:

  • 성능 저하: 데이터베이스에서 불필요한 데이터를 모두 가져오기 때문에, 데이터 양이 많을 경우 성능에 악영향을 줄 수 있습니다.
  • 메모리 사용량 증가: 모든 데이터를 애플리케이션 메모리로 가져오기 때문에 메모리 사용량이 늘어납니다.
  • 네트워크 트래픽 증가: 데이터베이스에서 가져오는 데이터의 양이 많아지면, 네트워크 트래픽이 증가할 수 있습니다.

 

어떤 방법이 더 효율적인가?

  • 리포지토리에서 필터링: 대규모 데이터를 다룰 때나 성능이 중요한 경우 이 방법이 더 효율적입니다. 데이터베이스 레벨에서 불필요한 데이터를 미리 걸러내기 때문에 성능상의 이점이 큽니다.
  • 서비스 레이어에서 필터링: 필터링 로직이 자주 바뀌거나, 복잡한 조건을 동적으로 처리해야 할 경우 유리합니다. 그러나 데이터 양이 많지 않은 경우에 사용하는 것이 좋습니다.

 

결론

일반적으로 성능과 효율성을 고려한다면 리포지토리에서 필터링하는 방법이 더 나은 선택입니다. 그러나 유연성을 요구하거나 특정 비즈니스 로직에 따라 필터링이 자주 바뀌는 경우에는 서비스 레이어에서 필터링하는 방법이 더 적합할 수 있습니다. 선택은 프로젝트의 요구 사항과 데이터의 양에 따라 결정하는 것이 좋습니다.

'스프링 심화 TIL' 카테고리의 다른 글

배달앱 프로젝트 회고 + 튜터님 피드백 정리  (0) 2024.09.04
S.A 튜터님 피드백  (0) 2024.08.23
배달앱 프로젝트 11조 S.A  (0) 2024.08.22
DB 복제 지연  (0) 2024.08.21
Docker 네트워킹 기본 개념  (0) 2024.08.18

명세서 API

삭제 메서드 delete나 patch 둘 중 하나로 통일(실제 삭제가 아니라 숨김처리를 할지라도 직관적으로 알아보기 쉽게 delete로 하는 경우도 있다고 함)

패스 복수형(orders, products, menus..)으로 통일

서치 엔드 포인트 추가

비공개처리 privacy/{id} -> {id}/privacy로 순서 수정

리퀘스트 헤더랑 리스폰스 작성할 것

 

ERD

프로덕트랑 중복되므로 메뉴 테이블 없애기

스토어 - 프로덕트 일대다 연관관계 맺기

리뷰는 프로덕트가 아닌 오더랑 일대일 연관관계로 수정

결제 - 오더 연관관계 맺기

오더에 오더 상태 필드 추가(주문 진행 중, 완료, 취소)

유저에 manager role 추가

카테고리 필드 프로덕트가 아닌 스토어에 있어야 됨

 

 

 

 

 

 

 

 

API 명세서

https://pumped-judo-47a.notion.site/API-90a43fd7b3d04c688949f05d22c36f9f

 

 

ERD

https://drive.google.com/file/d/13mN18dVUZOhYPG2_EqOONwqJUSi3_Myu/view?usp=sharing

 

delivery.drawio

 

drive.google.com

 

 

테이블 명세서

사용자 테이블 (p_users)

필드 이름 데이터 타입 설명
id INT ID, Primary Key
username VARCHAR(255) 사용자 이름
password VARCHAR(255) 사용자 비밀번호
role role_type 사용자 역할 (CUSTOMER, OWNER, MANAGER, MASTER)
is_public BOOLEAN 사용자 정보가 공개된 상태인지 여부, 기본값 TRUE
created_at TIMESTAMP 레코드 생성 시간
created_by VARCHAR(100) 레코드 생성자 (username)
updated_at TIMESTAMP 레코드 수정 시간
updated_by VARCHAR(100) 레코드 수정자 (username)
deleted_at TIMESTAMP 레코드 삭제 시간
deleted_by VARCHAR(100) 레코드 삭제자 (username)

 

 

배송지 테이블 (p_address)

필드 이름 데이터 타입 설명
id UUID 배송지 ID, Primary Key
addressname VARCHAR(100) 배송지 이름
addressLine1 VARCHAR(255) 기본 주소
addressLine1 VARCHAR(255) 건물명, 동/호수 등의 상세주소
user_id INT 유저 식별자 (외래 키)
created_at TIMESTAMP 레코드 생성 시간
created_by VARCHAR(100) 레코드 생성자 (username)
updated_at TIMESTAMP 레코드 수정 시간
updated_by VARCHAR(100) 레코드 수정자 (username)
deleted_at TIMESTAMP 레코드 삭제 시간
deleted_by VARCHAR(100) 레코드 삭제자 (username)

 

 

주문 테이블 (p_orders)

필드 이름  데이터 타입  설명
id UUID ID, Primary Key
user_id INT 주문자 ID, 외래키
store_id INT 주문 상점 ID, 외래키
total_amount INT 총 주문 가격
type OrderTypeEnum 주문 종류 DELIVERY(배달 주문), PICKUP (포장 주문)
status OrderStatusEnum 주문 상태 PENDING(진행중), CANCELED(취소), COMPLETED(완료)
request VARCHAR(100) 요청사항
created_at TIMESTAMP 레코드 생성 시간
created_by VARCHAR(100) 레코드 생성자 (username)
updated_at TIMESTAMP 레코드 수정 시간
updated_by VARCHAR(100) 레코드 수정자 (username)
deleted_at TIMESTAMP 레코드 삭제 시간
deleted_by VARCHAR(100) 레코드 삭제자 (username)

 

주문상품 테이블 (p_order_products)

필드 이름  데이터 타입  설명
id UUID 주문 상품 ID, Primary Key
order_id UUID 주문 식별자, 외래키
product_id UUID 상품 식별자, 외래키
user_id role_type 사용자 역할 (CUSTOMER, OWNER, MASTER)
quantity INT 상품 수량
created_at TIMESTAMP 레코드 생성 시간
created_by VARCHAR(100) 레코드 생성자 (username)
updated_at TIMESTAMP 레코드 수정 시간
updated_by VARCHAR(100) 레코드 수정자 (username)
deleted_at TIMESTAMP 레코드 삭제 시간
deleted_by VARCHAR(100) 레코드 삭제자 (username)

 

p_store

필드명  데이터 타입  설명
shop_id INT 상점의 고유 ID
shop_name VARCHAR(30) 상점 이름
shop_address VARCHAR(100) 상점 주소
shop_type VARCHAR(30) 상점 유형
shop_open_time TIME 상점 오픈 시간
shop_close_time TIME 상점 마감 시간
shop_phone VARCHAR(15) 상점 전화번호

 

 

p_menu

필드명 
데이터 타입  설명
menu_id INT 메뉴의 고유 ID
menu_name VARCHAR(30) 메뉴 이름
menu_description VARCHAR(100) 메뉴 설명
menu_price DECIMAL(10, 2) 메뉴 가격
public_sts CHAR(1) 공개 상태
db_sts CHAR(1) 데이터베이스 상태

 

 

p_store_menu

필드명  데이터 타입  설명
id INT 고유 ID
shop_id INT p_store 테이블의 shop_id 참조
menu_id INT p_menu 테이블의 menu_id 참조

 

p_review

필드명 데이터 타입 설명
review_id INT 리뷰의 고유 ID
review_content VARCHAR(150) 리뷰 내용
review_rating DECIMAL(2, 1) 리뷰 평점
menu_id INT p_menu 테이블의 menu_id 참조
user_id VARCHAR(30) 사용자 ID
public_sts CHAR(1) 공개 상태
db_sts CHAR(1) 데이터베이스 상태

 

 

p_payment

필드명 데이터 타입 설명
payment_id UUID 결제 ID
order_id UUID 주문 ID
is_deleted BOOLEAN 삭제 상태(데이터를 보존해야 하므로BOOLEAN 값 처리)
amount INT 주문 가격 총액
transaction DATETIME 결제 시각
status STRING (예: approved, declined, pending)
pg_response_code STRING PG사에서 반환한 응답 코드
pg_transaction_id STRING PG사에서 반환한 거래 ID

 

 

p_products

필드명 데이터 타입 설명
product_id UUID 상품 ID
product_name VARCHAR(30) 상품 이름
description VARCHAR(30) 상품 설명
price INT 상품 가격
category_id ENUM 상품 카테고리
is_deleted BOOLEAN 삭제 상태(데이터를 보존해야 하므로BOOLEAN 값 처리)
is_public BOOLEAN 가게 사장님이 상품을 숨김처리 했는지 나타냄(ex - 품절 상태 )

 

 

p_ai

필드명 데이터 타입 설명
request_id UUID ai 요청 ID
shop_id UUID 요청한 가게 ID
request_data VARCHAR(150) ai api 요청, 상품 설명 작성 요청
response_data VARCHAR(150) ai로 부터 상품 설명 작성 받음
request_date TIMESTAMP 요청 시각
response_date TIMESTAMP ai 설명 응답 시각
is_deleted BOOLEAN 삭제 상태(데이터를 보존해야 하므로BOOLEAN 값 처리)

 

 

인프라 설계도

 

'스프링 심화 TIL' 카테고리의 다른 글

삭제된 유저 필터링 방법  (0) 2024.08.26
S.A 튜터님 피드백  (0) 2024.08.23
DB 복제 지연  (0) 2024.08.21
Docker 네트워킹 기본 개념  (0) 2024.08.18
Docker 명령어  (0) 2024.08.16

복제 지연이란?

  • 복제 지연은 데이터가 쓰기 DB에서 읽기 DB로 복제되는 과정에서 발생하는 시간 지연을 의미합니다. 이 용어는 쓰기 작업이 발생한 후 그 변경 사항이 읽기 DB에 반영되기까지의 지연 시간을 설명하는 데 사용됩니다.
  • 복제 지연으로 인해, 읽기 DB에서 최신 상태의 데이터를 읽지 못하고 이전 상태의 데이터를 읽게 되는 문제가 발생할 수 있습니다.
    • 쓰기 DB: 모든 쓰기 작업(데이터 삽입, 업데이트, 삭제 등)을 처리하는 데이터베이스입니다. 보통 마스터(master) 데이터베이스라고도 합니다.
    • 읽기 DB: 읽기 작업만 처리하는 데이터베이스로, 보통 슬레이브(slave) 데이터베이스 또는 리플리카(replica)라고 합니다. 이 데이터베이스는 쓰기 DB에서 복제(replication)된 데이터를 사용합니다.

해결 방법

캐시 시스템을 도입하여, 쓰기 작업 후 바로 캐시를 갱신하고, 그 이후의 읽기 작업은 캐시에서 제공하는 방식을 사용합니다. 캐시는 일관성을 보장하기 위해 일정 시간 동안(예: 1초) 쓰기 DB의 데이터를 캐시하는 방법을 사용할 수 있습니다.

 

예시) 스프링

    @Cacheable(value = "products", key = "#id")
    public Product createProduct(Long id) {
        simulateSlowService();
        return new Product(id, "Product " + id, 100.0);
    }
    @CachePut(value = "products", key = "#product.id")
    public Product updateProduct(Product product) {
        // 실제 데이터베이스 업데이트 로직이 있다고 가정
        return product;
    }

 

 

예시) 장고

from django.core.cache import cache
from django.shortcuts import render
from django.http import JsonResponse

def get_product(id):
    # 가정: 데이터베이스에서 제품 정보를 가져옴
    return {"id": id, "name": f"Product {id}"}

def product_view(request, id):
    # 캐시에서 제품을 가져오려 시도
    product = cache.get(f'product_{id}')
    
    if not product:
        # 캐시에 없으면, 데이터베이스에서 가져오고 캐시에 저장
        product = get_product(id)
        cache.set(f'product_{id}', product, timeout=60)  # 60초 동안 캐시
        
    return JsonResponse(product)

def update_product_view(request, id):
    # 가정: POST 요청으로 이름 업데이트
    name = request.POST.get('name')
    
    # 데이터베이스 업데이트 로직 여기에 포함
    
    # 새로운 값으로 캐시 갱신
    updated_product = {"id": id, "name": name}
    cache.set(f'product_{id}', updated_product, timeout=60)
    
    return JsonResponse(updated_product)

'스프링 심화 TIL' 카테고리의 다른 글

S.A 튜터님 피드백  (0) 2024.08.23
배달앱 프로젝트 11조 S.A  (0) 2024.08.22
Docker 네트워킹 기본 개념  (0) 2024.08.18
Docker 명령어  (0) 2024.08.16
캐싱 전략  (0) 2024.08.13
  • 커스텀 브리지 네트워크(Custom Bridge Network): Docker에서 컨테이너 간의 통신을 위해 커스텀 네트워크(예: 브리지 네트워크)를 생성하는 것이 일반적입니다. 이 네트워크에 연결된 컨테이너들은 서로의 이름을 사용해 통신할 수 있습니다.
  • 컨테이너 이름(Hostname): Docker는 각 컨테이너에 이름을 부여하며, 네트워크 내에서 이 이름은 DNS 호스트네임처럼 동작합니다. 즉, 같은 네트워크에 있는 컨테이너들은 서로를 이름으로 접근할 수 있습니다.
spring.application.name=service-a

server.port=8080


## localhost대신 도커 컨테이너 이름으로 호출
service.b.url=http://service-b:8080

'스프링 심화 TIL' 카테고리의 다른 글

배달앱 프로젝트 11조 S.A  (0) 2024.08.22
DB 복제 지연  (0) 2024.08.21
Docker 명령어  (0) 2024.08.16
캐싱 전략  (0) 2024.08.13
Could not write JSON: failed to lazily initialize a collection of role  (0) 2024.08.11

이미지 관련 명령어

 

1. 이미지 빌드(생성)

docker build -t [이미지 이름]:[태그] .

-t : 도커 이미지에 이름과 태그 설정

태그는 선택 사항이며, 기본적으로 latest로 설정됨

. : Docker가 현재 디렉토리에서 Dockerfile을 찾고, 그 파일을 기준으로 이미지를 빌드하도록 지시한다. 만약 Dockerfile이 다른 디렉토리에 있다면, . 대신 해당 디렉토리의 경로를 지정해야 됨

※ Dockerfile은 이미지 빌드를 위한 지침을 정의하는 파일로, 어떤 소프트웨어를 설치하고, 어떤 설정을 해야 하는지 등을 지정한다.

예)

FROM openjdk:17-jdk-slim

VOLUME /tmp

ARG JAR_FILE=build/libs/*.jar

COPY ${JAR_FILE} app.jar

ENTRYPOINT ["java","-jar","/app.jar"]

 

2. 이미지 가져오기

docker pull postgres

도커 허브에서 해당 이미지를 가져옴

 

 

3. 이미지 목록 보기

docker images

 

 

4. 이미지 삭제

docker rmi myapp:latest

 

 

 

컨테이너 관련 명령어

 

1. 컨테이너 생성 & 실행

docker run [옵션] 이미지

 

예)

docker run -d -p 8080:80 myapp:latest

옵션

-d : 컨테이너를 백그라운드에서 실행한다. 이 옵션을 사용하면 터미널이 컨테이너의 출력으로부터 분리됨

-p : 호스트의 포트를 컨테이너의 포트에 매핑한다. 호스트포트:컨테이너포트 예시에서는 호스트의 8080 포트를 컨테이너의 80 포트에 매핑

 

이미 존재하는 컨테이너 실행

docker start 컨테이너 이름 또는 ID

 

실행중인 컨테이너 중지

docker stop 컨테이너 이름 또는 ID

 

 

2. 컨테이너 내부 접속

docker exec -it 컨테이너_아이디 /bin/bash

 

 

3. 컨테이너 목록 보기

docker ps

옵션

-a : 실행 중이거나 중지된 모든 컨테이너 보기

-al : 가장 최근에 생성된 컨테이너 보기

 

 

 

 

 

 

  • 만약 맥에서 실행시 Permission denied 관련 에러가 난다면 다음의 내용을 참고하세요
    • Rancher desktop 을 사용할 경우
      • Rancher desktop 윈도우에서 설정을 들어갑니다.
      • Virtual Machine > Volumes 로 들어가 Mount Type을 9p로 변경합니다.
      • Security Model을 Mapped-xattr 로 변경합니다.
    • Docker desktop의 경우
      • Docker desktop 윈도우에서 설정에 들어갑니다.
      • General 탭에서 virtoFS을 oxsfs(Legacy)로 수정합니다.

 

    @CachePut(cacheNames = "itemCache", key = "args[0]")
    @CacheEvict(cacheNames = "itemAllCache", allEntries = true)
    public ItemDto update(Long id, ItemDto dto) {
        Item item = itemRepository.findById(id)
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
        item.setName(dto.getName());
        item.setDescription(dto.getDescription());
        item.setPrice(dto.getPrice());
        return ItemDto.fromEntity(itemRepository.save(item));
    }

 

@CachePut : 메서드가 실행될 때마다 결과를 itemCache 캐시에 저장

@Cacheable과의 차이점 : @Cacheable은 캐시에 데이터가 없는 경우에만 메서드를 실행하고 캐시에 결과를 저장하는데(캐시 어사이드 방식) @CachePut은 메서드가 실행되면 결과가 항상 캐시에 저장된다.(캐시 프런트 방식)

 

@CacheEvict : update() 메서드가 실행될 때 마다 itemAllCache를 비워준다.

+ Recent posts