본문 바로가기
Spring

Spring JPA / 양방향 연관관계 주의사항

by yang sing 2023. 6. 27.

안녕하세요.

지난 포스팅에서는 JPA Entity의 연관관계에 대해서 작성을 했었는데요.

포스팅 마지막 부분에서 양방향 연관관계의 주인을 설정하는 방법까지 작성을 했었습니다.

이번 포스팅에서는 양방향 연관관계에서의 주의사항을 이어서 작성을 하도록 하겠습니다.

 

양방향 연관관계 사용 시 주의 사항

1. 객체 지향적 코드를 생각을 했을 때는 양방향 객체 참조 필드 모두 값을 넣어주는게 좋다. 

public class TestService {
    public void entityTest(){
        // 엔티티 매니저 객체 생성
        EntityManager em = emf.createEntityManager();
        // 트랜잭션 시작
        em.getTransaction().begin();
    
    	Team team = new Team();
        team.setName("team1");
        em.persist(team);
        
        Member member = new Member();
        member.setName("member1");
        member.setTeam(team);  // member 객체의 team 참조 필드에 team 데이터 세팅
        em.persist(team);
        
        team.getMemberList().add(member); // team 객체의 member 참초 필드에 member 데이터 세팅
    
    	em.getTransaction().commit();
    }
}

위 코드와 같이 member 객체에서도 member.setTeam(team) 값을 세팅해주고 team 객체의 Member List에도 member를 추가 했는데 이와 같이 코딩을 해야하는 이유는 영속성 컨텍스트와 관련지어서 생각하면 쉽다.  

public class TestService {
    public void entityTest(){
        // 엔티티 매니저 객체 생성
        EntityManager em = emf.createEntityManager();
        // 트랜잭션 시작
        em.getTransaction().begin();
    
    	Team team = new Team();
        team.setName("team1");
        em.persist(team);
        
        Member member = new Member();
        member.setName("member1");
        member.setTeam(team);
        em.persist(team);
        
        Team fineTeam = em.find(Team.class, team.getSeq()); // DB가 아닌 1차 캐시에서 조회
        
        fineTeam.getMemberList().getName();
        
    	em.getTransaction().commit();
    }
}

위 경우와 같이 영속성 컨텍스트에 존재하는 Entity 객체들을 DB에 커밋 또는 flush()를 하지 않은 상태에서 em.find를 통해 team 객체를 조회를 할 때 DB에서 조회하는게 아닌 1차 캐시에서 조회를 하게 됨으로 1차 캐시에 존재하는 team 객체의 member 필드는 값이 null인 상태가 된다.

이와 같은 경우를 피하기 위해서는 Team, Member 객체에서 참조를 하는 각각의 필드에 데이터를 둘 다 넣어주는게 좋다.

 

 

2. 양방향 매핑 시 무한 루프를 조심하자.

양방향 관계 매핑을 할 때 toString(), lombok, JSON 생성 라이브러리 등을 사용할 때 무한 루프를 조심해야한다.

@Entity
public class Team {
    @Id
    @GeneratedValue
    private Long seq;
    private String name;
    
    @OneToMany(mappedBy="team")
    private List<Member> memberList = new ArrayList<Member>();
    
    @Override
    public String toString(){
        return "Team{" +
            "seq=" + seq +
            ", name='" + name + '\'' +
            ", memberList=" + memberList +
            '}';
    }
}

@Entity
public class Member {
    @Id
    @GeneratedValue
    private Long seq;
    private String name;

    @ManyToOne
    @JoinColumn(name="team_seq")// member 테이블의 team_seq 컬럼 명시
    private Team team;	// 부모 테이블 매핑 객체인 Team 엔티티 참조
    
    @Override
    public String toString(){
        return "Member{" +
            "seq=" + seq +
            ", name='" + name + '\'' +
            ", team=" + team +
            '}';
    }
}

위 코드에서 보면 Team 객체의 toString() 에서 Member 참조 필드인 memberList를 호출을 하고 있고 Member 객체에서는  Team 참조 필드인 team을 호출하고 있다.

이런 상황에서 toString()을 호출 하게 된다면 두개의 객체가 서로 호출이 되면서 무한 toString()을 호출을 하게 되며 서비스에 장애가 발생을 하게 된다.

위와 같은 toString()은 lombok에 의한 toString() 자동 생성된 메소드의 예시이다. 양방향성 연관관계에서 toString() 메소드가 꼭 필요하다면 lombok에 의한 자동 생성이 아닌 직접 무한 루푸가 아닌 방향으로 정의를 해야 할 필요가 있다.