inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

스프링 DB 2편 - 데이터 접근 활용 기술

@BeforeEach 사용할 때 트랜젝션 사이클 문의

832

김도연

작성한 질문수 10

1

안녕하세요!

테스트 케이스에 @BeforeEach로 데이터 삽입 후 처리하는 중 오류가 발생해서 문의 드립니다.

해당 오류 : java.util.NoSuchElementException: No value present

클래스 레벨에 @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());
    }
}

spring

답변 1

1

나무늘보

안녕하세요, 김도연 님! 공식 서포터즈 codesweaver 입니다.

질문을 길게 작성해주셨는데 사실 답은 간단합니다. 'sequence 는 롤백되지 않는다'는 규칙이 데이터베이스에 있기 때문입니다.

sequence(mysql 계열이라면 auto_increment로 생성한 값)은 commit, rollback 에 상관없이 한번 생성한 값을 그대로 유지합니다.

 

만약 누군가가 sequence 값을 가져갔는데 이 값을 rollback 할지 commit 할지 몰라 기다려야 한다면, 다음 요청자에게 어떤 sequence 값을 줘야 할 지 판단할 수 없겠죠?

이 규칙은 이처럼 여러 요청이 동시에 들어올 때 최대한 lock 을 피하고, 데이터 정합성을 보장하기 위함으로 이해하시면 됩니다.



감사합니다.

0

김도연

안녕하세요 codesweaver님!

그러면 시퀀스 값은 롤백이 안되는 상태고 그 외에 다른 엔티티값만 롤백이 된다고 이해하면 될까요?이건 그러면 BeforeEach가 아니라 initDb처럼 데이터를 넣어서 사용해야겠네요!! 감사합니다.

설정 정보 없이 임베디드 데이터베이스 생성

0

41

1

RepositoryTest의 패키지 위치가 domain인 이유

0

60

2

REQUIRES_NEW 해결 방법에 대해서 질문있습니다!!

0

50

1

update()에 사용하는 setter 질문드립니다.

0

63

1

SQL 중심적 개발의 문제점에 대한 질문

0

95

1

혹시 Containing 을 안쓰신 이유가 있을까요?

0

96

2

[공유] 스프링부트 4.x 버전 mybatis 연동

0

213

1

@repository 어노테이션

0

111

3

ItemService

0

67

1

논리 커밋, 물리 커밋 질문드립니다.

0

59

1

내부 트랜잭션 커밋은 필수인가요?

0

64

1

프록시 커넥션 객체를 반환할 때 생성하는건가요?

0

63

1

Transaction readOnly 성능 개선 (김영한님의 대한 감사인사)

2

192

2

JPQL 대신 네이티브 쿼리를 사용해야 하는 경우

0

88

1

@EventListener(ApplicationReadyEvent.class) 관련

0

100

1

트랜잭션 동기화 매니저와 데이터 소스

0

83

1

DB 관련 강의 개설 계획은 없으신건가요?

0

141

2

물리 트랜잭션 과 논리트랜잭션 용어를 맞게 이해한걸까요

0

102

1

스프링 3 버전 이상 rollbackFor 변경된듯요

1

125

1

트랜잭션 전파 질문.

0

95

1

프로젝트 오픈 에러

0

138

1

외부 트랜잭션에서 isNewTransaction이 false로 나오는거에 대해 질문드립니다

0

87

2

같은 스레드를 사용하면 트랜잭션 동기화 매니저는 같은 커넥션을 반환

0

80

1

h2 인메모리 테스트중 예약어 충돌날 경우 대처방법

0

107

1