본문 바로가기
웹개발/JPA

연관관계 매핑

by 철없는민물장어 2023. 6. 30.
728x90
반응형
일대다, 다대일 관계 예시

 

멤버, 팀 도메인이 있다고 가정하자.

멤버는 하나의 팀에 속할 수 있고, 한 팀에는 여러명의 멤버가 존재할 수 있다.

 

멤버-팀은 다대일, 팀-멤버는 일대다 관계가 된다.

데이터베이스 테이블에는 다가 되는 멤버쪽에 team_id를 FK로 가지고 있으면 된다.

 

이런 방식대로 객체를 모델링하면

@Entity
public class Member {

    @Id @GeneratedValue
    private Long id;

    @Column(name="username")
    private String name;

    private Long teamId;
}

이렇게 된다.(Long teamId에 주목)

이 방식은 객체 지향적인 방법이 아니다.

만약, em.find로 member 인스턴스를 가져온 후에, 해당 멤버가 속한 팀 정보를 가져오려면

em.find를 또 호출하여 team정보를 가져와야한다.

 

FK(외래키)를 저장하지 말고, 참조를 가지도록 해서 객체 지향적인 모델링을 하자.

@Entity
public class Member {

    @Id @GeneratedValue
    private Long id;

    @Column(name="username")
    private String name;

    @ManyToOne
    @JoinColumn(name="TEAM_ID")
    private Team team;
}

멤버에 Team 참조 필드를 만들고, @ManyToOne 어노테이션을 붙이면 된다.

그럼 member에 team참조를 설정하고 em.persist하면 자동으로 데이터베이스에 FK가 설정되어 저장된다.


양방향 연관관계

위에서 작성한 코드로는 team객체를 통해서 해당 team에 어떤 멤버가 속해있는지는 알 수 없다.

(물론 데이터베이스에서는 멤버 테이블에 있는 team_id FK를 이용해서 양방향으로 탐색이 가능하지만,

객체 측면에서는 그렇지 않다는것이다.)

 

객체에서 양방향 관계를 맺기 위해서는 team 코드에 멤버 리스트를 추가해주고, 연관관계 매핑을 해주면 된다.

@Entity
public class Team {

    @Id @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member> memberList = new ArrayList<>();
}

팀-멤버 관계는 일대다이므로 @OneToMany어노테이션을 부착한다.

mappedBy = "team"으로 설정하여,Team객체는 Member객체의 "team"필드에 매핑됨을 나타낸다.

 

이렇게 설정해두면, em.find()로 팀 객체를 받아와서, 해당 팀에 어떤 멤버가 속해있는지 memberList를 통해 알 수 있게된다.

 

양방향 매핑시 주의사항

연관관계의 주인은 외래키의 위치를 기준으로 정해야한다.

 

이 예시에서, 연관관계의 주인은 Member로 이다. (Member에 team_id 외래키가 존재하므로)

따라서 멤버를 팀에 추가하거나 관리할 때에는 연관관계의 주인인 Member에 값을 설정해주어야 한다.

mappedBy되어있는 측(Team)은 조회만 가능하고 데이터베이스에 영향을 주지 못하기 때문이다.

 

team.getMemberList().add(member);

만약 이 코드만 추가하여 팀에 멤버를 추가하려하면 데이터베이스에 반영되지 않을것이다.

 

member.setTeam(team);
team.getMemberList().add(member);

member.setTeam(team)으로 데이터베이스에 반영할 수 있다.

 

그럼, team.getMemberList().add()는 안해도되겠네? 가 아니라..

순수 객체 상태를 고려해서 항상 양쪽에 값을 설정해야한다.

(member.setTeam(team)만 하더라도 team.getMemberList()에 자동으로 멤버가 추가되긴 하나, 트랜잭션 커밋시에 데이터베이스에 반영이 되는 점을 고려하면... 커밋 전에 team.getMemberList()를 사용하면 추가된 멤버가 반영되지 않은 채로 나온다.)

 

그럼 연관관계가 맺어진 두 객체를 다 값을 설정해주어야하는데, 이를 매번 같이 쓰기는 번거롭기도 하고 깜빡할 수 있으니

메소드를 하나 만들어 사용하는것을 권장한다.

@Entity
class Member{
	...
    public void changeTeam(Team team){
    	this.team=team;
        team.getMemberList().add(this);
    }

}

(위는 간단한 예시로, 필요하다면 기존의 팀의 멤버리스트에서 현재멤버를 빼는 등.. 더많은 로직을 써야할 수 있다)

 

위는 Member에 메소드를 넣었지만, Team측에 메소드를 만들어 쓸수도 있다.

(하지만 양측에 다 연관관계 편의 메소드를 만들어 쓰지 말것을 권장)

 

양방향 매핑시에 무한 루프를 조심해야한다.

예를들어, toString()을 사용할 때..

 

Member.toString()이 team.toString()을 호출하고..

team.toString()이 member.toString()을 생성하고.. 이렇게 무한루프에 빠질 수 있다.

(롬복의 toString()을 쓸 때 주의하자)

 

또, Controller에서 엔티티를 반환하지 말자..

엔티티를 스프링에서 JSON으로 변환해주는데, 이때문에 무한루프에 빠질수도 있고

엔티티 클래스가 수정되면 API스펙이 변하게되기 때문이다.


단방향 매핑만으로도 이미 연관관계 매핑은 완료된것이다.

양방향은 없어도 개발이 가능하다. 

단방향 매핑을 잘 하고 양방향은 필요할 때 추가해 사용하자. (양방향은 테이블에 영향을 주지 않으므로)

 


 

728x90
반응형

'웹개발 > JPA' 카테고리의 다른 글

상속관계 매핑  (0) 2023.07.07
다양한 연관관계  (0) 2023.07.03
엔티티 매핑  (0) 2023.06.29
영속성 컨텍스트  (0) 2023.06.29
JPA 기초  (0) 2023.06.29

댓글