JPA 에서 가장 중요한 것
JPA 에서 가장 중요한 것은 "객체와 관계형 테이블간의 매핑" 이다.
JPA는 자바의 ORM(Object Relational Mapping) 기술에 대한 표준 명세이다. ORM 의 의미가 "객체와 관계형 데이터베이스 테이블의 매핑"이며 JPA 의 목적은 "객체 지향 프로그래밍과 데이터베이스 사이의 패러다임 불일치를 해결" 하는 것이다.
Hibernate Document 를 살펴봐도 객체와 테이블의 매핑이 내용의 절반 이상을 차지한다.
객체와 테이블간의 매핑을 구체적으로 둘로 나눌 수 있다.
- 객체와 데이터베이스 사이의 일대일 매핑 : @Entity, @Column, @Id, @GenertatedValue ... 등
- 테이블 사이의 연관관계 매핑 : @OneToMany, @ManyToOne, @ManyToMany, @OneToOne ... 등
연관관계 매핑
방향 : 단방향, 양방향
데이터베이스는 외래 키 하나로 양 쪽 테이블의 조인이 가능하다. 따라서 단방향이니 양방향이니 나눌 필요가 없다.
하지만 객체는 참조용 필드가 존재해야 다른 객체를 참조할 수 있다. 따라서 두 객체 사이에서 한 쪽만 참조용 필드를 가지고 있으면 단방향, 양 쪽 모두 참조용 필드를 가지고 있으면 양방향 관계라고 한다. 양방향 관계를 다르게 말하자면 두 객체가 서로 단방향을 각각 가지고 있는 것이다.
그렇다면 양방향이 필요한 경우는 언제일까?
앞서 양방향은 단방향을 서로 가지고 있는 것이라고 했다. 비즈니스 로직에서, 두 객체 각자의 입장에서 참조가 필요하면 단방향을 맺어주면 된다.
"무조건 양방향을 하면 쉽지 않나?" 라고 생각할 수 있다. 그렇게 한다면 불필요한 연관관계가 매우 많아져 복잡하게 될 것이다. 따라서 위에서 말했듯이 "참조가 필요한가?" 를 판단해서 단방향으로 매핑해주면 된다.
연관관계의 주인
양방향 관계인 두 객체 사이에서 연관관계의 주인인 객체는 조회, 수정, 삭제를 할 수 있지만 연관관계의 주인이 아닌 쪽은 조회만 가능하다. 연관관계의 주인은 바로 외래키가 있는 곳이다. 양방향일 때 연관관계의 주인이 아닌 객체에서 mappedBy 속성을 사용해 주인을 지정해줘야 한다.
다중성
JPA는 보통 객체를 기준으로 하는데, 다중성만큼은 특이하게 데이터베이스를 기준으로 한다.
[참고] 연관관계는 대칭성을 가진다.
- 일대다 - 다대일
- 일대일 - 일대일
- 다대다 - 다대다
다대일 (N:1)
게시판(Board)와 게시글(Post)을 예로 들어보자. 하나의 게시판에 여러 게시글이 있을 수 있고, 하나의 게시글은 하나의 게시판에만 있을 수 있다.
1. 다대일 단방향
@Entity
public class Post {
@Id @GeneratedValue
@Column(name = "POST_ID")
private Long id;
@Column(name = "TITLE")
private String title;
@ManyToOne
@JoinColumn(name = "BOARD_ID")
private Board board;
//... getter, setter
}
@Entity
public class Board {
@Id @GeneratedValue
private Long id;
private String title;
//... getter, setter
}
다(N) 쪽인 Post 에서 @ManyToOne 만 추가해주었다.
📌 @JoinColumn 이란?
외래키를 매핑할 때 사용하는 어노테이션이다. 데이터베이스 기준으로 외래키가 존재하는 객체(연관관계의 주인)에서 사용하면 된다. 아래는 @JoinColumn 의 속성들이다.
| 속성 | 기능 | 기본값(생략했을 경우) |
| name | 매핑할 외래 키 컬럼명(이름) | 필드명_[참조하는 테이블의 기본 키 컬럼명] |
| referencedColumnName | 외래 키가 참조하는 대상 테이블의 컬럼명 | 참조하는 테이블의 기본 키 컬럼명 |
| foreignKey (DDL) | 외래 키 제약조건을 직접 지정할 때 사용하며, 테이블을 생성할 때만 사용한다. | |
| unique nullable insertable updatable columnDefinition table |
@Column의 속성과 동일하다. |
name 속성과 referencedColumnName 속성이 헷갈릴 수 있는데,
1) name 속성은 외래키를 어떤 컬럼명으로 저장할 지 Naming을 정하는 것이고
2) referencedColumnName 속성은 외래키가 참조하는 테이블의 어떤 컬럼인지 결정하는 것이다. 따라서 아무렇게나 지정하면 에러가 발생할 수 있다.
2. 다대일 양방향
@Entity
public class Post {
@Id @GeneratedValue
@Column(name = "POST_ID")
private Long id;
@Column(name = "TITLE")
private String title;
@ManyToOne
@JoinColumn(name = "BOARD_ID")
private Board board;
//... getter, setter
}
@Entity
public class Board {
@Id @GeneratedValue
private Long id;
private String title;
@OneToMany(mappedBy = "board")
List<Post> posts = new ArrayList<>();
//... getter, setter
}
일(1)쪽에 @OneToMany 를 추가해주고 양방향이 되었으니 mappedBy 를 연관관계의 주인이 아닌 쪽(Board)에서 지정해줍니다.
일대다 (1:N)
1. 일대다 단방향
위 다대일에서 이미 설명된 것 아닌가? 하고 언뜻 이해되지 않을 수 있다. 맞다. 사실 일대다는 실무에서 거의 쓰지 않는다.(단방향이든 양방향이든) 일대다는 외래키를 일(1)쪽에 두는 방식이다. 일반적인 데이터베이스의 외래키 메커니즘과 반대인 것이다. 그래서 거의 안쓴다.
@Entity
public class Post {
@Id @GeneratedValue
@Column(name = "POST_ID")
private Long id;
@Column(name = "TITLE")
private String title;
//... getter, setter
}
@Entity
public class Board {
@Id @GeneratedValue
private Long id;
private String title;
@OneToMany
@JoinColumn(name = "POST_ID") //일대다 단방향을 @JoinColumn필수
List<Post> posts = new ArrayList<>();
//... getter, setter
}
@JoinColumn을 일(1)쪽에 둔 것이 보인다.
이 방식의 문제를 트랜잭션 수행과정을 통해 알아보자.
//...
Post post = new Post();
post.setTitle("가입인사");
entityManager.persist(post); // post 저장
Board board = new Board();
board.setTitle("자유게시판");
board.getPosts().add(post);
entityManager.persist(board); // board 저장
//...
post 를 persist 할 때에는 정상적으로 post를 insert 쿼리가 나간다.
하지만 board 를 persist 할 때에는 board 를 insert 하는 쿼리가 나간 후 post 를 update 하는 쿼리가 나간다. "일(1) 쪽만 수정했는데 왜 다(N) 쪽 쿼리가 발생하지?" 와 같이 헷갈릴 수 있다.
그래서 추후 유지보수를 위해서라도 일대다 단방향 매핑이 필요한 경우, 그냥 다대일 양방향 연관관계를 매핑해버리는게 훨씬 좋다.
[참고] 간혹 'JPA 값 타입'을 사용하는 것을 대신할 때 유용한 경우가 있다고 하지만 거의 없다.
2. 일대다 양방향
아예 공식적으로 지원하지 않으니 그냥 쓰지마라.
일대일(1:1)
말그대로 일대일이므로 자유롭게 지정하고 싶은대로 지정하면 된다. @JoinColumn 이 있는 쪽이 외래키를 관리하게 된다. 양방향일 때는 @JoinColumn 이 없는 쪽에서 mappedBy 를 사용하면 된다.
일대일 단방향 또한 아예 공식적으로 지원하지 않는다. 따라서 일대일 양방향만 사용하면 된다.
다대다(N:N)
역시 실무에서 사용이 금지된다. 보통 중간에 두 객체의 외래키를 가지는 새로운 테이블을 만들어 분리한다.
A, B 객체를 다대다 연관을 짓는다고 하면, N 이라는 새로운 테이블을 만들고
A-N(다대일 양방향) , B-N(다대일 양방향) 으로 구성하면 된다.
참고한 곳
https://ttl-blog.tistory.com/126
https://boomrabbit.tistory.com/217
https://jeong-pro.tistory.com/231
'Spring' 카테고리의 다른 글
| @Repository 어노테이션 (1) | 2023.02.27 |
|---|---|
| JPQL (0) | 2023.02.20 |
| @NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor (0) | 2023.02.13 |
| Spring WebFlux - Annotated Controllers, URI Links (0) | 2023.02.12 |
| Spring Security (0) | 2023.02.05 |