JPA 연관관계

duckbill413·2023년 11월 10일
0

Spring JPA

목록 보기
2/7
post-thumbnail

JPA 연관관계

다대일 연관관계

N대 1 단방향

  • 가장 많이 사용되는 연관관계
@Entity
public class Member {
    @Id @GeneratedValue
    @Column(name = "member_id")
    private Long id;
    private String username;
    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;
}
@Entity
public class Team {
    @Id
    @GeneratedValue
    @Column(name = "team_id")
    private Long id;
    private String name;
}

회원은 [Member.team](http://Member.team)으로 팀 엔티티를 참조할 수 있지만 반대로 팀에는 회원을 참조하는 필드가 없다. Member.team 필드로 회원 테이블의 TEAM_ID 외래키를 관리한다.

N대 1 양방향

다대일 양방향의 객체 연관관계에서 실선이 연관관계의 주인이고 점선은 연관관계의 주인이 아니다.

@Getter
@Setter
@Entity
public class Member {
    @Id
    @GeneratedValue
    @Column(name = "member_id")
    private Long id;
    private String username;
    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;

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

        // 무한 루프 방지
        if (!team.getMembers().contains(this)) {
            team.getMembers().add(this);
        }
    }
}
@Getter
@Setter
@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) {
        this.members.add(member);
        if (member.getTeam() != this) { // 무한 루프 방지
            member.setTeam(this);
        }
    }
}
  • 양방향은 외래키가 있는 쪽이 연관관계의 주인이다. 일대다와 다대일 연관관계는 항상 다(N)에 외래키가 있다. JPA는 외래키를 관리할 때 연관관계의 주인만 사용한다. 주인이 아닌 Team.members는 조회를 위한 JPQL이나 객체 그래프를 탐색할 때 사용한다.
  • 양방향 연관관계는 항상 서로를 참조해야 한다. 양방향 연관관계는 항상 서로 참조해야 한다. 항상 서로 참조하게 하려면 연관관계 편의 메소드를 작성하는 것이 좋다.

테스트 환경시 실행이 잘되지 않는 경우 @Transactional 어노테이션을 붙이면 실행되는 경우가 있다.

@Transactional 은 데이터의 추가, 갱신, 삭제 등으로 이루어진 작업을 처리하던 중 오류가 발생했을 때 모든 작업을 원상태로 되돌릴 수 있다. 즉, 모든 작업이 성공적으로 수행되었을때 DB에 반영한다.

DB와 관련된 서비스 클래스 혹은 메서드에 @Transactional 을 붙여 사용할 수 있다.


1대 N 연관관계

일대다 단방향

  • 일대다 단방향은 일(1)이 연관관계의 주인
  • 테이블 일대다 관계는 항상 다(N) 쪽에 외래키가 있다.
  • JPA를 활용하여 Member와 MemberHistory간의 1대N 관계 구현
  • @JoinColumn 으로 해당 컬럼으로 join이 수행되어야함을 알려주어야 한다.
  • @OneToMany Annotation을 활용하여 관계 정의 (EAGER은 트랜잭션과 관련 링크)
  • name: MemberHistory 클래스의 member_id 로 조회
  • insertable: Member entity에서 조회는 가능하지만 추가은 할 수 없도록 함
  • updatable: Member entity에서 조회는 가능하지만 수정은 할 수 없도록 함

@Getter
@Setter
@Entity
public class Team {
    @Id
    @GeneratedValue
    @Column(name = "team_id")
    private Long id;
    private String name;
    @OneToMany
    @JoinColumn(name = "team_id") // member 테이블의 team_id(pk)
    private List<Member> members = new ArrayList<>();
}
@Getter
@Setter
@Entity
public class Member {
    @Id
    @GeneratedValue
    @Column(name = "member_id")
    private Long id;
    private String username;
}

1대 N 연관관계 단점

  • @JoinColumn을 꼭 사용해야 한다. 그렇지 않으면 조인 테이블 방식을 사용
  • 엔티티가 관리하는 외래키가 다른 테이블에 있음
  • 연관관계 관리를 위해 추가로 UPDATE SQL을 실행
  • → 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하는 것이 권장됨
  • 연관 관계 주인이 FK를 관리한다.
  • 본인 테이블에 FK가 있으면 엔티티의 저장과 연관관계 처리를 INSERT SQL 한 번으로 끝낼 수 있지만, 다른 테이블에 외래 키가 있으면 연관관계 처리를 위한 UPDATE SQL을 추가로 실행해야 한다.

일대다 양방향

@Getter
@Setter
@Entity
public class Team {
    @Id
    @GeneratedValue
    @Column(name = "team_id")
    private Long id;
    private String name;
    @OneToMany
    @JoinColumn(name = "team_id") // member 테이블의 team_id(pk)
    private List<Member> members = new ArrayList<>();
}
@Getter
@Setter
@Entity
public class Member {
    @Id
    @GeneratedValue
    @Column(name = "member_id")
    private Long id;
    private String username;
    @ManyToOne
    @JoinColumn(name = "team_id", insertable = false, updatable = false)
    private Team team;
}

