2025.05.09 ~ 05.14






POST 방식에서 Body 안에 raw 방식을 사용하면 Body 형식으로 요청을 보낼 수 있다


인증권한을 주지 않고 조회를 하게 되면 에러가 나게 된다.
로그인 할 때 부여받은 bearer 토큰을 입력한다
@RestController
@RequestMapping("/auth")
public class AuthController {
private final AuthService authService;
@Autowired
public AuthController(AuthService authService) {
this.authService = authService;
}
<!--
@RequestBody를 통해 RequestBody로 넘어온 JSON 문자열을 파싱해 MemberDTO 속성으로 매핑해 객체로 받아낸다.
(회원 아이디, 비밀번호)
========================================================================================================
참고로 요청의 body에서 데이터를 뽑아내겠다는 것은 요청이 POST 요청이었다는 것을 알 수 있다.
왜냐하면 GET 요청은 body가 아니라 header에 데이터가 담겨있기 때문이다.
-->
@Operation(summary = "회원 가입 요청", description = "회원 가입이 진행됩니다.", tags = {"AuthController"})
@PostMapping("/signup")
public ResponseEntity<ResponseDTO> signup(@RequestBody MemberDTO memberDTO) { <!-- 회원 가입 정보를 받아 냄 -->
return ResponseEntity
.ok()
.body(new ResponseDTO(HttpStatus.CREATED, "회원가입 성공", authService.signup(memberDTO)));
}
}
@Service
public class AuthService {
private static final Logger log = LoggerFactory.getLogger(AuthService.class);
private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;
private final TokenProvider tokenProvider;
private final ModelMapper modelMapper;
private final MemberRoleRepository memberRoleRepository;
@Autowired
public AuthService(MemberRepository memberRepository, PasswordEncoder passwordEncoder,
TokenProvider tokenProvider, ModelMapper modelMapper,
MemberRoleRepository memberRoleRepository) {
this.memberRepository = memberRepository;
this.passwordEncoder = passwordEncoder;
this.tokenProvider = tokenProvider;
this.modelMapper = modelMapper;
this.memberRoleRepository = memberRoleRepository;
}
<!-- signup은 DML(INSERT) 작업이므로 @Transactional 어노테이션 추가 -->
@Transactional
public MemberDTO signup(MemberDTO memberDTO) {
log.info("[AuthService] signup() Start.");
log.info("[AuthService] memberDTO {}", memberDTO);
<!-- 이메일 중복 유효성 검사(비즈니스 로직에 따라 선택적으로 구현하면 됨) -->
if(memberRepository.findByMemberEmail(memberDTO.getMemberEmail()) != null) {
log.info("[AuthService] 이메일이 중복됩니다.");
throw new DuplicatedMemberEmailException("이메일이 중복됩니다.");
}
<!-- 우선 Repository로 쿼리를 작성하기 전에 DTO를 Entity로 매핑. -->
Member registMember = modelMapper.map(memberDTO, Member.class);
<!-- 목차. 1. tbl_member 테이블에 회원 INSERT -->
<!-- 비밀번호 암호화 후 insert -->
registMember.setMemberPassword(passwordEncoder.encode(registMember.getMemberPassword()));
<!-- registMember.toBuilder().memberPassword(passwordEncoder.encode(registMember.getMemberPassword())).build(); -->
Member result1 = memberRepository.save(registMember); <!-- 반환형은 int값이 아닌 엔티티임. -->
<!-- 목차. 2. tbl_member_role 테이블에 회원별 권한 INSERT (현재 엔티티에는 회원가입 후 pk값이 없다!)
JPA 에서 회원을 먼저 저장해야(save) pk(회원번호) 가 생기는데, 엔티티로 작업할 땐 저장 전까지는 pk값이 null 이거나 할딩이 안된다. -->
<!-- 목차. 2-1. 우선 일반 권한(AuthorityCode값이 2번)의 회원을 추가(일종의 디폴트 권한을 지정해주면 됨) -->
<!--
목차. 2-2. 엔티티에는 추가 할 회원의 pk값이 아직 없으므로 기존 회원의 마지막 회원 번호를 조회
(하지만 jpql에 의해 앞선 save와 jpql이 flush()로 쿼리와 함께 날아가고 회원이 이미 sequence객체 값
증가와 함께 insert가 되 버린다. -> 결론은, maxMemberCode가 현재 가입하는 회원의 번호이다.)
maxMemberCode() 는 가장 최근(=방금 가입한) 회원번호
-->
int maxMemberCode = memberRepository.maxMemberCode(); <!-- JPQL을 사용해 회원번호 max값 추출 -->
MemberRole registMemberRole = new MemberRole(maxMemberCode, 2);
MemberRole result2 = memberRoleRepository.save(registMemberRole);
<!-- 위의 두 가지 save()가 모두 성공해야 해당 트랜잭션이 성공했다고 판단. -->
log.info("[AuthService] Member Insert Result {}",
(result1 != null && result2 != null) ? "회원 가입 성공" : "회원 가입 실패");
log.info("[AuthService] signup() End.");
return memberDTO;
}
}
public interface MemberRepository extends JpaRepository<Member, Integer> {
Member findByMemberId(String memberId);
Member findByMemberEmail(String memberEmail);
<!-- JPQL과 @Query를 활용한 구문 -->
<!-- JPQL에서 엔티티 이름은 대소문자까지 완벽히 일치할 것! -->
@Query("SELECT MAX(m.memberCode) FROM Member m")
int maxMemberCode();
<!-- purchase 도메인 추가하면서 추가한 메소드 -->
@Query("SELECT m.memberCode FROM Member m WHERE m.memberId = ?1")
int findMemberCodeByMemberId(String orderMemberId);
}
public interface MemberRoleRepository extends JpaRepository<MemberRole, MemberRolePk> {
}
@RestController
@RequestMapping("/auth")
public class AuthController {
private final AuthService authService;
@Autowired
public AuthController(AuthService authService) {
this.authService = authService;
}
<!--
@RequestBody를 통해 RequestBody로 넘어온 JSON 문자열을 파싱해 MemberDTO 속성으로 매핑해 객체로 받아낸다.
(회원 아이디, 비밀번호)
========================================================================================================
참고로 요청의 body에서 데이터를 뽑아내겠다는 것은 요청이 POST 요청이었다는 것을 알 수 있다.
왜냐하면 GET 요청은 body가 아니라 header에 데이터가 담겨있기 때문이다.
-->
@Operation(summary = "로그인 요청", description = "로그인 및 인증이 진행됩니다.", tags = {"AuthController"})
@PostMapping("/login")
public ResponseEntity<ResponseDTO> login(@RequestBody MemberDTO memberDTO) {
<!--
ResponseEntity
HTTP 응답 몸체와 헤더, 그리고 상태 코드를 제어할 수 있는 Spring Framework의 클래스다.
응답으로 변환될 정보가 담긴 모든 요소들을 해당 객체로 만들어서 반환해 준다.(body + header + status)
(ResponseBody와 차별점이 있다면, ResponseEntity는 HTTP 상태 코드나 헤더도 다룰 수 있다.)
필요한 정보들만 담아서 전달할 수 있기 때문에 REST API를 만들 때 유용하게 사용하는 클래스다.
또한 ResponseEntity를 사용할 때, 생성자 대신 Builder 사용을 권장한다.
(숫자 타입인 상태 코드를 실수로 잘못 입력하지 않도록 메소드들이 제공 된다.)
-->
<!-- Builder 사용 -->
return ResponseEntity
.ok()
.body(new ResponseDTO(HttpStatus.OK, "로그인 성공~", authService.login(memberDTO)));
<!-- (React 및 Spring 연계 시, 가장 중요한 개념!!!)
ResponseEntity의 body() 메소드를 사용하면 Response객체의 body에 담기는 ResponseDTO는 JSON문자열이 되고
화면단이 React인 곳으로 가면 결국 Redux Store에 해당 리듀서가 관리하는 state 값이 된다.
-->
}
}
@Service
public class AuthService {
private static final Logger log = LoggerFactory.getLogger(AuthService.class);
private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;
private final TokenProvider tokenProvider;
private final ModelMapper modelMapper;
private final MemberRoleRepository memberRoleRepository;
@Autowired
public AuthService(MemberRepository memberRepository, PasswordEncoder passwordEncoder,
TokenProvider tokenProvider, ModelMapper modelMapper,
MemberRoleRepository memberRoleRepository) {
this.memberRepository = memberRepository;
this.passwordEncoder = passwordEncoder;
this.tokenProvider = tokenProvider;
this.modelMapper = modelMapper;
this.memberRoleRepository = memberRoleRepository;
}
public Object login(MemberDTO memberDTO) {
log.info("[AuthService] login() START");
log.info("[AuthService] {}", memberDTO);
<!-- 목차. 1. 아이디 조회 -->
Member member = memberRepository.findByMemberId(memberDTO.getMemberId());
if(member == null) {
log.info("[AuthService] login() Required User Not Found!");
throw new LoginFailedException(memberDTO.getMemberId() + " 유저를 찾을 수 없습니다.");
}
<!-- 목차. 2. 비밀번호 매칭 -->
if(!passwordEncoder.matches(memberDTO.getMemberPassword(), member.getMemberPassword())) {
log.info("[AuthService] login() Password Match Failed!");
throw new LoginFailedException("잘못된 비밀번호 입니다.");
}
<!-- 목차. 3. 토큰 발급 -->
TokenDTO newToken = tokenProvider.generateTokenDTO(member);
return newToken;
}
}
public interface MemberRepository extends JpaRepository<Member, Integer> {
Member findByMemberId(String memberId);
Member findByMemberEmail(String memberEmail);
<!-- JPQL과 @Query를 활용한 구문 -->
<!-- JPQL에서 엔티티 이름은 대소문자까지 완벽히 일치할 것! -->
@Query("SELECT MAX(m.memberCode) FROM Member m")
int maxMemberCode();
<!-- purchase 도메인 추가하면서 추가한 메소드 -->
@Query("SELECT m.memberCode FROM Member m WHERE m.memberId = ?1")
int findMemberCodeByMemberId(String orderMemberId);
}
@RestController
@RequestMapping("/api/v1")
public class MemberController {
private final MemberService memberService;
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
@Operation(summary = "회원 조회 요청", description = "회원 한명이 조회됩니다.", tags = { "MemberController" })
@GetMapping("/members/{memberId}")
public ResponseEntity<ResponseDTO> selectMyMemberInfo(@PathVariable String memberId) {
return ResponseEntity.ok().body(new ResponseDTO(HttpStatus.OK, "조회 성공", memberService.selectMyInfo(memberId)));
}
}
@Service
public class MemberService {
private static final Logger log = LoggerFactory.getLogger(MemberService.class);
private final MemberRepository memberRepository;
private final ModelMapper modelMapper;
@Autowired
public MemberService(MemberRepository memberRepository, ModelMapper modelMapper) {
this.memberRepository = memberRepository;
this.modelMapper = modelMapper;
}
public MemberDTO selectMyInfo(String memberId) {
log.info("[MemberService] getMyInfo Start =======================");
Member member = memberRepository.findByMemberId(memberId);
log.info("[MemberService] {}", member);
log.info("[MemberService] getMyInfo End =========================");
return modelMapper.map(member, MemberDTO.class);
}
}
public interface MemberRepository extends JpaRepository<Member, Integer> {
Member findByMemberId(String memberId);
Member findByMemberEmail(String memberEmail);
<!-- JPQL과 @Query를 활용한 구문 -->
<!-- JPQL에서 엔티티 이름은 대소문자까지 완벽히 일치할 것! -->
@Query("SELECT MAX(m.memberCode) FROM Member m")
int maxMemberCode();
<!-- purchase 도메인 추가하면서 추가한 메소드 -->
@Query("SELECT m.memberCode FROM Member m WHERE m.memberId = ?1")
int findMemberCodeByMemberId(String orderMemberId);
}
@RestController
@RequestMapping("/api/v1")
<!-- Lombok이 제공하는 애너테이션 -->
<!-- final 또는 @NonNull이 붙은 필드들만 포함하는 생성자(Constructor)를 자동 생성 -->
@RequiredArgsConstructor
public class ProductController {
private static final Logger log = LoggerFactory.getLogger(ProductController.class);
private final ProductService productService;
@Operation(summary = "상품 리스트 조회 요청", description = "상품 조회 및 페이징 처리가 진행됩니다.", tags = { "ProductController" })
@GetMapping("/products")
public ResponseEntity<ResponseDTO> selectProductListWithPaging(
<!-- offset 은 보통 페이징 처리할 때 사용하는 변수,몇 번째 데이터부터 보여줄지 지정하는 숫자 -->
@RequestParam(name = "offset" , defaultValue = "1") String offset
) {
log.info("[ProductController] selectProductListWithPaging : " + offset);
<!-- 제품 갯수 -->
int total = productService.selectProductTotal();
<!-- Criteria 검색조건 -->
Criteria cri = new Criteria(Integer.valueOf(offset) , 10);
PagingResponseDTO pagingResponseDTO = new PagingResponseDTO();
<!-- 1. offset 의 번호에 맞는 페이지에 뿌릴 Product 들 -->
pagingResponseDTO.setData(productService.selectProductListWithPaging(cri));
<!-- 2. PageDTO(Criteria(보고싶은 페이지, 한페이지에 뿌릴 갯 수), 전체 상품 수) -->
pagingResponseDTO.setPageInfo(new PageDTO(cri , total));
return ResponseEntity.ok().body(new ResponseDTO(HttpStatus.OK, "조회 성공함!" , pagingResponseDTO));
}
}
@Service
@RequiredArgsConstructor
public class ProductService {
@Value("${image.image-dir}")
private String IMAGE_DIR;
@Value("${image.image-url}")
private String IMAGE_URL;
private static final Logger log = LoggerFactory.getLogger(ProductService.class);
private final ProductRepository productRepository;
<!-- Entity <-> DTO 객체 변환 관련 라이브러리 -->
private final ModelMapper modelMapper;
public int selectProductTotal() {
log.info("[ProductService] selectProductTotal() start!!");
List<Product> productList = productRepository.findByProductOrderable("Y");
log.info("[ProductService] selectProductTotal() end!!");
return productList.size();
}
public Object selectProductListWithPaging(Criteria cri) {
log.info("[ProductService] selectProductListWithPaging() start!!");
int index = cri.getPageNum() - 1;
int count = cri.getAmount();
Pageable paging = PageRequest.of(index , count , Sort.by("productCode").descending());
Page<Product> result = productRepository.findByProductOrderable("Y" , paging);
List<Product> productList = result.getContent();
<!-- 이미지 관련 처리 -->
for(int i = 0; i < productList.size(); i++) {
productList.get(i).setProductImageUrl(IMAGE_URL + productList.get(i).getProductImageUrl());
}
log.info("[ProductService] selectProductListWithPaging() end!!");
return productList.stream().map(product -> modelMapper.map(product , ProductDTO.class)).collect(Collectors.toList());
}
}
@Repository
<!-- JpaRepository 는 Spring Data JPA 에서 데이터 베이스와 연결해주는 인터페이스를 사용할 때 선언하는 방식 -->
<!-- DB에 있는 테이블과 자바 객체 사이의 CRUD 작업을 자동으로 처리해주는 역할 -->
public interface ProductRepository extends JpaRepository<Product , Integer> {
<!-- 판매 가능한 메뉴 조회 -->
List<Product> findByProductOrderable(String y);
<!-- 페이징 처리가 된 메뉴 조회 -->
Page<Product> findByProductOrderable(String y , Pageable pageable);
}
offset 설정 확인
@RestController
@RequestMapping("/api/v1")
<!-- Lombok이 제공하는 애너테이션
final 또는 @NonNull이 붙은 필드들만 포함하는 생성자 -->(Constructor)를 자동 생성
@RequiredArgsConstructor
public class ProductController {
private static final Logger log = LoggerFactory.getLogger(ProductController.class);
private final ProductService productService;
@Operation(summary = "상품 상세 조회 요청", description = "상품의 상세 페이지 처리가 진행됩니다.", tags = { "ProductController" })
@GetMapping("/products/{productCode}")
public ResponseEntity<ResponseDTO> selectProductDetail(@PathVariable int productCode) {
return ResponseEntity.ok().body(new ResponseDTO(HttpStatus.OK , productCode + "번 상품 상세조회 성공", productService.selectProduct(productCode)));
}
}
@Service
@RequiredArgsConstructor
public class ProductService {
@Value("${image.image-dir}")
private String IMAGE_DIR;
@Value("${image.image-url}")
private String IMAGE_URL;
private static final Logger log = LoggerFactory.getLogger(ProductService.class);
private final ProductRepository productRepository;
<!-- Entity <-> DTO 객체 변환 관련 라이브러리 -->
private final ModelMapper modelMapper;
public ProductDTO selectProduct(int productCode) {
log.info("[ProductService] selectProduct() Start");
<!-- Product vs Optional<Product> 차이 때문 -->
<!-- Optional<T> 는 값이 있을 수도 있고, 없을 수도 있다를 명확하게 코드로 표현하는 클래스 -->
<!-- 일단 get() 으로 단순 데이터를 끌고 오기만 했다 -->
Product product = productRepository.findById(productCode).get();
<!-- 이미지 설정 -->
product.setProductImageUrl(IMAGE_URL + product.getProductImageUrl());
log.info("[ProductService] selectProduct() End");
return modelMapper.map(product , ProductDTO.class);
}
}

