jpa 연관매핑 기초 - 단방향, 양방향

2023. 6. 22. 00:29jpa

Member 클래스 안에 Team이라는 클래스가 존재한다. 위와 같은 그림일 때 단방향 연관관계를 맺고 있다고 한다.

 

@Entity
public class Member {

    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

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

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

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Team getTeam() {
        return team;
    }

    public void setTeam(Team team) {
        this.team = team;
    }
}

Member와 Team은 다대일 관계이다. Member 클래스 안에 Team이 있기 때문에 @ManyToOne으로 처리하고 

@JoinColumn을 사용하면 db의 외래키와 매핑을 하게된다. 

 

@Entity
public class Team {

    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

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

    @Column(name = "TEAM_ID")
    private Long teamId;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Long getTeamId() {
        return teamId;
    }

    public void setTeamId(Long teamId) {
        this.teamId = teamId;
    }
}
Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setName("member1");
member.setTeam(team);
em.persist(member);

Member findMember = em.find(Member.class, member.getId());
Team findTeam = findMember.getTeam();
System.out.println("findTeam.getName() = " + findTeam.getName());

Team teamB = new Team();
teamB.setName("TeamB");
em.persist(teamB);
member.setTeam(teamB);

System.out.println("============");
tx.commit();

 

findTeam.getName() = TeamA
Hibernate: 
    call next value for hibernate_sequence
============
Hibernate: 
    /* insert jpabook.jpashop.domain.Team
        */ insert 
        into
            Team
            (USERNAME, TEAM_ID, MEMBER_ID) 
        values
            (?, ?, ?)
Hibernate: 
    /* insert jpabook.jpashop.domain.Member
        */ insert 
        into
            Member
            (USERNAME, TEAM_ID, MEMBER_ID) 
        values
            (?, ?, ?)
Hibernate: 
    /* insert jpabook.jpashop.domain.Team
        */ insert 
        into
            Team
            (USERNAME, TEAM_ID, MEMBER_ID) 
        values
            (?, ?, ?)
Hibernate: 
    /* update
        jpabook.jpashop.domain.Member */ update
            Member 
        set
            USERNAME=?,
            TEAM_ID=? 
        where
            MEMBER_ID=?

 

team, member를 영속화 한 상태에서 select를 하면 1차 캐시에서 조회가 가능하기 때문에 select가 가장 먼저 실행된다. teamB를 영속화하고 member에 set을 하면 update가 되는데 여기까지는 commit을 해야 즉 db에 flush가 될 때 실행된다.

 

양방향 매핑

양방향 관계는 사실 단방향 2개가 있는 것을 말한다.

@Entity
public class Team {

    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;

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

    @OneToMany(mappedBy = "team")
    List<Member> members = new ArrayList<>();

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Member> getMembers() {
        return members;
    }

    public void setMembers(List<Member> members) {
        this.members = members;
    }
}

 

 

연관관계의 주인

양방향 매핑 규칙

- 객체의 두 관계중 하나를 연관관계의 주인으로 지정

- 연관관계의 주인만이 외래키를 관리(등록, 수정)

- 주인이 아닌쪽은 읽기만 가능

- 주인은 mappedBy 속성 사용 x

- 주인이 아니면 mappedBy 속성으로 주인 지정

 

즉 위의 예시에서는 Member.team이 연관관계의 주인이다.

 

Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setName("member1");

team.getMembers().add(member);
em.persist(member);

주인이 아닌 방향으로 연관관계 설정시 오류발생

Team team = new Team();
team.setName("teamA");
em.persist(team);

Member member = new Member();
member.setName("mamber1");

team.getMembers().add(member);
member.setTeam(team); //연관관계의 주인에 값 입력

em.persist(member);

 

team.getMembers().add(member);를 넣지 않으면 어떤 문제가 발생할까?

DB에 반영하는데 문제는 생기지 않는다. 하지만, 영속화 컨텍스트의 1차 캐시에 저장된 team에서는 members에 해당 Member가 추가되지 않은 상태이다. 이런 상황에서 team.members를 사용하게 된다면 DB에서 조회하는게 아닌 1차 캐시에서 꺼내 사용하기 때문에 해당 member가 추가되지 않은 결과가 반환 될 것이고, 문제가 생기게 된다.

 

즉 양쪽에 모두 값을 세팅해줘야 한다.

@Entity
public class Team {

    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;

    private String name;

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

    public void addMember(Member member) {
        member.setTeam(this);
        members.add(member);
    }
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Member> getMembers() {
        return members;
    }

    public void setMembers(List<Member> members) {
        this.members = members;
    }
}
@Entity
public class Member {

    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

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

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

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Team getTeam() {
        return team;
    }

    public void setTeam(Team team) {
        this.team = team;
    }

    // 연관관계 메소드
    /*public void changeTeam(Team team) {
        this.team = team;
        team.getMembers().add(this);
    }*/
}
Team team = new Team();
            team.setName("TeamA");
            em.persist(team);

            Member member = new Member();
            member.setUsername("member1");
//            member.changeTeam(team);
            em.persist(member);

            team.addMember(member);

            em.flush();
            em.clear();

            Team findTeam = em.find(Team.class, team.getId());
            List<Member> members = findTeam.getMembers();

            System.out.println("===========");
            for (Member m : members) {
                System.out.println("m.getUsername() = " + m.getUsername());
            }
            System.out.println("==============");

 

연관관계 메소드는 한쪽에만 해주는 것이 좋다. 최악으로는 무한루프에 걸릴 수 있다. toSting, 롬복, json 등으로 인해

 

양방향 매핑 정리

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

양방향 매핑은 반대 방향으로 조회 기능이 추가

jpql에서 역방향으로 탐색할 일이 많은데 양방향은 필요할 때 추가해도 된다. (테이블에 영향을 주지 않음)

 

연관관계의 주인을 정하는 기준

비즈니스 로직을 기준으로 연관관계의 주인을 선택하면 안된다.

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

'jpa' 카테고리의 다른 글

jpa - 고급매핑 정리  (0) 2023.06.24
jpa - 다양한 연관관계 매핑  (0) 2023.06.24
java: Compilation failed: internal java compiler error  (0) 2023.06.20
jpa 엔티티 매핑 정리  (0) 2023.06.20
jpa 영속성 정리  (0) 2023.06.17