개요.

JPA 를 사용하는 도중 프록시라는 것을 사용해야 한다. 하지만 테스트 코드상에서 프록시 객체를 사용하기 위해선 @Transactional 어노테이션이 필요하다. 해당 어노테이션이 테스트 코드에 포함된 경우, 자동으로 테스트 메소드가 완료되면 롤백이 된다.

 

원래는 롤백이 되고, 쿼리가 날라감을 확인하기 위함인데, 나는 쿼리 확인 뿐만 아니라 DB 상에도 데이터를 누적시키고 싶었다. 구글링하니 두가지 어노테이션을 찾을 수 있었다.  두가지 어노테이션을 동일한 기능을 수행한다.

  • @Rollback(false)
  • @Commit

사용방법은 아래와 같다.

 

예시.

@SpringBootTest
@RunWith(SpringRunner.class)
@ActiveProfiles("dev")
@Transactional
public class DummyDatabaseTest {

    @Autowired private LeagueRepository leagueRepository;
    @Autowired private TeamRepository teamRepository;
    @Autowired private MemberRepository memberRepository;

    @Test
    @Commit
    public void 리그_테이블에_데이터를_삽입한다() {

        leagueRepository.save(League.builder().name("라리가").desc("스페인 리그").build());
        leagueRepository.save(League.builder().name("프리미어리그").desc("잉글랜드 리그").build());
        leagueRepository.save(League.builder().name("분데스리가").desc("독일 리그").build());
        leagueRepository.save(League.builder().name("세리에 A").desc("이탈리아 리그").build());
        leagueRepository.save(League.builder().name("리그 1").desc("프랑스 리그").build());
        leagueRepository.save(League.builder().name("에리디비시").desc("네덜란드 리그").build());
        leagueRepository.save(League.builder().name("K 리그").desc("대한민국 리그").build());

    }

    @Test
    @Rollback(false)
    public void 팀_테이블에_데이터를_삽입한다() {

        League spainLeague = leagueRepository.findByName("라리가").get();
        teamRepository.save(Team.builder().league(spainLeague).name("바르셀로나").description("메시를 주축으로 이루는 팀").build());
        teamRepository.save(Team.builder().league(spainLeague).name("레알마드리드").description("예전에 스타군단으로 알고있는 팀").build());
        teamRepository.save(Team.builder().league(spainLeague).name("발렌시아").description("이강인 축구선수가 소속된 팀").build());

        League engLeague = leagueRepository.findByName("프리미어리그").get();
        teamRepository.save(Team.builder().league(engLeague).name("리버풀").description("클롭감독 지휘아래 18/19 챔스 우승 팀").build());
        teamRepository.save(Team.builder().league(engLeague).name("토트넘").description("18/19 챔스 준우승 팀, 손흥민 선수가 소속된 팀").build());
        teamRepository.save(Team.builder().league(engLeague).name("맨체스터 Utd").description("박지성 선수가 한 때 뛰었던 팀").build());

    }

    @Test
    @Commit
    public void 멤버_테이블에_데이터를_삽입한다() {

        Team fcBarcelona = teamRepository.findByName("바르셀로나").get();
        memberRepository.save(Member.builder().team(fcBarcelona).name("리오넬 메시").description("현존하는 바르셀로나의 전설적인 선수").build());
        memberRepository.save(Member.builder().team(fcBarcelona).name("앙투안 그리즈만").description("프랑스 선수이며, 세컨드 스트라이커").build());
        memberRepository.save(Member.builder().team(fcBarcelona).name("프랑키 데용").description("네덜란드의 신성").build());

        Team fcRealMadrid = teamRepository.findByName("레알마드리드").get();
        memberRepository.save(Member.builder().team(fcRealMadrid).name("에덴 아자르").description("첼시에서 이적한 선수, 지금 부상때매 못 뛰고 있음").build());
        memberRepository.save(Member.builder().team(fcRealMadrid).name("가레스 베일").description("전 축구선수, 현 골프선수").build());
        memberRepository.save(Member.builder().team(fcRealMadrid).name("카림 벤제마").description("프랑스 국대며 현 레알마드리드 소속").build());

        Team fcTottenham = teamRepository.findByName("토트넘").get();
        memberRepository.save(Member.builder().team(fcTottenham).name("손흥민").description("대한민국 국대며, 탑 윙어").build());
        memberRepository.save(Member.builder().team(fcTottenham).name("무사 시소코").description("토트넘 미드필더").build());
        memberRepository.save(Member.builder().team(fcTottenham).name("위고 요리스").description("토트넘 골키퍼").build());

        Team fcLiverpool = teamRepository.findByName("리버풀").get();
        memberRepository.save(Member.builder().team(fcLiverpool).name("모하메드 살라").description("이집트 메시라고 불림").build());
        memberRepository.save(Member.builder().team(fcLiverpool).name("버질 반 데이크").description("리버풀의 벽").build());
        memberRepository.save(Member.builder().team(fcLiverpool).name("알리송 베케르").description("리버풀 골키퍼").build());

    }
}

 

위와 같이 수행하게 되면, 데이터 인서트에 대한 쿼리가 날라가나 롤백이 되지 않고 커밋이 되어 MySQL 상에 데이터가 들어감을 확인할 수 있다.

 

추가내용.

엔티티 연관관계가 비식별관계로 설정되어 있고, 현재 각각의 엔티티는 cascade = CascadeType.XXX  로 설정해두었다. Remove 를 제외한 모든 것들을 넣어두었다. 이 때, member 엔티티의 save() 메소드 수행 시 select 쿼리가 다량으로 날라가는 것을 확인할 수 있었다. 

 

확인한 내용으로는 CascadeType.MERGE 로 설정되어 있기 때문이었는데, 연관 엔티티가 1차캐시에 없기 때문에 영속상태로 만들어주려고 select 쿼리가 날라가고 있었다. 이에따라 해당 CascadeType.MERGE 를 지우고 나서야 select 쿼리가 다량이 아닌 필요한 부분에 날라감을 확인할 수 있었다.

 

추가로, save() 메소드 수행 시 해당 엔티티의 isNew() 여부를 판단하는데 isNew() 가 true 이면 persist() 를 호출하고 그렇지 않으면 merge() 를 호출하여 select 쿼리가 날라간다. 이러한 부분에 대해 엔티티가 Persistable<ID> 를 구현하는 방법으로 해당 인터페이스이 선언된 메소드 getId() 와 isNew() 메소드를 오버라이딩 하는 방법이 있다. 이와 관련해서 추가적인 공부가 필요하다.

 

참고자료.

https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#spring-testing-annotation-commit

Posted by doubler
,