개요
- User Entity, UserWallet Entity는 현재 OneToOne 관계를 가지고 있습니다.
- User Entity가 생성되는, 생성자에 UserWallet을 넣어 저장하고있습니다.
- 일반 회원가입 시도시 잘되지만, 테스트코드에서의 오류에 대해 짚어보고자 합니다.
시나리오 (Success)
회원가입
public UserResponseDto signUp(final SignupRequestDto dto) {
Boolean flag = userQuery.existsByEmail(dto.getEmail());
// 중복된 이메일이 없을 경우
if (!flag) {
String encodedPassword = pe.encode(dto.getPassword());
User user = User.of(dto,encodedPassword);
User saveUser = userCommand.create(user);
return UserResponseDto.of(saveUser);
}
else {
throw new UserException(EMAIL_ALREADY_EXISTS);
}
}
User.of(...)
// 일반 회원가입 - of
public static User of(SignupRequestDto dto, String encodedPassword){
return new User(
dto.getNickname(),
dto.getEmail(),
encodedPassword,
dto.getPhoneNumber(),
dto.getUserRole(),
dto.getResidentRegistrationNumber(),
dto.getBank(),
dto.getAccountNumber()
);
}
Constructor
// 일반 회원가입
public User(String nickname, String email, String password, String phoneNumber, UserRole userRole, String residentRegistrationNumber, BankType bank, String accountNumber) {
this.nickname = nickname;
this.email = email;
this.password = password;
this.phoneNumber = phoneNumber;
this.userRole = userRole != null ? userRole : UserRole.ROLE_USER; // 기본값 적용
this.authProvider = AuthProvider.GENERAL;
this.residentRegistrationNumber = residentRegistrationNumber;
this.userWallet = UserWallet.of(this,bank,accountNumber);
}
회원가입 성공
User Entity
UserWallet Entity
잘 저장이 되는 모습이다
그 다음은 문제의 테스트코드를 봐보자
시나리오 (Fail)
전체코드
@SpringBootTest // ✅ 실제 빈을 로드하여 통합 테스트
@ActiveProfiles(profiles = "dev") // ✅ application-dev.yml을 사용
public class KakaoPaymentServiceIntegrationTest {
@Autowired
KakaoPaymentService kakaoPaymentService; // ✅ 실제 서비스 빈 주입
@Autowired
UserCommand userCommand;
@Test
@DisplayName("카카오 페이먼트 결제 준비 성공 테스트")
public void test1() {
// given
User user1 = MockUserTestInfo.getUser1();
User user = userCommand.create(user1);
KakaoPaymentReadyRequestDto dto = MockPaymentInfo.getKakaoPaymentReadyRequestDto1();
AuthUser authUser = MockUserTestInfo.getAuthUser1();
// when
KakaoPaymentReadyResponseDto response = kakaoPaymentService.ready(authUser,dto);
// then
assertThat(response).isNotNull();
assertThat(response.getTid()).isNotEmpty(); // ✅ TID 값이 비어있지 않은지 확인
assertThat(response.getNextRedirectPcUrl()).isNotEmpty(); // ✅ PC Redirect URL 확인
assertThat(response.getNextRedirectMobileUrl()).isNotEmpty(); // ✅ Mobile Redirect URL 확인
assertThat(response.getNextRedirectAppUrl()).isNotEmpty(); // ✅ App Redirect URL 확인
}
MockUserTestInfo.getUser1()
// 1. User를 먼저 생성 (Patient와 Caregiver는 나중에 설정)
public static User getUser1() {
return new User(ID_1, NICKNAME_1, EMAIL_1, PHONE_1, PASSWORD_1, ROLE, SSN_1, PROVIDER, NOW, NOW, null, null, BANK, ACCOUNT_NUMBER1);
}
new User(...)
// User Test
public User(Long id, String nickname, String email, String phoneNumber, String password, UserRole userRole, String residentRegistrationNumber, AuthProvider authProvider, LocalDateTime createdAt, LocalDateTime updatedAt, Patient patient, Caregiver caregiver, BankType bank, String accountNumber) {
this.id = id;
this.nickname = nickname;
this.email = email;
this.phoneNumber = phoneNumber;
this.password = password;
this.userRole = userRole;
this.residentRegistrationNumber = residentRegistrationNumber;
this.authProvider = authProvider;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
this.patient = patient;
this.caregiver = caregiver;
this.userWallet = UserWallet.of(this,bank,accountNumber);
}
회원가입 실패
Error Message
UserWallet 생성할떄의 User Entity가 Null이라 Fk로서 참조 할 수 없다는 내용이다.
도대체 뭐가 문제지?
- 가짜 User 객체를 생성할떄, ID를 넣은것이 문제였다 !
// 1. User를 먼저 생성 (Patient와 Caregiver는 나중에 설정)
public static User getUser1() {
return new User(ID_1, NICKNAME_1, EMAIL_1, PHONE_1, PASSWORD_1, ROLE, SSN_1, PROVIDER, NOW, NOW, null, null, BANK, ACCOUNT_NUMBER1);
}
- Jpa는 id값으로 Insert를 할지, update를 할지 구분 짓는다.
- 가짜 객체를 만들어줄떄 id값을 넣어줬으니 update 쿼리가 나갈것이고
- update 쿼리는 id를 먼저 select해서 찾고, 만약 존재하지않는다면 새로 생성하게된다.
- 엥? 그럼 문제 없단거 아닌가요?
- 아니다 밑에 케이스들을 보며 정리해보자
Merge (update) - 문제발생
- id자리에 1을 넣었을떄 테스트코드를 돌리면 아래의 쿼리가 실행된다.
Hibernate:
select
u1_0.id,
... 중략
kpr1_0.vat_amount
from
user u1_0
left join
caregiver c1_0
on c1_0.id=u1_0.caregiver_id
left join
user u2_0
on u2_0.id=c1_0.user_id
left join
patient p2_0
on p2_0.id=u1_0.patient_id
left join
user u3_0
on u3_0.id=p2_0.user_id
left join
user_wallet uw3_0
on uw3_0.id=u1_0.user_wallet_id
left join
user u4_0
on u4_0.id=uw3_0.user_id
left join
kakao_payment_ready kpr1_0
on u1_0.id=kpr1_0.user_id
where
u1_0.id=?
Hibernate:
insert
into
user_wallet
(account_number, balance, bank, user_id)
values
(?, ?, ?, ?)
- id에 값이 존재하기때문에 select 쿼리로 엔티티를 가져온후
- insert를 하려고하는데, user_id는 우리가 가상으로 넣어준 Id 1인 값이기때문에 FK로 설정 할 수 없어 오류가 발생하는것이다.
Insert - 문제해결
- id자리에 1을 빼고 테스트코드를 돌리면 아래의 쿼리가 실행된다.
Hibernate:
insert
into
user_wallet
(account_number, balance, bank, user_id)
values
(?, ?, ?, ?)
Hibernate:
insert
into
user
(auth_provider, caregiver_id, created_at, email, nickname, password, patient_id, phone_number, resident_registration_number, updated_at, user_role, user_wallet_id)
values
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate:
update
user_wallet
set
account_number=?,
balance=?,
bank=?,
user_id=?
where
id=?
- UserWallet을 저장합니다 (User = null)
- User를 저장합니다 (UserWallet = null)
- User에 save가 실행되며 영속화되는 과정에서 UserWallet을 업데이트하여 user_id를 채운모습이다.
결론
- ID값에 데이터를 넣은것과, 안넣은것으로 JPA가 Insert,Update를 구분하여서 처리하고있었기 때문에 오류가 발생했다!
'SpringBoot' 카테고리의 다른 글
Hibernate Lazy Loading: 프록시 객체의 동작 원리와 실제 객체 조회 흐름 (0) | 2025.03.20 |
---|---|
카카오 소셜 로그인시, CORS 문제 발생 (1) | 2025.03.13 |
다양한 상황에서의 DB에 저장하는 시간을 알아보자 (0) | 2025.02.26 |
Redis에 엔티티 저장중 생긴 순환참조문제 (0) | 2025.01.23 |
JSP 란? (1) | 2025.01.21 |