작성
·
759
1
안녕하세요!
테스트 케이스에 @BeforeEach로 데이터 삽입 후 처리하는 중 오류가 발생해서 문의 드립니다.
해당 오류 : java.util.NoSuchElementException: No value present
통합 테스트에서만 발생하고 개별로 메소드마다 돌리면 문제 없음.
제가 이해하기론 Test class에 @Transactional을 붙여주고 돌리면 테스트 메소드 1개 실행 후 정상 처리되면 롤백. 이렇게 이해하고 있습니다. 그래서 제 테스트가 돌아가는 사이클을
@BeforeEach - > 좋아요_개수 Test -> 정상 처리 롤백
@BeforeEach - > 좋아요_여부 Test -> 정상 처리 롤백
@BeforeEach - > 좋아요_체크 Test -> 정상 처리 롤백
@BeforeEach - > 좋아요_해제 Test -> 정상 처리 롤백
위와 같이 이해하고 테스트들마다 정상 처리 롤백 후 비어있는 데이터에 다시 @BeforeEach로 데이터를 넣어주기에 넣어주는 데이터들의 ID값(GeneratedValue)이 1부터 시작하는 걸로 이해했습니다.
그런데 등록된 데이터의 ID값으로 findOne을 하니 해당 ID가 없다고 뜹니다.
무슨 문제인가..하고 디버깅을 돌리니
1번 좋아요_개수 테스트의 business ID = 1번, 2번
2번 좋아요_여부 테스트의 business ID = 3번, 4번
3번 좋아요_체크 테스트의 business ID = 5번, 6번
4번 좋아요_해제 테스트의 business ID = 7번, 8번
이렇게 ID값이 부여가 됩니다. 롤백 자체가 되지 않았다면 1번을 조회했을 때도 데이터는 나와야 하는데 그건 나오지 않고 ID값만 다음 숫자로 넘어가는데 제가 무언가 놓치고 있을까요? 선생님의 강의는 전부 결제해서 트랜젝션 관련 부분을 아무리 들어봐도 아직 미숙해서 그런지 해당 부분의 문제를 모르겠습니다..
클래스 레벨에 @Transactional을 붙여주면 내부에 before() 메소드 안에 트랜젝션들도 전파되고, 정상 처리되는 로직이라면 데이터 등록 후 좋아요_ㅁㅁ Test 로 넘어가고, 그 안에도 정상처리 되면 클래스 레벨에 물리 트랜젝션 커밋 처리 후 다음 테스트 로직이 실행 되는 걸로 이해 했는데 잘못 이해했다면 말씀 부탁드립니다ㅠ.ㅠ
BusinessMarkServiceTest
@SpringBootTest
@Transactional
public class BusinessMarkServiceTest {
@Autowired
BusinessMarkService businessMarkService;
@Autowired
MemberService memberService;
@Autowired
BusinessService businessService;
@Autowired
LoginService loginService;
@BeforeEach
public void before() {
Member member = new Member();
member.setNickname("testMember");
member.setMail("testMember@test.com");
member.setPassword("test1234!");
member.setMember_type("B");
member.setMember_status("J");
member.setHint_password("hint_01");
member.setAnswer_password("answer");
member.setUpdated_at(now());
member.setCreated_at(now());
memberService.join(member);
Member member2 = new Member();
member2.setNickname("testMember2");
member2.setMail("testMember2@test.com");
member2.setPassword("test1234!");
member2.setMember_type("B");
member2.setMember_status("J");
member2.setHint_password("hint_01");
member2.setAnswer_password("answer");
member2.setUpdated_at(now());
member2.setCreated_at(now());
memberService.join(member2);
Business business = new Business();
business.setBusinessName("테스트밥집");
business.setHomepage("test.com");
business.setPhone("010-1234-5678");
business.setAddress("제주특별자치도 제주시 첨단로 242");
business.setLng((float) 33.450701);
business.setLat((float) 126.570667);
business.setCreated_at(now());
business.setUpdated_at(now());
businessService.join(business);
Business business2 = new Business();
business2.setBusinessName("테스트밥집2");
business2.setHomepage("test2.com");
business2.setPhone("010-1234-5678");
business2.setAddress("제주특별자치도 제주시 첨단로 242");
business2.setLng((float) 33.450701);
business2.setLat((float) 126.570667);
business2.setCreated_at(now());
business2.setUpdated_at(now());
businessService.join(business2);
}
@Test
public void 좋아요_체크() throws Exception{
//given
Member loginMember = loginService.login("testMember2@test.com", "test1234!");
Business findBusiness = businessService.findOne(1L);
//when
BusinessMarkDto businessMarkDto = new BusinessMarkDto(loginMember,findBusiness.getId());
businessMarkService.pushBusinessMark(businessMarkDto);
//then
assertEquals(1, businessMarkService.getBusinessLikeInfo(businessMarkDto).getBusinessLikeNum());
}
@Test
public void 좋아요_해제() throws Exception{
//given
Member loginMember = loginService.login("testMember@test.com", "test1234!");
Member loginMember2 = loginService.login("testMember2@test.com", "test1234!");
Business findBusiness = businessService.findOne(1L);
//좋아요 표시하기
BusinessMarkDto businessMarkDto = new BusinessMarkDto(loginMember,findBusiness.getId());
businessMarkService.pushBusinessMark(businessMarkDto);
BusinessMarkDto businessMarkDto2 = new BusinessMarkDto(loginMember2,findBusiness.getId());
businessMarkService.pushBusinessMark(businessMarkDto2);
//when
//businessMarkDto2가 한번 더 좋아요 눌러서 해제 시키기
businessMarkService.pushBusinessMark(businessMarkDto2);
//then
assertEquals(1, businessMarkService.getBusinessLikeInfo(businessMarkDto).getBusinessLikeNum());
}
@Test
public void 좋아요_개수() throws Exception{
//given
Member loginMember = loginService.login("testMember@test.com", "test1234!");
Member loginMember2 = loginService.login("testMember2@test.com", "test1234!");
Business findBusiness = businessService.findOne(1L);
//좋아요 표시하기
BusinessMarkDto businessMarkDto = new BusinessMarkDto(loginMember,findBusiness.getId());
businessMarkService.pushBusinessMark(businessMarkDto);
BusinessMarkDto businessMarkDto2 = new BusinessMarkDto(loginMember2,findBusiness.getId());
businessMarkService.pushBusinessMark(businessMarkDto2);
//when
long businessMarkNum = businessMarkService.getBusinessLikeInfo(businessMarkDto2).getBusinessLikeNum();
//then
assertEquals(2, businessMarkNum);
}
@Test
public void 좋아요_여부() throws Exception{
//given
Member loginMember = loginService.login("testMember@test.com", "test1234!");
Business findBusiness = businessService.findOne(1L);
//when
BusinessMarkDto businessMarkDto = new BusinessMarkDto(loginMember,findBusiness.getId());
businessMarkService.pushBusinessMark(businessMarkDto);
Boolean check = businessMarkService.getBusinessLikeInfo(businessMarkDto).getCheck();
//then
assertEquals(true, check);
}
}
BusinessMarkService
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class BusinessMarkService {
private final BusinessMarkRepository businessMarkRepository;
private final BusinessRepository businessRepository;
//좋아요 및 취소
public Boolean pushBusinessMark(BusinessMarkDto businessMarkDto) {
businessMarkRepository.BusinessMarkSearch(businessMarkDto.getMember().getMail(),
businessMarkDto.getBusinessId())
.ifPresentOrElse(businessMark -> businessMarkRepository.deleteById(businessMark.getId()),
()-> {
Business business = getBusiness(businessMarkDto);
businessMarkRepository.save(new BusinessMark(businessMarkDto.getMember(), business));
});
return true;
}
//업체 게시글 찾기
@Transactional(readOnly = true)
public Business getBusiness(BusinessMarkDto businessMarkDto) {
return businessRepository.findById(businessMarkDto.getBusinessId())
.orElseThrow(() -> new IllegalArgumentException("해당 게시글은 존재하지 않습니다."));
}
// 좋아요 개수
@Transactional(readOnly = true)
public BusinessMarkResponseDto getBusinessLikeInfo(BusinessMarkDto businessMarkDto) {
long businessLikeNum = getBusinessLikeNum(businessMarkDto);
boolean check = checkPushedLike(businessMarkDto);
return new BusinessMarkResponseDto(businessLikeNum, check);
}
@Transactional(readOnly = true)
public Boolean checkPushedLike(BusinessMarkDto businessMarkDto) {
return businessMarkRepository.BusinessMarkSearch(businessMarkDto.getMember().getMail(), businessMarkDto.getBusinessId())
.isPresent();
/*Optional<BusinessMark> businessMark =
businessMarkRepository.BusinessMarkSearch(businessMarkDto.getMember().getMail(), businessMarkDto.getBusinessId());
if(businessMark != null){
return true;
}
return false;*/
}
@Transactional(readOnly = true)
public long getBusinessLikeNum(BusinessMarkDto businessMarkDto) {
return businessMarkRepository.BusinessMark(businessMarkDto.getBusinessId());
}
}
MemberService
@Slf4j
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class MemberService {
@Autowired
private final MemberRepository memberRepository;
@Autowired
private PasswordEncoder passwordEncoder;
/** 회원가입 **/
@Transactional
public String join(Member member){
//비밀번호 암호화 후 레포지토리에 넘기기
String encodedPassword = passwordEncoder.encode(member.getPassword());
member.setPassword(encodedPassword);
validateDuplicateMember(member);//중복회원검증
memberRepository.save(member);
return member.getMail();
}
/** 중복회원검증 **/
private void validateDuplicateMember(Member member) {
List<Member> findMembers = memberRepository.findByMail(member.getMail());
if (!findMembers.isEmpty()){
throw new IllegalStateException("이미 존재하는 회원입니다");
}
}
private void validateDuplicateNickname(Member member) {
Member findMembers = memberRepository.findOndByMail(member.getMail());
if (!findMembers.getNickname().equals(member.getNickname())){
throw new IllegalStateException("이미 존재하는 회원입니다");
}
}
/** 회원전체조회 **/
public List<Member> findMembers(){
return memberRepository.findAll();
}
public Member findOne(String mail) {
return memberRepository.findOndByMail(mail);
}
/** 회원 수정 **/
@Transactional
public void update(String mail, String nickname, String answer_password){
Member member = memberRepository.findOndByMail(mail);
if(nickname.equals(member.getNickname())) {
validateDuplicateNickname(member);
}
member.setNickname(nickname);
member.setAnswer_password(answer_password);
member.setUpdated_at(now());
}
/** 회원 탈퇴 **/
@Transactional
public String delete(String mail, String password){
Member findUser = memberRepository.findOndByMail(mail);
if(!passwordEncoder.matches(password,findUser.getPassword())){
//throw new IllegalStateException("비밀번호가 맞지 않습니다.");
System.out.println("암호 실패");
return null;
}
findUser.setUpdated_at(now());
findUser.setMember_status("D");
return findUser.getPassword();
}
}
BusinessService
@Slf4j
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class BusinessService {
@Autowired
private final BusinessRepository businessRepository;
@Autowired
private final MemberRepository memberRepository;
@Autowired
private PasswordEncoder passwordEncoder;
/** 중복업체검증 **/
private void validateDuplicateBusiness(Business business) {
List<Business> findBusiness = businessRepository.findByBusinessName(business.getBusinessName());
if (!findBusiness.isEmpty()){
throw new IllegalStateException("이미 존재하는 업체명입니다");
}
}
/** 업체생성 **/
@Transactional
public String join(Business business){
validateDuplicateBusiness(business);//중복회원검증
log.info("business {}", business);
businessRepository.save(business);
return business.getBusinessName();
}
/** 업체 수정 **/
/** @Transactional
public void saveBusiness(Business business){
businessRepository.save(business);
} **/
@Transactional
public void update(Long id, String BusinessName, String homepage,
String phone, String address, float lat, float lng, String etc){
Business business = businessRepository.findById(id).orElseThrow();
if(!BusinessName.equals(business.getBusinessName())) {
validateDuplicateBusiness(business); //중복 업체명 검증
}
business.setBusinessName(BusinessName);
business.setHomepage(homepage);
business.setPhone(phone);
business.setAddress(address);
business.setLat(lat);
business.setLng(lng);
business.setEtc(etc);
business.setUpdated_at(now());
}
/** 업체 삭제 **/
@Transactional
public String delete(Member member, Long id){
Member findUser = memberRepository.findOndByMail(member.getMail());
//Business business = businessRepository.findById(id).get();
Business business = businessRepository.findById(id).orElseThrow();
if(!passwordEncoder.matches(member.getPassword(),findUser.getPassword())){
//throw new IllegalStateException("비밀번호가 맞지 않습니다.");
System.out.println("암호 실패");
return null;
}
business.setStatus("D");
business.setUpdated_at(now());
return findUser.getPassword();
}
/** 업체 찾기 **/
public Business findOne(Long id) {
Business business = businessRepository.findById(id).orElseThrow();
return business;
}
}
LoginService
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class LoginService {
@Autowired
private final MemberRepository memberRepository;
@Autowired
private PasswordEncoder passwordEncoder;
public Member findOne(String mail){ return memberRepository.findOndByMail(mail);}
/** 로그인 **/
@Transactional
//public String login(Member member){
public Member login(String mail, String password){
List<Member> findMember = memberRepository.findByMail(mail);
Member findUser = memberRepository.findOndByMail(mail);
if (findMember==null){
//throw new IllegalStateException("해당 이메일의 유저가 존재하지 않습니다.");
//System.out.println("이메일 실패");
return null;
}
if(!passwordEncoder.matches(password,findUser.getPassword())){
//throw new IllegalStateException("비밀번호가 맞지 않습니다.");
return null;
}
System.out.println("로그인 완료 :"+findUser.getNickname());
//세션 표시를 위해 닉네임값 넘기기
return findUser;
}
/** 비밀번호 매치 **/
@Transactional
public String passwordMatches(Member member){
Member findUser = memberRepository.findOndByMail(member.getMail());
if(!passwordEncoder.matches(member.getPassword(),findUser.getPassword())){
//throw new IllegalStateException("비밀번호가 맞지 않습니다.");
System.out.println("암호 실패");
return null;
}
return findUser.getPassword();
}
/** 비밀번호 변경 **/
@Transactional
public void updatePassword(String mail, String editPassword){
Member member = memberRepository.findOndByMail(mail);
if (editPassword.equals(member.getPassword())) {
throw new IllegalStateException("이전 비밀번호와 동일합니다.");
}
member.setPassword(editPassword);
member.setUpdated_at(now());
}
}
답변 1
1
안녕하세요, 김도연 님! 공식 서포터즈 codesweaver 입니다.
질문을 길게 작성해주셨는데 사실 답은 간단합니다. 'sequence 는 롤백되지 않는다'는 규칙이 데이터베이스에 있기 때문입니다.
sequence(mysql 계열이라면 auto_increment로 생성한 값)은 commit, rollback 에 상관없이 한번 생성한 값을 그대로 유지합니다.
만약 누군가가 sequence 값을 가져갔는데 이 값을 rollback 할지 commit 할지 몰라 기다려야 한다면, 다음 요청자에게 어떤 sequence 값을 줘야 할 지 판단할 수 없겠죠?
이 규칙은 이처럼 여러 요청이 동시에 들어올 때 최대한 lock 을 피하고, 데이터 정합성을 보장하기 위함으로 이해하시면 됩니다.
감사합니다.
안녕하세요 codesweaver님!
그러면 시퀀스 값은 롤백이 안되는 상태고 그 외에 다른 엔티티값만 롤백이 된다고 이해하면 될까요?이건 그러면 BeforeEach가 아니라 initDb처럼 데이터를 넣어서 사용해야겠네요!! 감사합니다.