@RestController
@RequestMapping("/api/v1")
<!-- Lombok이 제공하는 애너테이션
final 또는 @NonNull이 붙은 필드들만 포함하는
생성자(Constructor)를 자동 생성 -->
@RequiredArgsConstructor
public class ProductController {
private static final Logger log = LoggerFactory.getLogger(ProductController.class);
private final ProductService productService;
@Operation(summary = "검색 상품 리스트 조회 요청", description = "검색어에 해당되는 상품 리스트 조회가 진행됩니다.", tags = { "ProductController" })
@GetMapping("/products/search") <!- 쿼리 스트링으로 오는 값을 search 에 넣어준다 -->
public ResponseEntity<ResponseDTO> selectSearchProductList(@RequestParam(name = "s" , defaultValue = "all") String search) {
return ResponseEntity.ok().body(new ResponseDTO(HttpStatus.OK , search + " 검색어 조회 성공 ", productService.selectSearchProductList(search)));
}
}
@Service
@RequiredArgsConstructor
public class ProductService {
@Value("${image.image-dir}")
private String IMAGE_DIR;
@Value("${image.image-url}")
private String IMAGE_URL;
private static final Logger log = LoggerFactory.getLogger(ProductService.class);
private final ProductRepository productRepository;
<!-- Entity <-> DTO 객체 변환 관련 라이브러리 -->
private final ModelMapper modelMapper;
public List<ProductDTO> selectSearchProductList(String search) {
log.info("[ProductService] selectSearchProductList() Start");
log.info("[ProductService] searchValue : {}", search);
<!-- Containing 는 포함하고 있는지를 확인하는 구문 -->
List<Product> productListWithSearchValue = productRepository.findByProductNameContaining(search);
log.info("[ProductService] productListWithSearchValue : {}", productListWithSearchValue);
<!-- 이미지 관련 처리 -->
for (int i = 0; i < productListWithSearchValue.size(); i++){
productListWithSearchValue.get(i).setProductImageUrl(IMAGE_URL + productListWithSearchValue.get(i).getProductImageUrl());
}
log.info("[ProductService] selectSearchProductList() End");
return productListWithSearchValue.stream()
.map(product -> modelMapper.map(product , ProductDTO.class))
.collect(Collectors.toList());
}
}
@Repository
<!-- JpaRepository 는 Spring Data JPA 에서 데이터 베이스와
연결해주는 인터페이스를 사용할 때 선언하는 방식
DB에 있는 테이블과 자바객체 사이의 CRUD 작업을 자동으로 처리해주는 역할 -->
public interface ProductRepository extends JpaRepository<Product , Integer> {
<!-- 판매 가능한 메뉴 조회 -->
List<Product> findByProductOrderable(String y);
<!-- 페이징 처리가 된 메뉴 조회 -->
Page<Product> findByProductOrderable(String y , Pageable pageable);
<!-- 우리가 입력한 검색어를 포함하고 있는 제품 리스트 조회 메서드 -->
List<Product> findByProductNameContaining(String search);
}

