군만두의 IT 공부 일지

[3주차] 내일배움캠프 Spring Java - AI 활용 비즈니스 프로젝트 DAY5 - Store 엔티티&서비스 코드 수정하기 본문

개발일지/스파르타코딩클럽

[3주차] 내일배움캠프 Spring Java - AI 활용 비즈니스 프로젝트 DAY5 - Store 엔티티&서비스 코드 수정하기

mandus 2025. 2. 18. 14:28

 

오늘은 Store의 CRUD 기능을 구현한 내용에 대해서 Store, StoreRepository, StoreService, StoreController 클래스 코드를 팀원에게 코드 리뷰를 받았다. 코드 리뷰의 목적은 서로 코드를 어떻게 구현했는지 확인하고, 개선할 부분을 조언해주기 위함이다.

Store 엔티티의 변경 사항

  • 주소 정보 임베딩: 공통적으로 사용되는 Address 클래스를 임베드 타입으로 적용하여, 관리의 일관성을 높였다.
  • 유효성 검증 로직 강화: 필드별로 유효성 검증 로직을 추가하여 데이터의 정확성을 보장했다. 특히 수정 작업 시 null 값이 입력되지 않도록 했다.
  • Setter 메소드 제거: 데이터 무결성을 위해 @Setter 애너테이션의 사용하지 않고, 필요한 값 변경은 생성자와 메소드를 통해 제어하도록 개선했다.
  • 테이블 명명 규칙: p_store로 테이블명을 변경하여, 프로젝트의 요구사항을 충족했다.
  • id 필드 UUID 적용: 고유성 보장을 위해 id 필드의 타입을 UUID로 변경했다.
@Entity
@Getter
@NoArgsConstructor
@Table(name = "p_store")
public class Store extends BaseEntity {

    @Id
    @GeneratedValue
    @Column(length = 255, nullable = false)
    private UUID StoreId;

    @Column(length = 255, nullable = false)
    private String categoryId;

    @Column(length = 255, nullable = false)
    private String name;

    @Column(length = 255, nullable = false)
    private String pictureUrl;

    @Column(length = 255, nullable = false)
    private String phone;

    @Column(length = 255, nullable = false)
    private String operatingHours;

    @Column(length = 255, nullable = true)
    private String closedDays;

    @Column(nullable = false)
    private int rating;

    @Column(nullable = false)
    private int reviewCount;

    @Column(length = 255, nullable = false)
    private String status;

    @Column(length = 255, nullable = false)
    private String deliveryType;

    @Column(length = 255, nullable = false)
    private String deliveryArea;

    @Column(nullable = true)
    private Integer minimumOrderPrice;

    @Column(nullable = true)
    private Integer deliveryTip;

    @Embedded
    private Address address;

    @Builder
    public Store(String categoryId, String name, String pictureUrl, String phone, String operatingHours, String closedDays, int rating, int reviewCount, String status, String deliveryType, String deliveryArea, Integer minimumOrderPrice, Integer deliveryTip, Address address) {
        this.categoryId = categoryId;
        this.name = name;
        this.pictureUrl = pictureUrl;
        this.phone = phone;
        this.operatingHours = operatingHours;
        this.closedDays = closedDays;
        this.rating = rating;
        this.reviewCount = reviewCount;
        this.status = status;
        this.deliveryType = deliveryType;
        this.deliveryArea = deliveryArea;
        this.minimumOrderPrice = minimumOrderPrice;
        this.deliveryTip = deliveryTip;
        this.address = address;
    }

    public void updateStoreDetails(String name, String phone, String operatingHours, String closedDays, String pictureUrl, String categoryId, String status, String deliveryType, String deliveryArea, Integer minimumOrderPrice, Integer deliveryTip, Address address) {
        if (name != null) this.name = name;
        if (phone != null) this.phone = phone;
        if (operatingHours != null) this.operatingHours = operatingHours;
        if (closedDays != null) this.closedDays = closedDays;
        if (pictureUrl != null) this.pictureUrl = pictureUrl;
        if (categoryId != null) this.categoryId = categoryId;
        if (status != null) this.status = status;
        if (deliveryType != null) this.deliveryType = deliveryType;
        if (deliveryArea != null) this.deliveryArea = deliveryArea;
        if (minimumOrderPrice != null) this.minimumOrderPrice = minimumOrderPrice;
        if (deliveryTip != null) this.deliveryTip = deliveryTip;
        if (address != null) this.address = address;
    }
}

StoreService의 로직 개선

  • 소프트 삭제 구현: 삭제된 데이터의 추적 및 참조를 가능하게 하는 소프트 삭제 로직을 추가했다. BaseEntity에 이미 구현된 삭제 메소드를 활용했다.
  • 더티 체킹 자동화: JPA의 더티 체킹 기능을 활용하여, 데이터가 변경될 때 자동으로 감지하고 업데이트 하도록 구현했다.
  • 빌더 패턴 적용: 객체의 안정적인 생성을 위해 Builder 패턴을 적용하였고, set 메소드를 사용하지 않게 수정했다.
