안녕하세요 jju_developer입니다.
오늘은 그동안 진행을 했었던 final project에 대한 정리를 해보고자 합니다.
제가 맡은 부분은 회원정보 수정이었습니다.
@RestController 어노테이션을 통해서 Rest Api 컨트롤러 시작임을 알리며
@RequiredArgsConstructor 어노테이션을 통해 생성자 주입을 사용하였습니다.
HTTP PUT 메서드를 사용하여 회원 정보를 수정을 나타내었고,
HTTP DELETE 메서드를 사용하여 회원 정보를 삭제하는 로직을 만들었습니다.
private final CustomerModifyService customerModifyService;
회원 정보 수정 서비스의 CustomerModifyService는 CustomerModifyController의 의존성으로 주입되는 서비스 객체입니다.
final로 선언함으로써 한 번 할당된 customerModifyService 필드는 해당 객체가 생성될 때 초기화되고 이후에 변경할 수 없습니다.
이는 필드 값의 안정성과 예측 가능성을 높여줍니다.
여기서 final로 선언을 했기 때문에 대문자로 변수명을 작성해줬어야 하는데 이 부분을 놓쳤습니다.
1. CustomerModifyController
@GetMapping("/{id}")
public ResponseEntity<CustomerDto> read(@PathVariable Long id){
CustomerDto customerDto = customerModifyService.read(id);
if (customerDto != null){
return new ResponseEntity<>(customerDto, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
회원 정보를 수정하는 칸에서 내가 원하는 아이디의 정보 (즉, 로그인을 한 사용자의 정보)를 화면에서 볼 수 있어야 하겠죠?
그래서 Read를 하기 위해 GetMapping을 사용하였습니다.
백엔드에서 해당 로직이 맞는지 확인을 하기 위해서 데이터베이스에 임의로 한 명의 고객을 생성을 하고
계속 만들어 보았습니다.
(controller 전체 코드는 맨 아래에 써놓겠습니다ㅎㅎ~!!)
1. 해당 메서드는 {id} 라는 경로 변수를 통해 고객의 ID를 전달받습니다.
2. 메소드 내부에서는 customerModifyService를 사용하여 주어진 ID에 해당하는 고객 정보를 가져옵니다.
서비스에 read라는 메서드가 정의가 되어있겠죠? 이를 CustomerDto 객체에 할당합니다.
그런 다음, 가져온 customerDto가 null이 아닌 경우 ResponseEntity를 생성하여 HTTP 상태 코드 HttpStatus.OK와 함께 customerDto를 응답 본문에 포함시킵니다. 이는 성공적인 응답을 나타냅니다.
반대로, 가져온 customerDto가 null인 경우 ResponseEntity를 생성하여 HTTP 상태 코드 HttpStatus.NOT_FOUND만을 포함시킵니다. 이는 요청한 ID에 해당하는 고객 정보가 없음을 나타냅니다.
2. CustomerModifyService
<인터페이스 먼저 볼까요?>
package com.holdcredit.holdcredit.service;
import com.holdcredit.holdcredit.domain.dto.customerDto.CustomerDto;
import com.holdcredit.holdcredit.domain.dto.customerDto.CustomerModifyDto;
import org.springframework.stereotype.Service;
@Service
public interface CustomerModifyService {
//회원 정보 수정시, 수정할 부분만 따로 dto에 담는다.
//화면에 보여질 고객정보 가져오기
CustomerDto getCustomer(CustomerDto customerDto);
CustomerDto read(Long id);
//회원 정보 수정
void updateCustomer(Long id, CustomerModifyDto requestDto);
//회원 정보 삭제
void deleteCustomer(Long id);
//void pwdUpdate(Long id, String password);
boolean verifyCustomerPassword(Long customerId, String password);
}
사실 이 코드에서 read 부분만 사용을 했기 때문에
해당 인터페이스를 구현한 구현 부분으로 바로 넘어가 보도록 하겠습니다.
여기서 잠깐! 왜 서비스를 바로 쓰지 않고 인터페이스를 구현을 먼저 했을까요?
그건 바로
CustomerModifyServiceImpl 클래스가 CustomerModifyService 인터페이스를 구현하는 것은 프로그래밍 원칙인
"인터페이스 분리 원칙"을 따르기 위함입니다.

CustomerModifyService 인터페이스를 정의함으로써 해당 인터페이스를 구현하는
다른 클래스를 쉽게 추가할 수 있습니다. 이는 코드의 유연성과 확장성을 높여줍니다.
예를 들어, 나중에 다른 개발자가 동일한 인터페이스를 구현하는 새로운 CustomerModifyService 구현체를 작성하면,
이를 간단히 교체하여 기존 코드를 변경하지 않고도 다른 구현을 사용할 수 있습니다.
CustomerModifyServiceImpl 클래스를 CustomerModifyService 인터페이스에 의존하도록 구성함으로써,
코드의 결합도를 낮출 수 있습니다. 이는 코드의 유지보수성을 높이고 테스트 용이성을 개선하는 데 도움을 줍니다.
인터페이스를 사용하면 코드의 가독성과 이해도를 높일 수 있습니다. 인터페이스는 코드를 사용하는 측에 어떤 메서드가 제공되는지 명확하게 알려주며, 해당 메서드의 역할과 기능을 인터페이스 자체에서 확인할 수 있습니다. 이는 코드를 이해하고 유지보수하는 개발자에게 도움이 됩니다.
따라서 CustomerModifyServiceImpl 인터페이스를 정의함으로써, 유연성과 확장성, 의존성 분리, 코드 가독성과 이해도 등의 이유로 인터페이스와 구현 클래스로 나누어 코드를 짜보았습니다.
<서비스 구현 클래스 전체 코드>
package com.holdcredit.holdcredit.service.impl;
import com.holdcredit.holdcredit.domain.dto.customerDto.CustomerDto;
import com.holdcredit.holdcredit.domain.dto.customerDto.CustomerModifyDto;
import com.holdcredit.holdcredit.domain.entity.Customer;
import com.holdcredit.holdcredit.repository.CustomerModifyRepository;
import com.holdcredit.holdcredit.repository.CustomerRepository;
import com.holdcredit.holdcredit.service.CustomerModifyService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
@Transactional
@Service
@RequiredArgsConstructor
public class CustomerModifyServiceImpl implements CustomerModifyService {
private final CustomerRepository customerRepository;
private final CustomerModifyRepository customerModifyRepository;
private final PasswordEncoder passwordEncoder;
/* <서비스에서 하는일!>
dto에서 수정 할 회원 정보를 입력하면 그것을 핸들러 한번 거쳐서 엔티티로 바꾼 객체를 customerEntity에 저장한다*/
@Override
public CustomerDto getCustomer(CustomerDto customerDto) {
// 미사용
return null;
}
@Override
public CustomerDto read(Long id) {
Optional<Customer> optionalCustomer = customerRepository.findById(id);
if (optionalCustomer.isPresent()) {
Customer customer = optionalCustomer.get();
return customer.toDto();
} else {
return null;
}
}
// 회원 정보 수정
@Override
public void updateCustomer(Long id,CustomerModifyDto requestDto) {
Customer customer = customerRepository.findById(id).get();
customer.updateCustomer(requestDto, passwordEncoder);
customerModifyRepository.save(customer);
}
//회원 정보 삭제
@Override
public void deleteCustomer(Long id){
customerModifyRepository.deleteById(id);
// customerModifyRepository.deleteByIdAndPassword(id, modifyDto.getPassword());
}
//회원탈퇴 비밀번호 로직 맞는지
@Override
public boolean verifyCustomerPassword(Long customerId, String password) {
Optional<Customer> optionalCustomer = customerModifyRepository.findById(customerId);
System.out.println("service...");
System.out.println(passwordEncoder.matches(password, optionalCustomer.get().getPassword()));
if (optionalCustomer.isPresent()) {
Customer customer = optionalCustomer.get();
return passwordEncoder.matches(password, customer.getPassword());
}
return false;
}
}
여기 전체 코드 중에서 read 만 보겠습니다.
//회원정보 읽기
@Override
public CustomerDto read(Long id) {
Optional<Customer> optionalCustomer = customerRepository.findById(id);
if (optionalCustomer.isPresent()) {
Customer customer = optionalCustomer.get();
return customer.toDto();
} else {
return null;
}
}
customerRepository를 사용하여 주어진 id로 고객을 조회합니다.
jpa에서 기본적으로 crud를 제공하기 때문에 레포지토리에서 아이디를 찾습니다.
Optional 객체는 값이 있을 수도 있고 없을 수도 있는 상황에서 사용되는 자바의 클래스로 알고 있습니다.
그렇기 때문에 해당 코드에서 Optional 객체는 customerRepository.findById(id) 메서드의 반환값으로 사용되고 있습니다.
Optional 객체를 사용하면 null 체크를 명시적으로 수행하지 않아도 됩니다.
만약 customerRepository.findById(id) 메서드가 고객을 찾지 못하면 Optional.empty()를 반환하므로,
개발자는 null 체크를 수행하는 추가적인 코드를 작성하지 않아도 됩니다!!
그래서 Optional을 사용하였습니다.
return customer.toDto() 구문은 Customer 객체를 CustomerDto 객체로 변환하여 반환하는 코드입니다.
해당 코드는 read(Long id) 메서드에서 호출되며, 고객 정보를 찾은 후에 해당 고객 정보를 CustomerDto로 변환하여 반환합니다.
toDto()는 메서드 이기 때문에 커스토머 엔티티에 가보면 엔티티를 dto로 변환시켜 주는 메서드가 있을 겁니다.
한번 거기로 가봅시다!
3. Customer
package com.holdcredit.holdcredit.domain.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.holdcredit.holdcredit.domain.dto.customerDto.CustomerDto;
import com.holdcredit.holdcredit.domain.dto.customerDto.CustomerListDto;
import com.holdcredit.holdcredit.domain.dto.customerDto.CustomerModifyDto;
import com.holdcredit.holdcredit.domain.entity.enumeration.EducationLevel;
import com.holdcredit.holdcredit.domain.entity.enumeration.Gender;
import com.holdcredit.holdcredit.domain.entity.enumeration.JobDomain;
import com.holdcredit.holdcredit.domain.entity.enumeration.Authority;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.persistence.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@SequenceGenerator(sequenceName ="CUSTOMER_SEQ", initialValue = 1000001, allocationSize = 1, name ="CUSTOMER_SEQ_GENERATOR") //G_generator = S_name
public class Customer {
@Id //회원번호
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CUSTOMER_SEQ_GENERATOR")
@Column(name ="customer_no")
private Long id;
@Column(nullable = false, length = 500)
private String password;
@Column(nullable = false, length = 20)
private String customerName;
@Column(nullable = false)
private LocalDate birth; //LocalDateTime 이 아니라 LocalDate만 받아야합니다.
@Column(nullable = false)
@Enumerated(EnumType.STRING)
private Gender gender;
@Column(nullable = false)
private String phoneNum;
@Column(nullable = false, length = 30)
private String email;
@Builder.Default
private LocalDateTime joinDate = LocalDateTime.now(); //현재 시간으로 바로 저장.
@Builder.Default
private LocalDateTime updateDate = LocalDateTime.now();
/*
* java.util.Date 클래스는 더 이상 권장되지 않는 클래스이며, 대신 java.time.LocalDateTime 클래스를 사용하는 것이 좋습니다.
* LocalDateTime 클래스는 java.util.Date 클래스보다 더 간결하고 안정적인 API를 제공합니다.
* 위와 같이 joinDate 필드를 LocalDateTime 으로 선언하였습니다.
* 현재 날짜와 시간으로 초기화됩니다.
* */
@Builder.Default
@Enumerated(EnumType.STRING)
private JobDomain job = JobDomain.ETC; // 기본값 : '기타'로 저장
@Builder.Default
@Enumerated(EnumType.STRING)
private EducationLevel educationLevel = EducationLevel.UNIVERSITY; // 기본값 : '고졸'로 저장
@Builder.Default
@Enumerated(EnumType.STRING)
private Authority authority = Authority.CUSTOMER; // 기본값 : 자동 'CUSTOMER'로 저장
// private UserLevel userLevel = AUTHORITY; // AUTHORITY ㅁ
/* ================================================================================= */
/* 연관 관계 설정 */
// FAQ
@Builder.Default
@OneToMany(mappedBy = "customer")
private List<Faq> faqs = new ArrayList<>();
// Notice
@Builder.Default
@OneToMany(mappedBy = "customer")
@JsonIgnore
private List<Notice> notices = new ArrayList<>();
// QNA
@Builder.Default
@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Qna> qnas = new ArrayList<>();
//부채수준
@Builder.Default
@OneToMany(mappedBy = "customer")
@JsonIgnore
private List<Debt> debts = new ArrayList<>();
//신용카드 형태
@Builder.Default
@OneToMany(mappedBy = "customer")
@JsonIgnore
private List<CreditCard> creditCards = new ArrayList<>();
// Score
@OneToOne(mappedBy = "customer")
@JsonIgnore
private Score score;
//회원 수정
public CustomerDto toDto(){
return CustomerDto.builder()
.customer_no(id)
.password(password)
.customer_name(customerName)
.birth(birth)
.gender(gender)
.phone_num(phoneNum)
.email(email)
.join_Date(joinDate)
.authority(authority)
.education_level(educationLevel)
.job(job)
.build();
}
public void updateCustomer(CustomerModifyDto requestDto, PasswordEncoder passwordEncoder){
this.password = passwordEncoder.encode(requestDto.getPassword());
this.phoneNum = requestDto.getPhone_num();
this.email = requestDto.getEmail();
this.job = requestDto.getJob();
this.educationLevel = requestDto.getEducation_level();
}
}
이 무시무시한 코드에서 저희는 toDto()만 보도록 하겠습니다.
//회원 수정
public CustomerDto toDto(){
return CustomerDto.builder()
.customer_no(id)
.password(password)
.customer_name(customerName)
.birth(birth)
.gender(gender)
.phone_num(phoneNum)
.email(email)
.join_Date(joinDate)
.authority(authority)
.education_level(educationLevel)
.job(job)
.build();
}
toDto() 메서드는 Customer 객체를 CustomerDto 객체로 변환하는 로직을 포함하고 있습니다.
해당 메서드는 Customer 객체의 필드 값을 사용하여 CustomerDto 객체를 생성하고 반환합니다.
각 필드 값들을 CustomerDto의 빌더를 사용하여 설정한 후에 최종적으로 build() 메서드를 호출하여 CustomerDto 객체를 생성하였습니다.
예를 들어, id 필드 값은 customer_no로 설정되고, password 필드 값은 password로 설정됩니다. 나머지 필드 값들도 마찬가지로 해당하는 필드명으로 설정됩니다.
따라서, toDto() 메서드를 호출하면 Customer 객체의 필드 값들을 기반으로 한 CustomerDto 객체가 생성되고 반환됩니다.
이렇게 변환한 이유를 궁금해하실 겁니다!
CustomerDto는 데이터 전송을 위한 객체로 사용될 수 있습니다.
Customer 객체와 CustomerDto 객체는 역할과 책임이 다르기 때문에,
필요한 필드만을 선택하여 CustomerDto로 전달하는 것이 좋을 것 같다고 판단하였습니다.
이를 통해 불필요한 정보 노출을 방지하고 필요한 데이터만 전송할 수 있습니다.

자, 이제 여기까지 따라오시느라 수고 많으셨습니다.
백엔드 서버에는 해당 url을 치게 된다면 어떤 식으로 보이는지 보여드리겠습니다.
✅ 데이터베이스
1000004번째 고객의 정보를 읽어보겠습니다! (READ!)
✅ 검색창에 내가 만든 HTTP URL 입력!
http://localhost:8080/customerModify/1000004
이렇게 저희가 정의한 /customerModify/{id}
고객번호를 입력을 하면!! 짠 하고 제이슨 형태로 백서버에 잘 넘어가는 것을 볼 수 있습니다.
정말 이럴 때마다 기분이 얼마나 좋은지~

이렇게 원하는 아이디의 정보를 dto에 담아서 서버로 전달을 하였습니다.
그럼 이제 이것을 예쁘게 화면에 보여주기 위해서는 해당 {키:벨류}로 되어있으니까
키값으로 꺼내면 벨류가 나올 것입니다.
그럼 여기까지!
다음 포스팅에서는 보내준 이 객체들을 어떻게 리액트에서 받아오는지
알아보도록 하겠습니다.
수고하셨습니다~! 😄
✩다음 포스팅 아래 클릭 클릭~✩
Final Project 정리 _ react에서 서버 정보 받아오기
안녕하세요 jju_developer입니다~ 지난 시간에 회원정보를 read 하는 것을 배우고 url에 제이슨 객체로 값이 전달이 된 것을 보았습니다. 아직 안보신 분들은 아래를 클릭해 주세요~^^ final project 정리_c
jju240.tistory.com
<controller 전체 코드 보기>
package com.holdcredit.holdcredit.controller;
import com.holdcredit.holdcredit.domain.dto.customerDto.CustomerDto;
import com.holdcredit.holdcredit.domain.dto.customerDto.CustomerModifyDto;
import com.holdcredit.holdcredit.domain.entity.Customer;
import com.holdcredit.holdcredit.service.CustomerModifyService;
import com.holdcredit.holdcredit.service.impl.CustomerModifyServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequiredArgsConstructor
@RequestMapping("/customerModify")
public class CustomerModifyController {
private final CustomerModifyService customerModifyService;
//원하는 회원의 고객 번호를 일단 주소창에 치면 그 회원의 정보를 다 가져올 수 있도록 1차 설계 진행.
@GetMapping("/{id}")
public ResponseEntity<CustomerDto> read(@PathVariable Long id){
CustomerDto customerDto = customerModifyService.read(id);
if (customerDto != null){
return new ResponseEntity<>(customerDto, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
//회원 정보 수정
@PutMapping("/Modify/{id}")
public ResponseEntity<?> customerModify(@PathVariable Long id, @Validated @RequestBody CustomerModifyDto requestDto) {
customerModifyService.updateCustomer(id, requestDto);
Map<String, Object> map = new HashMap<>();
map.put("message", "JJU 성공");
System.out.println("requestDto = " + requestDto);
return new ResponseEntity<>(map, HttpStatus.OK);
}
//회원 정보 삭제
@DeleteMapping("/delete/{id}")
public ResponseEntity<?> deleteCustomer(@PathVariable Long id, @RequestParam String password) {
boolean isPasswordCorrect = customerModifyService.verifyCustomerPassword(id, password);
if (!isPasswordCorrect) {
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("message", "Incorrect password");
return new ResponseEntity<>(errorResponse, HttpStatus.UNAUTHORIZED);
}
customerModifyService.deleteCustomer(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}
'☞Project' 카테고리의 다른 글
Final Project 정리_controller 회원정보 수정 (0) | 2023.07.17 |
---|---|
Final Project 정리 _ react에서 서버 정보 받아오기 (0) | 2023.07.17 |
Introducing "HOLD CREDIT"_Your Gateway to Credit Evaluation (22) | 2023.06.27 |