@RestController
@RequestMapping("/api/v1")
<!-- Lombok이 제공하는 애너테이션
final 또는 @NonNull이 붙은 필드들만 포함하는
생성자(Constructor)를 자동 생성 -->
@RequiredArgsConstructor
public class ProductController {
private static final Logger log = LoggerFactory.getLogger(ProductController.class);
private final ProductService productService;
@Operation(summary = "식사 상품 리스트 조회 요청", description = "식사 카테고리에 해당하는 상품 리스트 조회가 진행됩니다.", tags = { "ProductController" })
@GetMapping("/products/meals")
public ResponseEntity<ResponseDTO> selectProductListAboutMeal() {
return ResponseEntity.ok().body(new ResponseDTO(HttpStatus.OK , "카테고리 별 조회 성공!" , productService.selectProductListAboutMeal()));
}
}
Service
@RequiredArgsConstructor
public class ProductService {
@Value("${image.image-dir}")
private String IMAGE_DIR;
@Value("${image.image-url}")
private String IMAGE_URL;
private static final Logger log = LoggerFactory.getLogger(ProductService.class);
private final ProductRepository productRepository;
<!-- Entity <-> DTO 객체 변환 관련 라이브러리 -->
private final ModelMapper modelMapper;
public List<ProductDTO> selectProductListAboutMeal() {
log.info("[ProductService] selectProductListAboutMeal() Start");
<!-- findByCategoryCode(categoryCode) 처럼
변수로 작성하게 되면 일일히 만들어 줄 필요가 없다 -->
List<Product> productListAboutMeal = productRepository.findByCategoryCode(1);
<!-- 이미지 관련 처리 -->
for (int i = 0; i < productListAboutMeal.size(); i++){
productListAboutMeal.get(i).setProductImageUrl(IMAGE_URL + productListAboutMeal.get(i).getProductImageUrl());
}
log.info("[ProductService] selectProductListAboutMeal() End");
return productListAboutMeal.stream()
.map(product -> modelMapper.map(product , ProductDTO.class))
.collect(Collectors.toList());
}
}
@Repository
<!-- JpaRepository 는 Spring Data JPA 에서 데이터 베이스와
연결해주는 인터페이스를 사용할 때 선언하는 방식
DB에 있는 테이블과 자바객체 사이의 CRUD 작업을 자동으로 처리해주는 역할 -->
public interface ProductRepository extends JpaRepository<Product , Integer> {
<!-- 판매 가능한 메뉴 조회 -->
List<Product> findByProductOrderable(String y);
<!-- 페이징 처리가 된 메뉴 조회 -->
Page<Product> findByProductOrderable(String y , Pageable pageable);
<!-- 우리가 입력한 검색어를 포함하고 있는 제품 리스트 조회 메서드 -->
List<Product> findByProductNameContaining(String search);
<!-- 카테고리 코드 별 메뉴 리스트 조회 메서드 -->
List<Product> findByCategoryCode(int i);
}