소프트 삭제 (Soft Delete)
데이터를 데이터베이스에서 실제로 삭제하지 않고, 삭제된 것처럼 처리하는 기법이다. 데이터를 영구적으로 삭제하지 않고, 조회되지 않도록 플래그를 설정함으로써 데이터의 보존과 참조 가능성을 유지할 수 있다. 예를 들어, 'deleted' 라는 boolean 타입의 컬럼을 추가하여, 데이터가 삭제 명령을 받을 때 이 컬럼의 값을 true로 설정한다. 데이터를 조회할 때는 'deleted' 값이 false인 데이터만 조회하도록 필터링하여, 삭제된 데이터는 결과에 포함되지 않도록 한다.
더티 체킹 (Dirty Checking)
JPA와 같은 ORM에서 유용한 기능으로, 엔티티의 상태 변화를 자동으로 감지하고 데이터베이스와 동기화하는 과정이다. 엔티티의 속성 값이 변경되었을 때, 그 변경된 상태를 감지하고 트랜잭션이 끝날 때 해당 변경 사항을 데이터베이스에 자동으로 반영한다. 개발자는 데이터 변경 후 별도로 update 쿼리를 작성할 필요 없이, 객체의 상태만 관리하면 된다.
빌더 패턴 (Builder Pattern)
복잡한 객체의 생성 과정을 단순화시키고, 코드의 가독성을 높이기 위해 사용되는 디자인 패턴 중 하나이다. 많은 매개변수를 갖는 객체를 생성할 때 유용하며, 필요한 데이터만을 설정하여 객체를 생성할 수 있다. 빌더 패턴을 사용하면, 각 매개변수의 의미를 명확히 할 수 있으며, 객체의 불변성을 유지할 수 있다. 예를 들어, Person 객체가 여러 속성(이름, 나이, 이메일 등)을 가질 경우, 빌더 패턴을 이용하여 필요한 속성만을 선택적으로 설정하여 객체를 생성할 수 있다.
@Service
@RequiredArgsConstructor
public class StoreService {

    private final StoreRepository storeRepository;

    @Transactional
    public Store createStore(StoreRequestDto dto) {
        Store store = Store.builder()
                .categoryId(dto.getCategoryId().toString())
                .name(dto.getName())
                .address(Address.of(dto.getAddress()))
                .phone(dto.getPhone())
                .operatingHours(dto.getOperatingHours())
                .closedDays(dto.getClosedDays())
                .pictureUrl(dto.getPictureUrl())
                .status(dto.getStatus())
                .deliveryType(dto.getDeliveryType().toString())
                .deliveryArea(dto.getDeliveryArea())
                .minimumOrderPrice(dto.getMinimumOrderPrice())
                .deliveryTip(dto.getDeliveryTip())
                .build();

        return store;
    }

    @Transactional(readOnly = true)
    public List<Store> getAllStores() {
        return storeRepository.findAll();
    }

    @Transactional(readOnly = true)
    public Optional<Store> getStoreById(String id) {
        return storeRepository.findById(id);
    }

    @Transactional
    public void updateStore(String id, StoreRequestDto dto) {
        Store store = storeRepository.findById(id).orElseThrow(() -> new NoSuchElementException("가게를 찾을 수 없습니다."));

        store.updateStoreDetails(
                dto.getName(),
                dto.getPhone(),
                dto.getOperatingHours(),
                dto.getClosedDays(),
                dto.getPictureUrl(),
                dto.getCategoryId().toString(),
                dto.getStatus(),
                dto.getDeliveryType().toString(),
                dto.getDeliveryArea(),
                dto.getMinimumOrderPrice(),
                dto.getDeliveryTip(),
                Address.of(dto.getAddress())
        );
    }

    @Transactional
    public void deleteStore(String id) {
        Store store = storeRepository.findById(id).orElseThrow(() -> new RuntimeException("가게를 찾을 수 없습니다."));
        store.delete();
    }
}

추가로 해야 할 일

  • 배달 가능 지역 설정 구현
  • 공통 ResponseDTO 적용

스프링 부트와 JPA의 다양한 기능을 직접 구현하면서 이론적으로만 알던 것을 확실하게 배울 수 있었던 것 같다.

참고자료

1) 기억용블로그, "@Setter없이 Entity를 update하는 방법", 2022.04.28, https://middleearth.tistory.com/11

2) jaamong, "[JPA] 더티체킹과 update", 2023.06.26, https://backend-jaamong.tistory.com/116

Comments