kok202
JPA 양방향 맵핑, Test 코드 작성시 유의사항

2019. 4. 25. 15:05[정리] 기능별 개념 정리/JPA

@Entity
@Table(name="team")
public class Team {
    @Id
    @Column(name="team_id")
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private long id;
    
    @OneToMany(mappedBy="team")
    private List<Member> members;
}
@Entity
@Table(name="member")
public class Member {
    @Id
    @Column(name="member_id")
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private long id;
    
    private String name;
    
    @ManyToOne
    @JoinColumn(name="team_id")
    private Team team;
    
    public Member(String name){ 
        this.name=name; 
    }
}
public interface MemberRepository extends JpaRepository<MemberEntity, Long> {
}
public interface TeamRepository extends JpaRepository<TeamEntity, Long> {
}

위 코드는 문제없다.

 

@RunWith(SpringRunner.class)
@DataJpaTest
@EnableAutoConfiguration
@ContextConfiguration(classes = {MemberEntity.class, MemberRepository.class, TeamEntity.class, TeamRepository.class})
public class IssueGroupRepositoryTest {
    @Autowired
    private MemberRepository memberRepository;

    @Autowired
    private TeamRepository teamRepository;

    private static MemberEntity memberEntityA;
    private static MemberEntity memberEntityB;
    private static MemberEntity memberEntityC;

    @Before
    public void setUp() throws Exception {
        TeamEntity teamA = new TeamEntity("teamA");
        teamRepository.save(teamA);
        
        ArrayList<MemberEntity> members = new ArrayList<>();
        memberEntityA = new MemberEntity("kok202");
        memberEntityB = new MemberEntity("brandon");
        memberEntityC = new MemberEntity("testMember");
        memberEntityA.setGroupId(teamA);
        memberEntityB.setGroupId(teamA);
        memberEntityC.setGroupId(teamA);
        members.add(memberEntityA);
        members.add(memberEntityB);
        members.add(memberEntityC);
        memberRepository.saveAll(issueList);
    }

    @Test
    @Transactional
    public void testLoad() {
        teamRepository.findAll().forEach(team -> {
            System.out.println(team);
        });
    }
}

먼저 다음과 같이 테스트하면 Stackoverflow 에러가 발생한다. testLoad 메소드에서 System.out.println 할 때 발생하는 에러인데, get메소드로 team 인스턴스를 출력할 때 member 리스트를 가지고 있어서 getMembers 로 접근해서 member의 하위 항목을 출력하려한다. 근데 member 는 team 을 가지고 있으므로 getTeam 으로 team 을 출력하려한다. 여기서 사이클이 생겨서 무한루프를 돌고 스택오버플로우 에러가 발생하는 것이다.

 

@RunWith(SpringRunner.class)
@DataJpaTest
@EnableAutoConfiguration
@ContextConfiguration(classes = {MemberEntity.class, MemberRepository.class, TeamEntity.class, TeamRepository.class})
public class IssueGroupRepositoryTest {
    @Autowired
    private MemberRepository memberRepository;

    @Autowired
    private TeamRepository teamRepository;

    @Before
    public void setUp() throws Exception {
        TeamEntity teamA = new TeamEntity("teamA");
        teamRepository.save(teamA);
        
        ArrayList<MemberEntity> members = new ArrayList<>();
        MemberEntity memberEntityA = new MemberEntity("kok202");
        MemberEntity memberEntityB = new MemberEntity("brandon");
        MemberEntity memberEntityC = new MemberEntity("testMember");
        memberEntityA.setGroupId(teamA);
        memberEntityB.setGroupId(teamA);
        memberEntityC.setGroupId(teamA);
        members.add(memberEntityA);
        members.add(memberEntityB);
        members.add(memberEntityC);
        memberRepository.saveAll(issueList);
    }

    @Test
    @Transactional
    public void testLoad() {
        teamRepository.findAll().forEach(team -> {
            team.getMembers.forEach(member -> {
                System.out.println(member.getName);
            });
        });
    }
}

스택 오버 플로우 문제해결 : 그냥 간단한 예제를 출력해보고 싶은 것이므로 System.out.println 을 조심히 사용하기로 했다. 아니면 사이클이 생기는 곳에 @JsonIgnore를 줘도 된다고 한다.

 

그런데 테스트 코드를 돌리면 kok202, brandon, testMember 이렇게 3개의 결과가 출력되기를 바라는데  아무것도 출력이 되지 않는다. 데이터 삽입이 제대로 이뤄지지 않은건가 싶지만, MappedBy도 제대로 지정해줬고, 관계의 주인쪽에서 데이터를 제대로 삽입해줬으니 데이터 삽입하는 코드쪽에서는 문제가 없다. DataJpaTest가 아닌 MySQL로 코드를 돌려보고 실제 데이터가 어떻게 들어갔는지 확인해보니 테이블에도 레코드가 제대로 들어간것을 확인했다. 외래키도 제대로 참조하고 있다.

 

디버깅 포인트 : Select 쿼리 자체가 날라가지 않았다. -> 영속성 컨텍스트에 teamA 객체가 여전히 남아있기 때문에 발생한 문제다. DB에 삽입된 정보를 바탕으로 값을 가져오는게 아니라 영속성 컨텍스트의 teamA를 바로 가져오고 있다.

 

@RunWith(SpringRunner.class)
@DataJpaTest
@EnableAutoConfiguration
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@ContextConfiguration(classes = {MemberEntity.class, MemberRepository.class, TeamEntity.class, TeamRepository.class})
public class IssueGroupRepositoryTest {
    @Autowired
    private MemberRepository memberRepository;

    @Autowired
    private TeamRepository teamRepository;
    
    @Test
    @Transactional
    @Rollback(value = false)
    public void a_testSave(){
        TeamEntity teamA = new TeamEntity("teamA");
        teamRepository.save(teamA);
        ArrayList<MemberEntity> members = new ArrayList<>();
        MemberEntity memberEntityA = new MemberEntity("kok202");
        MemberEntity memberEntityB = new MemberEntity("brandon");
        MemberEntity memberEntityC = new MemberEntity("testMember");
        memberEntityA.setGroupId(teamA);
        memberEntityB.setGroupId(teamA);
        memberEntityC.setGroupId(teamA);
        members.add(memberEntityA);
        members.add(memberEntityB);
        members.add(memberEntityC);
        memberRepository.saveAll(issueList);
    }

    @Test
    @Transactional
    @Rollback(value = false)
    public void b_testLoad() {
        teamRepository.findAll().forEach(team -> {
            team.getMembers.forEach(member -> {
                System.out.println(member.getName);
            });
        });
    }
}

두개의 트랜잭션 테스트를 만들고 롤백을 false로 지정해줘서 테스트 코드의 결과가 살아있도록 해줬다. testSave 다음에 testLoad 가 실행됬으면 했기 때문에 @FixMethodOrder(MethodSorters.NAME_ASCENDING) 를 해주고 앞에 test 메소드 앞에 prefix 로 a_, b_ 와 같이 달아줘서 테스트 코드가 순차 실행되도록 하였다. -> 해결