@RestController
@RequestMapping("/api/v1")
<!-- Lombok이 제공하는 애너테이션
final 또는 @NonNull이 붙은 필드들만
포함하는 생성자 (Constructor)를 자동 생성-->
@RequiredArgsConstructor
public class ProductController {
private static final Logger log = LoggerFactory.getLogger(ProductController.class);
private final ProductService productService;
@Operation(summary = "관리자 페이지 상품 상세 페이지 조회 요청", description = "관리자 페이지에서 상품 상세 페이지 조회가 진행됩니다.", tags = { "ProductController" })
@GetMapping("/products-management/{productCode}")
public ResponseEntity<ResponseDTO> selectProductDetailForAdmin(@PathVariable int productCode) {
return ResponseEntity.ok().body(new ResponseDTO(HttpStatus.OK , "관리자 제품 상세조회 성공!" , productService.selectProductForAdmin(productCode)));
}
}
@Service
@RequiredArgsConstructor
public class ProductService {
@Value("${image.image-dir}")
private String IMAGE_DIR;
@Value("${image.image-url}")
private String IMAGE_URL;
private static final Logger log = LoggerFactory.getLogger(ProductService.class);
private final ProductRepository productRepository;
<!-- Entity <-> DTO 객체 변환 관련 라이브러리 -->
private final ModelMapper modelMapper;
public ProductDTO selectProductForAdmin(int productCode) {
log.info("[ProductService] selectProductForAdmin() Start");
Product product = productRepository.findById(productCode).get();
product.setProductImageUrl(IMAGE_URL + product.getProductImageUrl());
log.info("[ProductService] selectProductForAdmin() End");
return modelMapper.map(product , ProductDTO.class);
}



@RestController
@RequestMapping("/api/v1")
<!-- Lombok이 제공하는 애너테이션
final 또는 @NonNull이 붙은 필드들만
포함하는 생성자(Constructor)를 자동 생성 -->
@RequiredArgsConstructor
public class ProductController {
private static final Logger log = LoggerFactory.getLogger(ProductController.class);
private final ProductService productService;
<!--
@RequestBody 는 Json 형식의 요청 데이터를 객체로 매핑하는 데 사용이 되며
파일 업로드 같은 멀티파트 데이터를 처리할 수 없다.
반면, @ModelAttribute 는 폼 데이터와 파일 업로드 데이터를 함께 처리할 수 있도록
설계가 되었기 때문에 Image or File 관련 처리는 @ModelAttribute 를 사용하는 것이
더욱 적합하게 된다.
-->
@Operation(summary = "상품 등록 요청", description = "해당 상품 등록이 진행됩니다.", tags = { "ProductController" })
@PostMapping(value = "/products")
public ResponseEntity<ResponseDTO> insertProduct(@ModelAttribute ProductDTO productDTO , MultipartFile productImage) {
log.info("[Controller] 프론트에서 전달 받은 productDTO : {}" , productDTO);
log.info("[Controller] 프론트에서 전달 받은 productImage : {}" , productImage);
return ResponseEntity.ok().body(new ResponseDTO(HttpStatus.CREATED , "상품 등록 성공", productService.insertProduct(productDTO , productImage)));
}
}
@Service
@RequiredArgsConstructor
public class ProductService {
@Value("${image.image-dir}")
private String IMAGE_DIR;
@Value("${image.image-url}")
private String IMAGE_URL;
private static final Logger log = LoggerFactory.getLogger(ProductService.class);
private final ProductRepository productRepository;
<!-- Entity <-> DTO 객체 변환 관련 라이브러리 -->
private final ModelMapper modelMapper;
@Transactional
public String insertProduct(ProductDTO productDTO , MultipartFile productImage) {
log.info("[ProductService] insertProduct() Start");
log.info("[ProductService] productDTO : {}", productDTO);
<!-- 이미지 파일 이름을 랜덤하게 지정해서 저장한다 -->
String imageName = UUID.randomUUID().toString().replace("-" , "");
String replaceFileName = null;
int result = 0;
try {
replaceFileName = FileUploadUtils.saveFile(IMAGE_DIR , imageName , productImage);
<!-- 변환 처리 된 파일 값으로 Set -->
productDTO.setProductImageUrl(replaceFileName);
<!-- 화면에서 전달 받은 DTO 객체를 Entity 로 변경 -->
Product insertProduct = modelMapper.map(productDTO , Product.class);
productRepository.save(insertProduct);
<!-- 정상적으로 예외 없이 마무리 되면 result 를 1로 초기화 -->
result = 1;
} catch (IOException e) {
<!-- 예외 발생 시 파일에 대한 정보 삭제 -->
FileUploadUtils.deleteFile(IMAGE_DIR , replaceFileName);
throw new RuntimeException(e);
}
log.info("[ProductService] insertProduct() End");
return (result > 0) ? productDTO.getProductName() + " 상품 등록 성공!!" : " 상품 등록 실패";
}
}



@Entity
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
@Table(name = "tbl_product")
@Builder(toBuilder = true)
public class ProductAndCategory {
@Id
@Column(name = "product_code")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int productCode;
@Column(name = "product_name")
private String productName;
@Column(name = "product_price")
private String productPrice;
@Column(name = "product_description")
private String productDescription;
@Column(name = "product_orderable")
private String productOrderable;
<!-- @Column(name = "category_code")
private int categoryCode; -->
<!-- Product 와 Category 의 연관관계를 형성 할 필드 설정 -->
@ManyToOne
@JoinColumn(name = "category_code")
private Category category;
@Column(name = "product_image_url")
private String productImageUrl;
@Column(name = "product_stock")
private Long productStock;
}
@Data
public class ProductAndCategoryDTO {
private int productCode;
private String productName;
private String productPrice;
private String productDescription;
private String productOrderable;
private CategoryDTO category;
private String productImageUrl;
private Long productStock;
}
@RestController
@RequestMapping("/api/v1")
// Lombok이 제공하는 애너테이션
// final 또는 @NonNull이 붙은 필드들만 포함하는 생성자(Constructor)를 자동 생성
@RequiredArgsConstructor
public class ProductController {
private static final Logger log = LoggerFactory.getLogger(ProductController.class);
private final ProductService productService;
@Operation(summary = "관리자 페이지 상품 리스트 조회 요청", description = "관리자 페이지에서 상품 리스트 조회가 진행됩니다.", tags = { "ProductController" })
@GetMapping("/products-management")
public ResponseEntity<ResponseDTO> selectProductListWithPagingForAdmin(
@RequestParam(name = "offset" , defaultValue = "1") String offset) {
int total = productService.selectProductTotal();
// 1 페이지에 10 개의 데이터 라는 검색 조건을 담을 객체
Criteria cri = new Criteria(Integer.valueOf(offset), 10);
PagingResponseDTO pagingResponseDTO = new PagingResponseDTO();
// DB 에서 조회해온 데이터를 담는 setData()
pagingResponseDTO.setData(productService.selectProductListWithPagingForAdmin(cri));
pagingResponseDTO.setPageInfo(new PageDTO(cri, total));
return ResponseEntity.ok().body(new ResponseDTO(HttpStatus.OK , "조회 성공" , pagingResponseDTO));
}
}
@Service
@RequiredArgsConstructor
public class ProductService {
@Value("${image.image-dir}")
private String IMAGE_DIR;
@Value("${image.image-url}")
private String IMAGE_URL;
private static final Logger log = LoggerFactory.getLogger(ProductService.class);
private final ProductRepository productRepository;
// Category <-> Product 연관관계 전용 레포지토리
private final ProductAndCategoryRepository productAndCategoryRepository;
/* Entity <-> DTO 객체 변환 관련 라이브러리 */
private final ModelMapper modelMapper;
public List<ProductAndCategoryDTO> selectProductListWithPagingForAdmin(Criteria cri) {
log.info("[ProductService] selectProductListWithPagingForAdmin() Start");
int index = cri.getPageNum()-1;
int count = cri.getAmount();
Pageable paging = PageRequest.of(index, count, Sort.by("productCode").descending());
// 레파지토리 작성하면 쓸 부분
// ProductRepositoy 는 Product 엔티티에 대한 레파지토리이므로 ProductAndCategory 엔티티에 대해서는 사용할 수 없다
// 엔티티를 분리해서 사용했으므로 레파지토리도 분리해서 사용해야 한다
// findAll : 페이징 넘버를 담고 있는 전체 데이터 조회
Page<ProductAndCategory> result = productAndCategoryRepository.findAll(paging);
List<ProductAndCategory> productList = result.getContent();
// 이미지 관련 처리 -> 여러분들이 신경쓰지 않아도 됩니다.
for(int i = 0; i < productList.size(); i++) {
productList.get(i).setProductImageUrl(IMAGE_URL + productList.get(i).getProductImageUrl());
}
log.info("[ProductService] selectProductListWithPagingForAdmin() End");
return productList.stream()
.map(product -> modelMapper.map(product , ProductAndCategoryDTO.class))
.collect(Collectors.toList());
}
}
@Repository
public interface ProductAndCategoryRepository extends JpaRepository<ProductAndCategory , Integer> {
}
관리자 계정으로 먼저 로그인
카테고리부분까지 나오는 것을 확인
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Getter
@ToString
@Table(name = "tbl_order")
@Builder(toBuilder = true)
public class Order {
@Id
@Column(name = "order_code")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int orderCode;
@Column(name = "product_code")
private int productCode;
@Column(name = "order_member")
private int orderMember;
@Column(name = "order_phone")
private String orderPhone;
@Column(name = "order_email")
private String orderEmail;
@Column(name = "order_receiver")
private String orderReceiver;
@Column(name = "order_address")
private String orderAddress;
@Column(name = "order_amount")
private String orderAmount;
@Column(name = "order_date")
private String orderDate;
}
@Data
public class OrderDTO {
// 구매를 완료하고 하고 나서 내역 확인할 때 사용
private int orderCode;
private int productCode;
private int orderMember;
private String orderPhone;
private String orderEmail;
private String orderReceiver;
private String orderAddress;
private String orderAmount;
private String orderDate;
}
@Data
public class PurchaseDTO {
// 구매 시 사용할 DTO 필드 구성
private String memberId;
private String orderAddress;
private int orderAmount;
private String orderEmail;
private String orderPhone;
private String orderReceiver;
private int productCode;
}
@Slf4j
@RestController
@RequestMapping("/api/v1")
public class OrderController {
private final OrderService orderService;
@Autowired
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
/* 설명. @RequestBody로 넘어온 JSON 문자열을 모두 받아줄 DTO(커맨드객체)를 작성할 것(getter, setter 필수)*/
@Operation(summary = "상품 주문 요청", description = "해당 상품 주문이 진행됩니다.", tags = { "OrderController" })
@PostMapping("/purchase")
public ResponseEntity<ResponseDTO> insertPurchase(@RequestBody PurchaseDTO purchaseDTO) {
return ResponseEntity.ok().body(new ResponseDTO(HttpStatus.CREATED, " 주문 성공" , orderService.insertProduct(purchaseDTO)));
}
}
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderService {
private final MemberRepository memberRepository;
private final OrderRepository orderRepository;
private final ModelMapper modelMapper;
private final ProductRepository productRepository;
@Transactional
public String insertProduct(PurchaseDTO purchaseDTO) {
log.info("[OrderService] insertPurchase() Start");
log.info("[OrderService] purchaseDTO : {}", purchaseDTO);
int result = 0;
try {
/* 1. 해당 주문을 진행하고 있는 회원의 PK 값 조회 */
int memberCode = memberRepository.findMemberCodeByMemberId(purchaseDTO.getMemberId());
/* 2. 주문 INSERT */
Date now = new Date();
// 주문 date 값을 포멧팅
SimpleDateFormat sdf = new SimpleDateFormat("yy/MM/dd HH:mm:ss");
String orderDate = sdf.format(now);
Order order = Order.builder()
.productCode(purchaseDTO.getProductCode())
.orderMember(memberCode)
.orderPhone(purchaseDTO.getOrderPhone())
.orderAddress(purchaseDTO.getOrderAddress())
.orderDate(orderDate)
.orderEmail(purchaseDTO.getOrderEmail())
.orderReceiver(purchaseDTO.getOrderReceiver())
.orderAmount(String.valueOf(purchaseDTO.getOrderAmount()))
.build();
// 위에 생성한 order 인스턴스 save
orderRepository.save(order);
/* 3. 상품(Product) 재고 Update */
// 상품 한 행 식별
Product product = productRepository.findById(Integer.valueOf(order.getProductCode())).get();
// 재고 업데이트
product = product.toBuilder()
// 기존 재고 - 주문 시 양
.productStock(product.getProductStock() - purchaseDTO.getOrderAmount())
.build();
// 업데이트 반영
productRepository.save(product);
result = 1;
}catch (Exception e){
log.error("[Order] Exception 발생!!" , e);
}
log.info("[OrderService] insertPurchase() End");
return (result > 0) ? "주문 성공!! " : " 주문 실패 ㅠㅜ ";
}
}
@Repository
public interface OrderRepository extends JpaRepository<Order , Integer> {
}





@Entity
@NoArgsConstructor
@AllArgsConstructor
@Getter
@ToString
@Table(name = "tbl_order")
@Builder(toBuilder = true)
public class OrderAndProduct {
@Id
@Column(name = "order_code")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int orderCode;
@ManyToOne
@JoinColumn(name = "product_code")
private Product product;
@Column(name = "order_member")
private int orderMember;
@Column(name = "order_phone")
private String orderPhone;
@Column(name = "order_email")
private String orderEmail;
@Column(name = "order_receiver")
private String orderReceiver;
@Column(name = "order_address")
private String orderAddress;
@Column(name = "order_amount")
private String orderAmount;
@Column(name = "order_date")
private String orderDate;
}
@Data
public class OrderAndProductDTO {
private int orderCode;
// 제품 관련 정보 객체
private ProductDTO product;
private int orderMember;
private String orderPhone;
private String orderEmail;
private String orderReceiver;
private String orderAddress;
private String orderAmount;
private String orderDate;
}
@Slf4j
@RestController
@RequestMapping("/api/v1")
public class OrderController {
private final OrderService orderService;
@Autowired
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@Operation(summary = "회원 주문 리스트 조회 요청", description = "해당 회원의 주문건에 대한 상품 리스트 조회가 진행됩니다.", tags = { "OrderController" })
@GetMapping("/purchase/{memberId}")
public ResponseEntity<ResponseDTO> getPurchaseList(@PathVariable String memberId) {
return ResponseEntity.ok().body(new ResponseDTO(HttpStatus.OK , "조회 성공" , orderService.selectPurchaseList(memberId)));
}
}
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderService {
private final MemberRepository memberRepository;
private final OrderRepository orderRepository;
private final ModelMapper modelMapper;
private final ProductRepository productRepository;
private final OrderAndProductRepository orderAndProductRepository;
public List<OrderAndProductDTO> selectPurchaseList(String memberId) {
log.info("[OrderService] selectPurchaseList() Start");
// 우리에게 주어진 힌트가 memberId 이기 때문에 Id 로 Code(식별자) 조회
int memberCode = memberRepository.findMemberCodeByMemberId(memberId);
// 제품과 주문 엔티티 연관관계 형성
List<OrderAndProduct> orderList = orderAndProductRepository.findByOrderMember(memberCode);
log.info("[OrderService] purchaseList {}", orderList);
log.info("[OrderService] selectPurchaseList() End");
return orderList.stream().map(
order -> modelMapper.map(order , OrderAndProductDTO.class)
).collect(Collectors.toList());
}
}
public interface OrderAndProductRepository extends JpaRepository<OrderAndProduct , Integer> {
// 회원 별 주문 목록 조회
List<OrderAndProduct> findByOrderMember(int memberCode);
}

@Entity
@NoArgsConstructor
@AllArgsConstructor
@Getter
@ToString
@Builder(toBuilder = true)
@Table(name = "tbl_review")
public class Review {
@Id
@Column(name = "review_code")
private int reviewCode;
@Column(name = "product_code")
private int productCode;
@Column(name = "member_code")
private int memberCode;
@Column(name = "review_title")
private String reviewTitle;
@Column(name = "review_content")
private String reviewContent;
@Column(name = "review_create_date")
private String reviewCreateDate;
}
@Data
public class ReviewDTO {
private int reviewCode;
private int productCode;
private int memberCode;
private String reviewTitle;
private String reviewContent;
private String reviewCreateDate;
}
@Slf4j
@RestController
@RequestMapping("/api/v1")
public class ReviewController {
private final ReviewService reviewService;
@Autowired
public ReviewController(ReviewService reviewService) {
this.reviewService = reviewService;
}
@Operation(summary = "상품 리뷰 등록 요청", description = "해당 상품 리뷰 등록이 진행됩니다.", tags = { "ReviewController" })
@PostMapping("/reviews")
/* 리뷰를 달 제품 코드 , 리뷰 입력 회원번호 , 리뷰 제목 , 리뷰 내용 */
public ResponseEntity<ResponseDTO> insertProductReview(@RequestBody ReviewDTO reviewDTO) {
log.info("[ReviewController] 전달 받은 reviewDTO : {} " , reviewDTO);
return ResponseEntity.ok().body(new ResponseDTO(HttpStatus.CREATED , "리뷰 입력 성공!!" , reviewService.insertProductReview(reviewDTO)));
}
}
@Slf4j
@Service
@RequiredArgsConstructor
public class ReviewService {
private final ModelMapper modelMapper;
private final ReviewRepository reviewRepository;
@Transactional
public Object insertProductReview(ReviewDTO reviewDTO) {
log.info("[ReviewService] insertProductReview() Start");
int result = 0;
Date now = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yy/MM/dd HH:mm:ss");
String reviewDate = sdf.format(now);
reviewDTO.setReviewCreateDate(reviewDate);
try{
Review review = modelMapper.map(reviewDTO , Review.class);
reviewRepository.save(review);
result = 1;
} catch (Exception e){
log.error("[Review] 등록 중 에러 발생 : {}" , e);
}
log.info("[ReviewService] insertProductReview() End");
return (result > 0) ? "리뷰 등록 성공" : "리뷰 등록 실패" ;
}
}
@Repository
public interface ReviewRepository extends JpaRepository<Review , Integer> {
}
@Configuration
public class BeanConfiguration {
@Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration()
// private 필드에 접근하기 위한 설정
.setFieldAccessLevel(org.modelmapper.config.Configuration.AccessLevel.PRIVATE)
// DTO , Entity 필드 접근 가능 설정
.setFieldMatchingEnabled(true);
return modelMapper;
}
}


