본문 바로가기
IT 개인 공부/Spring

[Spring] 양방향 매핑시 주의점 : toString

by Libi 2021. 9. 15.
반응형

이름과 나이를 멤버 변수로 가지는 Member라는 클래스가 있다고 하자.

public class Member {

    private String name;
    private int age;
    
    //Getter & Setter
}

 

Member의 정보를 눈으로 확인하고 싶은 경우 다음과 같은 방식을 사용할 것이다.

System.out.println("Member [name=" + member.getName() + "] [age=" + member.getAge() + "]");

 

하지만 이 방법은 매번 코드를 작성해야 하는 번거로움이 존재한다.

이는 toString() 메서드를 재정의하여 재사용하는 방식으로 해결해줄 수 있다.

@Override
public String toString() {
    return "Member{" +
           "name='" + name + '\'' +
           ", age=" + age +
           '}';
}

 

또한, Lombok 라이브러리를 사용하면 toString()를 직접 구현하지 않고도 사용할 수 있다.

@Getter @Setter @ToString
public class Member {

    private String name;
    private int age;
}

 

그렇다면 이번에는 Team이라는 클래스가 하나 추가되었으며 Member와 Team은 양방향 관계로 서로 참조하는 상황이라고 하자.

@Getter @Setter @ToString
public class Member {

    private String name;
    private Team team; //Team 참조
}

@Getter @Setter @ToString
public class Team {

    private String name;
    private Member member; //Member 참조
}

 

Member 객체나 Team 객체를 통해 toString() 메서드를 호출하게 되면 어떤 문제가 발생할까?

class ToStringTest {

    @Test
    void toStringTest() {
        Member member = new Member();
        member.setName("member");
        Team team = new Team();
        team.setName("team");

        //양방향 관계 매핑
        member.setTeam(team);
        team.setMember(member);

        System.out.println("member = " + member);
        System.out.println("team = " + team);
    }
}

 

실행하면 정상적인 출력이 아닌 StackOverflowError가 발생하는 것을 확인할 수 있다.

이러한 에러가 발생하는 이유는 무엇일까?

 

@ToString을 통해 구현된 실제 toString() 메서드는 각각 다음과 같이 구현되어 있을 것이다.

-------- Member -----------
@Override
public String toString() {
    return "Member{" +
           "name='" + name + '\'' +
           ", team=" + team +
           '}';
}

-------- Team -----------
@Override
public String toString() {
    return "Team{" +
           "name='" + name + '\'' +
           ", member=" + member +
           '}';
}

 

Member에서 toString() 메서드를 호출하면 name과 team을 호출하게 된다. 이때 team 객체의 Team 클래스에 toString() 메서드가 재정의되어있다면 team의 멤버들을 출력하는 toString() 메서드가 추가적으로 호출된다.

Team 클래스의 toString() 역시 마찬가지로 member를 호출하게 되기 때문에 결국 무한으로 team과 member 객체를 호출하게 되면서 StackOverflowError가 발생하게 되는 것이다.

 

즉, Lombok의 @ToString, @Data나 IDE가 제공하는 toString() 메서드 자동 생성 기능이 편리하다고 무분별하게 사용하게 되면 이러한 상황이 발생할 수 있다.

그렇다면 이를 해결하는 방법으로는 무엇이 있을까?

 

가장 단순한 방법은 Lombok이나 IDE가 제공하는 toString() 메서드를 그대로 사용하지 않고 toString() 메서드를 직접 구현하여 연관관계 속성을 출력하지 않는 방법이다.

이는 양방향 관계의 한 클래스 혹은 두 클래스의 toString()에서 연관관계 속성 출력 부분을 제거해주면 된다.

//해결방법 : Member 클래스에서 team 출력 제거
@Override
public String toString() {
    return "Member{" +
           "name='" + name + '\'' +
           '}';
}

//해결방법 : Team 클래스에서 member 출력 제거
@Override
public String toString() {
    return "Team{" +
           "name='" + name + '\'' +
           '}';
}

 

만약 자신은 직접 구현하기 귀찮아서 Lombok 라이브러리를 꼭 사용해야겠다면 두 번째 방법인 Lombok에서 지원하는 @ToString(exclude = "필드명") 옵션을 추가하면 된다.

 

멤버 변수인 team 필드를 제외하면 해결할 수 있다.

@Getter @Setter
@ToString(exclude = "team") //team 필드 제외
public class Member {

    private String name;
    private Team team;
}

 

다시 테스트를 돌려보면 원하는 대로 잘 동작하는 것을 확인할 수 있다.

 

반응형

댓글