인프런 커뮤니티 질문&답변

김지원님의 프로필 이미지
김지원

작성한 질문수

실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발

회원 기능 테스트

flush() 전에 Insert 쿼리 출력되는 현상

해결된 질문

작성

·

1K

0

[질문 템플릿]
1. 강의 내용과 관련된 질문인가요? (예/아니오) 예
2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오) 예
3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오) 예

[질문 내용]

강의에서는 flush()를 호출하기 전에는 insert 문이 나가지 않는다고 하셨는데, 저 같은 경우에는 위와 같이 작성해도 insert 문이 콘솔에 출력됩니다. 이유가 궁금합니다.

 

답변 4

1

안녕하세요, 김지원 님. 공식 서포터즈 y2gcoder 입니다.

보내주신 프로젝트 확인해보았습니다.

먼저 문제점과 큰 관련은 없으나 앞으로의 수업을 위해서라도 src/main/resources/application.yml은 살려두시길 권해드립니다. 이번 강의에서 src/test/resources/application.yml을 생성하기는 했으나 실제 어플리케이션 실행 때에 적용할 설정도 필요하기 때문입니다.

처음 말씀해주신대로 회원가입() 테스트를 진행했을 때 말씀하셨던 것과 동일한 오류가 발생하여 찾아보니, 강의와 다른 점이 2가지 있었습니다.

  1. MemberServiceTest의 @Transactional의 import가 javax,transactional.Transactional로 되어있음.

  2. Member 엔티티의 기본키 생성 전략이 기본값이 아닌 IDENTITY로 되어있음.

 

1번은 사실 해당 문제에 대한 원인은 아니고 그냥 고쳐주시면 될 것 같습니다.
2번이 근본적인 원인이었습니다. DB 마다 다른 기본키 생성 전략이 다른 것은 알고 계실 거라 생각합니다. AUTO는 기본키 생성전략을 DB에 따라 다르게 적용하는데, H2의 기본키 생성전략 기본값은 SEQUENCE 입니다. 김지원님은 IDENTITY로 명시해주셨습니다.

@Entity
@Getter @Setter
public class Member {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "member_id")
    private Long id;

    private String name;

    @Embedded
    private Address address;

    @OneToMany(mappedBy = "member")
    private List<Order> orders = new ArrayList<>();
}
    //...
    @Test
    public void 회원가입() throws Exception {
        // Given
        Member member = new Member();
        member.setName("kim");

        // When
        Long saveId = memberService.join(member);

        // Then
        assertEquals(member, memberRepository.findOne(saveId));
    }
    //...

기본키 전략이 IDENTITY면 member 객체의 id값을 알기 위해서는 insert 문이 필요합니다. IDENTITY 전략에서는 em.persist 시점에 바로 INSERT SQL을 실행하게 됩니다. 그래서 사진으로 보내주신 것처럼 insert 문이 발생하게 된 것입니다.

만약 강의와 동일하게 @GeneratedValue 로 설정하셨다면, H2 데이터베이스는 기본적으로 SEQUENCE 전략을 취하게 됩니다. 데이터베이스 자체의 시퀀스를 이용하기 때문에 member 객체의 id값을 알기 위해 INSERT SQL이 바로 나가지 않는 대신

image이렇게 call next value for hibernate_sequence 와 같이 시퀀스 값을 부르는 SQL이 나가게 됩니다. 이 부분은 영한님 강의 영상 내에서도 확인하실 수 있습니다 :)

 

결론은 Member 엔티티의 기본키 생성 전략을 바꿔주시면 될 것 같습니다!



감사합니다.

0

안녕하세요. 김지원님, 공식 서포터즈 y2gcoder입니다.

보여주신 코드에서는 말씀하신대로 동작해야 할 것 같다고 생각합니다.

전체 프로젝트를 압축해서 구글 드라이브로 공유해서 링크를 남겨주세요.
구글 드라이브 업로드 방법은 다음을 참고해주세요.

구글 드라이브 업로드 방법 링크

주의: 업로드시 권한 문제 꼭 확인해주세요

추가로 다음 내용도 코멘트 부탁드립니다.

1. 실행 방법을 알려주세요.
2. 어떻게 문제를 확인할 수 있는지 자세한 설명을 남겨주세요.

감사합니다.

김지원님의 프로필 이미지
김지원
질문자

https://drive.google.com/file/d/1aasi0kR5Mhqy7E3LhsxWIECualWdNpng/view?usp=sharing

  1. MemberServiceTest - 회원가입() 실행

  1. 회원가입() 실행 시 콘솔에 insert 문이 출력 됩니다.

0

김지원님의 프로필 이미지
김지원
질문자

package jpabook.jpashop.respository;

import jpabook.jpashop.domain.Member;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

import javax.persistence.EntityManager;
import java.util.List;

@Repository
@RequiredArgsConstructor
public class MemberRepository {

    private final EntityManager em;

    public void save(Member member) {
        em.persist(member);
    }

    public Member findOne(Long id) {
        return em.find(Member.class, id);
    }

    public List<Member> findAll() {
        return em.createQuery("select m from Member m", Member.class) // 엔티티 객체에 대해 조회
                .getResultList();
    }

    public List<Member> findByName(String name) {
        return em.createQuery("select m from Member m where m.name = :name", Member.class)
                .setParameter("name", name)
                .getResultList();
    }
}

0

김지원님의 프로필 이미지
김지원
질문자

package jpabook.jpashop.service;

import jpabook.jpashop.domain.Member;
import jpabook.jpashop.respository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Transactional(readOnly = true) // jpa 조회 성능 최적화
@RequiredArgsConstructor
public class MemberService {

    private final MemberRepository memberRepository;

    /**
     * 회원 가입
     */
    @Transactional // jpa의 데이터 변경이나 모든 로직들은 가급적이면 transaction 안에서 실행되어야 한다.
    public Long join(Member member) {
        validateDuplicateMember(member);
        memberRepository.save(member);
        return member.getId();
    }

    private void validateDuplicateMember(Member member) {
        List<Member> findMembers = memberRepository.findByName(member.getName());
        if (!findMembers.isEmpty()) {
            throw new IllegalStateException("이미 존재하는 회원입니다.");
        }
    }

    // 회원 전체 조회
    public List<Member> findMembers(){
        return memberRepository.findAll();
    }

    public Member findOne(Long memberId) {
        return memberRepository.findOne(memberId);
    }
}

자세히 파악은 못했지만 join 메소드의 Transactional 애노테이션을 제거하고 테스트해보시겠어요?

김지원님의 프로필 이미지
김지원

작성한 질문수

질문하기