@Entity
@NoArgsConstructor
@AllArgsConstructor
@Getter
@ToString
@Builder(toBuilder = true)
@Table(name = "tbl_review")
public class ReviewAndMember {
@Id
@Column(name = "review_code")
private int reviewCode;
// 리뷰 와 멤버 간의 연간관계 형성
@ManyToOne
@JoinColumn(name = "member_code")
private Member member;
@Column(name = "member_code")
private int memberCode;
@Column(name = "review_title")
private String reviewTitle;
@Column(name = "review_content")
private String reviewContent;
@Column(name = "review_create_date")
private String reviewCreateDate;
}
@Slf4j
@RestController
@RequestMapping("/api/v1")
public class ReviewController {
private final ReviewService reviewService;
@Autowired
public ReviewController(ReviewService reviewService) {
this.reviewService = reviewService;
}
@Operation(summary = "상품 리뷰 리스트 조회 요청", description = "해당 상품에 등록된 리뷰 리스트 조회가 진행됩니다.", tags = { "ReviewController" })
@GetMapping("/reviews/{productCode}")
public ResponseEntity<ResponseDTO> selectReviewListWithPaging(
@PathVariable String productCode ,
@RequestParam(name = "offset" , defaultValue = "1")
String offset) {
log.info("[ReviewController] selectReviewListWithPaging : " + offset);
log.info("[ReviewController] productCode : " + productCode);
Criteria cri = new Criteria(Integer.valueOf(offset) , 10);
cri.setSearchValue(productCode); // 상품을 리뷰에 대한 검색 조건으로 설정
int total = (int) reviewService.selectReviewTotal(Integer.valueOf(cri.getSearchValue()));
// Criteria 검색조건
PagingResponseDTO pagingResponseDTO = new PagingResponseDTO();
/* 1. offset 의 번호에 맞는 페이지에 뿌릴 Review 들 */
pagingResponseDTO.setData(reviewService.selectReviewListWithPaging(cri));
/* 2. PageDTO(Criteria(보고싶은 페이지, 한페이지에 뿌릴 갯 수), 전체 상품 수) */
pagingResponseDTO.setPageInfo(new PageDTO(cri , total));
return ResponseEntity.ok().body(new ResponseDTO(HttpStatus.OK, "조회 성공함!" , pagingResponseDTO));
}
}
@Slf4j
@Service
@RequiredArgsConstructor
public class ReviewService {
private final ModelMapper modelMapper;
private final ReviewRepository reviewRepository;
private final ReviewAndMemberRepository reviewAndMemberRepository;
public long selectReviewTotal(int productCode) {
log.info("[ReviewService] selectReviewTotal() Start");
// Product 코드를 통해 행의 수 반환
long result = reviewRepository.countByProductCode(productCode);
log.info("[ReviewService] selectReviewTotal() End");
return result;
}
public List<ReviewAndMemberDTO> selectReviewListWithPaging(Criteria cri) {
log.info("[ReviewService] selectReviewListWithPaging() Start");
int index = cri.getPageNum() - 1;
int count = cri.getAmount();
// 리뷰는 최신순 부터 확인
Pageable pageable = PageRequest.of(index , count , Sort.by("reviewCode"));
// 리뷰를 조회할 때 작성자에 대한 정보를 가져와야 하기 때문에
// Member Entity 와 연관관계를 형성
Page<ReviewAndMember> result = reviewAndMemberRepository.findByProductCode(Integer.valueOf(cri.getSearchValue()) , pageable);
List<ReviewAndMember> reviewList = result.getContent();
log.info("[ReviewService] selectReviewListWithPaging() End");
return reviewList.stream()
.map(review -> modelMapper.map(review , ReviewAndMemberDTO.class))
.collect(Collectors.toList());
}
}
@Repository
public interface ReviewRepository extends JpaRepository<Review , Integer> {
// count 는 반환형 long
long countByProductCode(int productCode);
}
@Repository
public interface ReviewAndMemberRepository extends JpaRepository<ReviewAndMember , Integer> {
// 제품 기준 리뷰를 페이징 처리까지 한 조회 메서드
Page<ReviewAndMember> findByProductCode(Integer integer, Pageable pageable);
}