이 방법은 일대다 양방향 매핑이라기 보다는 일다다 단방향 매핑 반대편에 다대일 단방향 매핑을 읽기 전용으로 추가해서 일대다 양방향처럼 보이도록 하는 방법이다. 따라서 일대다 단뱡향 매핑이 가지는 단점을 그대로 가진다. 다대일 양방향 매핑을 권장한다.


1대1 연관관계

일대일 단방향

  • @OneToOne Annotation table 간에 1대1 관계를 가지는 컬럼에 붙여준다.
  • optional = false : not null 이 된다.
  • mappedBy = “Table명” : 연관키를 해당 테이블에서는 더이상 가지지 않게 된다.
    • Entity relation의 경우 잘못 설정할 경우 ToString Method 등의 경우 stack overflow, 순환 참가 발생할 수 있다.
      • relation을 단방향으로 걸거나 ToString을 처리 해주어야 한다. @ToString.Exclude
@Getter
@Setter
@Entity
public class Member {
    @Id
    @GeneratedValue
    @Column(name = "member_id")
    private Long id;
    private String username;
    @OneToOne
    @JoinColumn(name = "locker_id")
    private Locker locker;
}
@Getter
@Setter
@Entity
public class Locker {
    @Id
    @GeneratedValue
    @Column(name = "locker_id")
    private Long id;
    private String name;
}
  • 주 테이블에 외래키 주 객체가 대상 객체를 참조하는 것처럼 주 테이블에 외래키를 두고 대상 테이블을 참조한다. 외래키를 객체 참조와 비슷하게 사용할 수 있어서 객체지향 개발자들이 선호한다. 이 방법의 장점은 주 테이블이 외래키를 가지고 있으므로 주 테이블만 확인해도 대상 테이블과 연관관계가 있는지 알 수 있다.

일대일 양방향

@Getter
@Setter
@Entity
public class Member {
    @Id
    @GeneratedValue
    @Column(name = "member_id")
    private Long id;
    private String username;
    @OneToOne
    @JoinColumn(name = "locker_id")
    private Locker locker;
}
@Getter
@Setter
@Entity
public class Locker {
    @Id
    @GeneratedValue
    @Column(name = "locker_id")
    private Long id;
    private String name;
    @OneToOne(mappedBy = "locker")
    private Member member;
}

양방향이므로 연관관계의 주인을 정해야 한다. Member 테이블이 외래키를 가지므로 Member.locker가 연관관계의 주인이다.


N:N 연결관계

  • 실무에서는 사용되지 않는다.

다대다 단방향

@Getter
@Setter
@Entity
public class Member {
    @Id
    @GeneratedValue
    @Column(name = "member_id")
    private Long id;
    private String username;
    @ManyToMany
    @JoinTable(name = "member_product", 
								joinColumns = @JoinColumn(name = "member_id"), 
								inverseJoinColumns = @JoinColumn(name = "product_id"))
    private List<Product> products = new ArrayList<>();
}
@Getter
@Setter
@Entity
public class Product {
    @Id
    @GeneratedValue
    private String id;
    private String name;
}

회원 엔티티와 상품 엔티티를 @ManyToMany로 매핑했다. 여기서 중요한 점은 manyToMany 와 @JoinTable을 사용해서 연결 테이블로 바로 매핑한 것이다. 따라서, 회원과 상품을 연결하는 회원_상품테이블 없이 매핑을 완료한 것이다.

다대다 양방향

Product에 @ManyToMany를 사용한다. 그리고 양쪽 중 원하는 곳에 mappedBy로 연관관계의 주인을 지중한다. (mappedBy가 없는 곳이 연관관계의 주인이다.)

@Getter
@Setter
@Entity
public class Product {
    @Id
    @GeneratedValue
    private String id;
    private String name;
    @ManyToMany(mappedBy = "products")
    private List<Member> members;

    public void addMember(Member member) {
        members.add(member);
        member.getProducts().add(this);
    }
}

다대다: 새로운 기본키 사용

@Getter
@Setter
@Entity
public class Order {
    @Id
    @GeneratedValue
    @Column(name = "order_id")
    private Long id;
    @ManyToOne
    @JoinColumn(name = "member_id")
    private Member member;
    @ManyToOne
    @JoinColumn(name = "product_id")
    private Product product;
    private int orderAmount;
}

대리키를 사용함으로써 이전에 보았던 식별 관계에 복합키를 사용하는 것보다 매핑이 단순하고 이해하기 쉽다.

@Getter
@Setter
@Entity
public class Member {
    @Id
    @GeneratedValue
    @Column(name = "member_id")
    private Long id;
    private String username;

    @OneToMany(mappedBy = "member")
    private List<Order> orders = new ArrayList<>();
}
@Getter
@Setter
@Entity
public class Product {
    @Id @Column(name = "product_id")
    private String id;
    private String name;

		@OneToMany(mappedBy = "product")
    private List<Order> orders = new ArrayList<>();
}
profile
같이 공부합시다~

0개의 댓글