- 연관관계 개요, ERD 알아보기
- 1대1
- 1대N
- N대1
- N대N
RDB는 관계형 데이터베이스이다. 관계형 데이터베이스는 관계와 조인을 통해 집합연산과 관계연산을 할 수 있다. DB생성시 relation을 생각하고 개발한다. 각 테이블이 연관성을 가지고 있다는 정보를 가지고 키를 공유한다. 수정,변경시에 용이하고 여러번 값을 작성해야 하는 불편함이 없어진다. 관계형 DB는 데이터 저장으로 대표적인 방법이다.
ERD 는 entity relation diagram이다. Entity는 양방향으로 참조하는 경우가 있다. 잘못하면 entity관계가 복잡해지고 잠재적 오류 발생할 수 있다. ERD로 개략적인 entity의 구성을 그려보는 것이 좋다. 우리가 만들어야 할 entity에 대한 구성도를 그려볼 수 있다. draw.io프로그램을 사용한다.
서로 분리된 테이블을 만들고 관계를 맺어야 관리하기가 편리할 것이다.
현업에서 많이 활용된다. 책의 리뷰의 평균을 계산하기 위한 테이블을 생성하고 book과 1대1 연관관계를 맺게 작성하였다.
새로 만든 bookReviewInfo entity class의 예시이다. [ entity는 기본 생성자가 필요하며, 상속받은 객체의 필드를 컬럼으로 받아오기 위해서는 callspuer속성을 true로 변경해주어야 한다. ]
이전의 코드를 주석으로 처리 하여 남겨두는 것은 클린코드에 위배된다고 하였으며, 깃으로 버전을 관리하므로 이전 코드를 주석처리해놓은 것을 모두 삭제하는 것이 좋다고 하셧다.
@GenerateValue(strategy = GenerationType.IDENTITY) : 독립적으로 테이블 별 값을 생성하여 준다. 사용할 경우 hibernate_sequence 는 삭제해야 한다. 이 값은 DB에 저장시 id값을 개별적으로 생성하여 주므로 DB에 저장하기 이전인 경우에는 null값을 가지고 있을 것이다.
1대1을 연관관계를 맺은 엔티티의 PK를 자동으로 받아와 외래키로 사용하게 해주는 어노테이션이다. 내가 관계를 맺을 필드위에 작성하며 해당 필드의 get 메소드를 통해 1대1연관을 맺은 엔티티를 가져올 수 있다.
optional = true : left outer 조인이 일어난다. optional 값의 defulat 가 true이기 때문에, 이 값이 존재할 수 도 있고 안할수도 있으므로 outer 조인으로 처리하게 된다. false일경우 해당 필드의 null을 허용하지 않겠다는 의미가 된다. 이렇게 false처리를 할경우 inner조인으로 변경이 된다.
mappedBy속성 : 연관키를 해당 테이블에서 더이상 가지지 않게 된다. entity relation 사용시 순환참조가 발생하게 된다. 특별히 필요한 경우가 아니라면, 단방향으로 걸거나, toString에서 제외하는 처리가 필요하다. @ToString.Exclude 작성하여 준다. 이렇게 하면 다른 entity에서도 연관관계가 맺어진 entity를 가져올수 있게 된다.
user는 회원 정보를 저장하며 userHistory 는 수정정보를 저장하는 테이블이다. user_history 는 user의 id를 필드로 가지고 있다.
1대N관계에서 1측인 User 엔티티에서 관계를 맺어 가져올 것은 N개의 entity collection 이다.( List를 사용하였다. ) 해당 필드에는 @OneToMany 어노테이션과 @JoinColumn어노테이션을 붙여준다.
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = "user_id",insertable = false,updatable = false)
private List<UserHistory> userHistories = new ArrayList<>();
N측인 UserHistory 엔티티에서는 관계를 맺은 필드(외래키)에 @Column(nam=?) 으로 해당 @JoinColumn의 name속성으로 작성한 이름과 동일하게 맞추어 준다.
@Column(name="user_id")
private Long userId;
이렇게 관계를 맺어주면 userRepository의 get 메소드를 이용하여 user를 가져온뒤 관계를 맺고있는 userHistory 의 정보를 바로 불러올수 있다.(기존에는 해당 user의 id를 사용하여 userHistoryRepository 를 이용하여 id에 맞는 userHistory 의 정보를 받아와야 했다. )
List<UserHistory> result = userRepository.getByemail("yoojin@google.com").getUserHistories();
// yoojin@google.com이라는 이메일을 가진 user의 history 를 가져온다.
History 의 값을 가지고 현재 user의 값을 조회하는 것은 드물다. 그래서 User와 UserHistory에 대한 내용은 1대N으로 사용하였다. 반대로 User에서 userHistoryId를 가져야 한다면 값이 1,2,3,4,5,6... 이런식으로 어떤 값의 배열의 형태를 가져야 한다. 그래서 OneToMany에서 참조하는 값은 1측에 해당하는 PK ID를 N측에서 foreign key로 가지게 된다. ( user(1) - userHistory(N) : user의 id를 userhistory 에서 foriegn key 로 가진다.) 일반적인 상황에서는 @ManyToOne이 더욱 깔끔하다. 해당 엔티티가 필요로하는 foreign key 값을 엔티티가 함께 가지고 있기 때문이다. ( 외래키는 N이 가진다. 이때 1대N관계에서는 조인할 외래키를 따로 설정하여 줫지만 N대1관계에서는 엔티티가 외래키를 가지고 있다. )
Foreign key 를 가지는 쪽: 1대N관계라면 N측이 가져야한다. 1쪽에서 N개의 데이터를 수용할수 없기 때문이다.
@ToString.Exclude 를 붙인 필드는 ToString에서 제외된다.
어느 엔티티에서 연관엔티티가 필요한지를 생각해보고 연결 방법을 정한다. User에서 userHistory를 조회하는 것이 많이 발생하므로 user에서 @OneToMany를 사용하여 관계를 맺어주는것이 훨씬 나은 방법이다. (만약 userHistory에서 user를 조회하고 싶은 경우 userHistory 에서 @ManyToOne을 사용한다. )
여러개의 책과 하나의 출판사의 관계이다. Book은 @ManyToOne을 관계를 맺는 필드에 붙여주고 Publisher는 @OneToMany와 @JoinColumn(name=?) 을 관계를 맺는 필드에 붙여준다.
여러개의 리뷰와 하나의 책의 관계이다. Review 는 @ManyToOne, Book은 @OneToMany와 @JoinColumn(name=) 을 붙여준다.
여러개의 리뷰와 하나의 사용자의 관계다. 위와 동일하다.
Book과 Author 의 관계이다. 현업에서 많이 사용하지 않는다. 어떤 작가는 여러 책을 쓰고 어떤 책은 공동저자가 존재 할 수 있다. Many to Many 의 관계이다.
1쪽의 pk를 many쪽에서 가지고 있는다. many to many 이면 외래키를 구하기 어렵다. 저장할수있는 단일값이 없다. many to many인 경우에는 중간 테이블을 구성하여 각 pk를 매핑 하게 된다. 1대N에서는 중간테이블을 제거하였지만 n대n에서는 제거할수 없다.
@ManyToMany 를 사용하여 관계를 맺어준다. 중간테이블이 생기며 이를 제거할수 없다. 현업에서는 이런 매핑을 거의 다루지 않는다. 꼭 필요한 경우에만 사용한다. 피해가려고 설계를 한다.
별도의 네이밍을 통해 중간 매핑 도메인을 만들긴 어렵다. 네이밍을 신중하게 결정해야겠지만 지금은 예제로서 간단하게 작성해보았다. 1대N과 N대1의 위의 작성과정과 동일하게 작성한다고 생각하면 된다. 중간테이블을 내가 만드는 것이다.