@Slf4j
@RestController
@RequestMapping("/api/v1")
public class ReviewController {
private final ReviewService reviewService;
@Autowired
public ReviewController(ReviewService reviewService) {
this.reviewService = reviewService;
}
@Operation(summary = "리뷰 상세 페이지 조회 요청", description = "해당 리뷰의 상세 페이지 조회가 진행됩니다.", tags = { "ReviewController" })
@GetMapping("/reviews/product/{reviewCode}")
public ResponseEntity<ResponseDTO> selectReviewDetail(@PathVariable int reviewCode) {
return ResponseEntity.ok().body(new ResponseDTO(HttpStatus.OK , reviewCode + "번 리뷰 상세 조회 성공", reviewService.selectReviewDetail(reviewCode)));
}
}
@Slf4j
@Service
@RequiredArgsConstructor
public class ReviewService {
private final ModelMapper modelMapper;
private final ReviewRepository reviewRepository;
private final ReviewAndMemberRepository reviewAndMemberRepository;
public ReviewAndMemberDTO selectReviewDetail(int reviewCode) {
log.info("[ReviewService] getReviewDetail() Start");
ReviewAndMember review = reviewAndMemberRepository.findById(reviewCode).get();
log.info("[ReviewService] getReviewDetail() End");
return modelMapper.map(review , ReviewAndMemberDTO.class);
}
}


