@OneToOne@OneToManymappedBy@JoinColumn읽기전용으로 적용하기@OneToMany 단방향 적용시 문제점@ManyToOne@ManyToMany다대다 매핑 설정 시, 중간 테이블 엔티티 생성 없이 설정방법(@JoinTable)null 제약 조건과 JPA 조인 전략
- @OneToOne, @OneToMany 등 적용할 때에는 어느 entity에서 어떤 entity값이 필요한 지를 생각해보면 어떻게 적용할 지를 알 수 있음
- ToString.Exclude를 붙여주어서 StackOverflow error를 방지해야함
@OneToOne
- OneToOne은 한쪽에만 붙여서 한쪽에서만 접근 or 양쪽에 붙인다면 한쪽에 ToString.Exclude & mappedBy 해주어야함
- 양쪽에서 둘다 OneToOne & ToString → StackOverFlow error(ToString 순환참조 문제) 가 발생함 → ToString.Exclude를 한쪽에 붙여주기
- mappedBy x → 둘다 서로 참조하게 되어 query에서 join 문이 각각 2번씩 호출되게 됨(book에서 bookHistory를 참조하고 또 bookHistory에서 book을 참조하고..)
- mappedBy 를 적용하게 되면 table 생성 시에는 그 필드가 빠지게 됨
- mappedBy에 지정하는 값은 반대쪽 relation에서 현재 table을 참조할 때의 변수 이름으로 지정
public class Book { @OneToOne(mappedBy = "book") private BookReviewInfo bookReviewInfo; } public class BookReviewInfo { @OneToOne(optional=false) private Book book; // <- 이 변수의 이름을 mappedBy에 }
- optional = false 이면 항상 그 값이 존재하게 되어서 쿼리가 inner join으로 해당 relation을 찾게 됨. outer join이 아니라.
@OneToMany
@Entity @Table(name = "orders") @Getter @Setter public class Order { @Id @Column(name="id") private String uuid; @Column(name="order_datetime", columnDefinition = "TIMESTAMP") private LocalDateTime orderDatetime; @Enumerated(EnumType.STRING) private OrderStatus orderStatus; @Lob private String memo; @ManyToOne @JoinColumn(name="member_id", referencedColumnName = "id") private Member member; // name 은 orders 테이블에서의 컬럼 이름 } @Entity @Table(name="member") @Getter @Setter public class Member { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private Long id; @Column(name="name", nullable = false, length = 30) private String name; @Column(name="nick_name", nullable = false, length = 30, unique = true) private String nickName; private int age; @Column(name="address", nullable = false) private String address; @Column(name="description", nullable = true) private String description; @OneToMany(mappedBy = "member") /* Member테이블에서 foreignKey로 관리되는 주체인 필드의 이름*/ private List<Order> orders ; }
mappedBy
- 엔티티를 단방향으로 매핑하면 참조를 하나만 사용하므로 이 참조로 외래 키를 관리하면 되는데, 엔티티를 양방향으로 매핑하면 회원→ 팀, 팀 →회원 두 곳에서 서로를 참조함 (객체의 참조는 둘인데 외래 키는 하나 → 두 객체 연관관계 중 하나를 정해서 테이블의 외래 키를 관리해야 하는데 이것을 연관관계의 주인이라 함)
- 연관관계의 주인만이 데이터베이스 연관관계와 매핑되고 외래 키를 관리(등록, 수정, 삭제)할 수 있음. 주인이 아닌 쪽은 읽기만 할 수 있다
- 주인은 mappedBy 속성 사용x
- 주인이 아니면 mappedBy 속성을 사용해서 속성의 값으로 연관관계의 주인을 지정해야 함
- 연관관계의 주인은 테이블에 외래 키가 있는 곳으로 정해야 함
- @OneToMany(mappedBy=” “) 에 들어갈 값은 연관관계의 주인인 Order.member 즉 entity에서 foreignKey로 매핑되는 필드이름 (테이블에 생성되는 컬럼이름이 아닌 필드이름임)
- 다대일, 일대다 에서는 항상 다 쪽이 외래 키를 가지게 됨
- mappedBy를 해야할 상황에서 안하게 되면 자동으로 테이블이름을 이상하게 참조하여 쿼리가 날아감
- 자동 참조 테이블이름 : <현재 테이블이름>s_<해당 변수명>
@JoinColumn
- 외래 키를 매핑(데이터베이스의 외래 키를 객체에 매핑)할 때 사용함
- @JoinColumn을 명시해주지 않으면 User와 User history를 연결하는 중간 매핑테이블(@JoinTable)이 생성되게 됨
Hibernate: create table user_user_histories ( user_id bigint not null, user_histories_id bigint not null )
@JoinTable 로 적용 시 생기는 문제
parent
가 10개의 Child를 포함하는children
을 가진다.parent.children
에서 Child의 id가 1, 2인 것 2개만 삭제한다.
- 해당 매핑 테이블을 없애기 위해 @JoinColumn을 명시해주면 반대쪽 테이블에 어떤 column으로 join을 적용할 지를 정의해줌
@JoinColumn(name=”member_id”, referencedColumnName = “id”)
- foreign key이름을 member_id로 하고, 상대방 테이블의 id 필드와 join을 할거다
- referencedColumnName 의 컬럼명은 @Column이 붙어있으면 해당 어노테이션에서 정의한 name이름
// 위의 @JoinColumn과 동일한 의미 alter table order add constraint FKt4dc2r9nbvbujrljv3e23iibt foreign key (member_id) // 이 부분이 name references member (id) // 이 부분이 referencedColumnName
정의된 필드 이름_반대쪽테이블의pk필드이름
으로 UserHistory 테이블에 추가로 생성됨- Many쪽 테이블에 One에 해당하는 id는 항상 추가됨
@OneToMany(fetch=FetchType.EAGER) @JoinColumn private List<UserHistory> userHistories = new ArrayList<>(); create table user_history ( id bigint generated by default as identity, created_at timestamp, updated_at timestamp, email varchar(255), name varchar(255), user_id bigint, user_histories_id bigint, primary key (id) )
읽기전용으로 적용하기
public class User { @OneToMany(fetch=FetchType.EAGER) @JoinColumn(name="user_id", insertable = false, updatable = false) private List<UserHistory> userHistories = new ArrayList<>(); // nullPointerException 방지 } // insertable과 updatable 이 false이므로 User 클래스에서 UserHistory클래스를 변경하지 못함. // 읽기전용
@OneToMany 단방향 적용시 문제점
@OneToMany 단방향 문제점@ManyToOne
- 일반적인 상황에서는 @OneToMany보다 @ManyToOne이 좀 더 깔끔하게 entity를 구성할 수 있음. 그 이유는 One(User.java) 측에서 Many(UserHistory.java)의 id를 가지려면 id 의 list를 가져야 하지만, Many측에서 One(User.java)의 id는 항상 가지고 있기 때문임
- @ManyToOne(optional=false)
@ManyToMany
- 실무 현업에서는 거의 사용되지 않음
- @OneToMany에서 테이블이 생성됐던 것과 마찬가지로 여기서도 매핑 테이블이 생성됨. 그러나 여기서는 그 테이블을 없애는 것이 불가능함. FK를 어느 쪽에서도 가지기 애매하기 때문
- 예시 ( 기존의 N: N 을 중간에 테이블을 하나 만듦으로써 1:N, N:1로 바꾸게 됨)
- User와 Products의 관계(e-commerce)에서 쓰이게됨
- 그러나 이 또한도 자동으로 생기는 매핑 테이블을 쓰지 않고, Order라는 entity를 새로 만들어서 Order : User (N : 1) , Order : Product(N : 1) 이렇게 사용가능함
- 중간 테이블의 PK를 복합키(양쪽 테이블의 PK의 조합)으로 가져갈 수도 있고(식별 관계), 양쪽 테이블의 PK는 FK로만 쓰고 새로운 PK를 정의해서 가져갈 수 있는데(비식별 관계) 후자가 더 간단함
- 식별관계 : 부모 테이블의 기본 키를 받아서 자식 테이블의 기본키 + 외래키로 사용
비식별관계 : 단순히 외래키 로만 사용
public class Order{ @Id @GeneratedValue private Long id; @ManyToOne @JoinColumn(name="MEMBER_ID") private Member member; @ManyToOne @JoinColumn(name="PRODUCT_ID") private Product product; .. }
다대다 매핑 설정 시, 중간 테이블 엔티티 생성 없이 설정방법(@JoinTable)

@Entity public class Member { @Id @Column(name= "MEMBER_ID") private String id; private String username; @ManyToMany @JoinTable(name = "MEMBER_PRODUCT", joinColumns=@JoinColumn(name="MEMBER_ID"), inverseJoinColumns=@JoinColumn(name="PROUDCT_ID")) private List<Product> products = new ArrayList<Product>(); @Entity public class Product { @Id @Column(name = "PRODUCT_ID") private String id; private String name; }
- joinColumns : 현재 방향인 회원과 매핑할 조인 컬럼 정보를 지정 (JoinTable로 명시된 테이블의 컬럼이름)
- inverseJoinColumns : 반대 방향인 상품과 매핑할 조인 컬럼 정보를 지정 (JoinTable로 명시된 테이블의 컬럼이름)
null 제약 조건과 JPA 조인 전략
- 해당 객체가 null이 가능한지 가능하지 않은 지에 대해 정해주는 파라미터임
- @OneToOne, @ManyToOne(optional=false) 하면 해당 필드는 null이 될 수 없음 → Inner 조인으로 쿼리 실행
- @OneToOne, @ManyToOne(optional=true) 하면 해당 필드는 null이 될 수 있음 → Outer 조인으로 쿼리 실행
- @OneToMany와 @ManyToMany는 어떠한 옵션 값에도 외부조인으로 실행됨