@Slf4j
@RestController
@RequestMapping("/api/v1")
public class ReviewController {
private final ReviewService reviewService;
@Autowired
public ReviewController(ReviewService reviewService) {
this.reviewService = reviewService;
}
@Operation(summary = "리뷰 수정 요청", description = "리뷰 작성자의 리뷰 수정이 진행됩니다.", tags = { "ReviewController" })
@PutMapping("/reviews")
/* 전달 받을 데이터 : 리뷰를 식별할 수 있는 reviewCode , reviewTitle , reviewContent */
public ResponseEntity<ResponseDTO> updateProductReview(@RequestBody ReviewDTO reviewDTO) {
return ResponseEntity.ok().body(new ResponseDTO(HttpStatus.OK , "리뷰 수정 성공" , reviewService.updateProductReview(reviewDTO)));
}
}
@Slf4j
@Service
@RequiredArgsConstructor
public class ReviewService {
private final ModelMapper modelMapper;
private final ReviewRepository reviewRepository;
private final ReviewAndMemberRepository reviewAndMemberRepository;
@Transactional
public String updateProductReview(ReviewDTO reviewDTO) {
log.info("[ReviewService] updateProductReview() Start");
int result = 0;
try {
// 리뷰 수정하기 위한 수정할 엔티티 인스턴스 추출
Review review = reviewRepository.findById(reviewDTO.getReviewCode()).get();
review = review.toBuilder()
.reviewTitle(reviewDTO.getReviewTitle())
.reviewContent(reviewDTO.getReviewContent())
.build();
reviewRepository.save(review);
result = 1;
} catch (Exception e){
log.error("[Review] 수정 중 오류 발생 : {}" , e);
}
log.info("[ReviewService] updateProductReview() End");
return (result > 0) ? "수정 성공" : "수정 실패" ;
}
}


DB 서버와 IntelliJ 연결 및 사용




권한설정에 환경넣기



ModelMapper를 스프링 Bean으로 등록할 때 커스터마이즈 하는 이유