티스토리 뷰
0. 들어가며
:: Entity 객체가 DB 테이블과 다른 점 == JOIN이 아닌 객체 내 객체로 연관 관계를 가짐
:: JPA에서는 Entity 객체에 DB 테이블의 모든 연관 관계를 객체와 어노테이션으로 명시해야 함
- 고려 요소
- 다중성 :: N:M 관계
- 방향 :: 단 / 양 방향(객체 참조)
- 연관 관계의 주인 :: FK 관리의 주체
0.1 다중성
:: 연관 관계에서의 N:M을 정의
:: 연관 관계는 두 객체 사이의 관계이며, 양쪽의 각각 객체의 입장에서 대칭성을 가짐
- @OneToMany ↔ @ManyToOne
- @OneToOne ↔ @OneToOne
- @ManyToMany ↔ @ManyToMany로 정의되는데,
ManyToMany는 복잡한 비즈니스 로직에 있어서 대응이 어려워 중간에 연결 테이블(Associate Table)에 해당하는 Entity를 추가함
// 단순한 @ManyToMany 사용 X
@Entity
public class User {
@Id
private Long id;
@ManyToMany
private List<Role> roles = new ArrayList<>();
}
에서
// 연결 테이블용 엔티티
@Entity
public class UserRole {
@Id @GeneratedValue
private Long id;
@ManyToOne
private User user;
@ManyToOne
private Role role;
private LocalDateTime assignedAt;
} // 를 통해 통제
@Entity
public class User {
@Id
private Long id;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL) // 1:n으로 변경
private List<UserRole> userRoles = new ArrayList<>();
}
0.2 방향
:: 단방향 or 양방향
:: DB 테이블은 FK 하나로 양쪽 테이블 조인이 가능 But, 객체는 참조를 위한 객체 필드가 있어야 양쪽 객체 간 참조 가능
- 양방향 :: 양쪽 객체에 모두 다중성의 대칭성을 적용
- 단방향 :: 한쪽 객체(연관관계의 주인)에만 정의 시, 단방향
0.2 - 1 양방향 관계
:: 두 객체 모두가 서로에 대해 각각 참조용 필드를 갖는 경우
:: 양방향 매핑 시, 무한 루프를 주의할 것(Entity의 직접 사용이 아닌 DTO를 사용하여 최대한 회피할 것)
:: 이외에, 양쪽에 값을 세팅하는 것을 잊지말 것
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "team_id") // 연관 관계의 주인이 @JoinColumn을 가짐
private Team team;
}
@Entity
public class Team {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team") // 주인이 아니라면 mappedBy
private List<Member> members = new ArrayList();
}
두개의 엔티티가 양방향으로 설정되어 있을 때, 사용에 있어서
Member m = new Member();
Team t = new Team();
m.setTeam(t); // team은 설정했지만...
// 이러면 t.getMembers()는 비어있음
// 객체 상태 불일치 발생 :: 각 새로운 객체를 생성했지만 연결해주는 연결 점이 없음!!!
때문에 연관관계 편의 메서드를 한쪽에 정의하여 사용함으로써 해당 문제를 회피해야 함
(한쪽의 엔티티에만 설정하면 됨.)
public class Member {
...
public void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
}
// OR
public class Team {
...
public void addMember(Member member) {
members.add(member);
member.setTeam(this);
}
}
보통 N:1의 관계에서 N을 담당하는 객체에 편의 메서드를 정의함
:: 추가로, 연관관계에 있어서 DB에 저장 시에는 연관 관계의 ID만을 기반으로 새 Entity를 저장하는 게 Good
1) 기본 방식 :: 연관 객체 "직접" 주입
// 연관객체 Entity 를 기반으로 새 Entity 저장 시,
User user = userRepository.findById(userId).get();
Message message = new Message(user);
messageRepository.save(message);
- 객체 간 연관 관계를 명확히 구성 및 연관된 데이터를 함께 사용 가능
- But, 매번 DB에서 User를 조회해야 해서 쿼리의 낭비의 발생(조회 후set해줘야 해서)
- 때문에 연관 객체인 User를 Repository에서 조회해올 필요없이 id만 뽑아서 저장하는 방법을 고려하게 됨
2) 개선 방식 :: ID만으로 연관 관계 설정
@Entity
public class Message {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// @Column(name = "user_id")
// private Integer userId;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
}
@Entity
public class Message {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "user_id") // 실제 DB에 들어가는 컬럼으로 id값만
private Integer userId;
// insertable = false :: 이 컬럼으로 INSERT 하지 않겠다
// updatable = false :: 이 컬럼으로 UPDATE 하지 않겠다
@ManyToOne
@JoinColumn(name = "user_id", insertable = false, updatable = false)
private User user; // 조회용 연관 엔티티 (읽기 전용)
}
// 연관객체의 ID 만을 기반으로 새 Entity 저장
Message message = new Message(userId);
messageRepository.save(message);
// 조회 시,
Message message = messageRepository.findById(1).get();
User user = message.getUser(); // 이때는 LAZY 로딩 발생
- DB에서 User를 굳이 조회 X(쿼리의 최소화를 통한 성능 향상)
- But, 연관 관계가 명확해지지 않아, user엔티티에 직접 접근 불가(Read Only)
0.2 - 2 단방향 관계
:: 양방향과 단방향을 고려할 때, 최대한 단방향으로 정의하는 게 좋음 -> 양방향 정의 시, 연관 관계가 많으면 그만큼 필드가 복잡
:: 단뱡향으로 정의 시, 연관 관계의 주인이 FK를 갖고 이것으로 조작하기 때문에 @JoinColumn을 가진 주인에 있는 객체 필드만
:: 결국, 단방향 매핑으로 하고 나중에 역방향으로 객체 탐색이 꼭 필요하다고 느낄 때 추가하는 것이 가장 이상적
- 간결한 코드와 가독성 :: 양방향에 비해 단방향이 이해하기 쉽다.
- 메모리 사용 최적화 :: 한쪽 객체에서만 참조객체를 유지하면 된다.
- 성능 :: 메모리 사용 최적화 와 동일하게 필요한 객체만 로드하므로 성능이 향상된다.
0.3 연관 관계의 주인
:: 외래키(FK)를 갖는 테이블
:: 객체의 필드에서 @JoinColumn 어노테이션을 사용한다면 해당 연관 관계의 주인
:: ORM 에서는 Relational 테이블과 Object 객체 사이에 다음과 같은 간격이 존재
- Relational :: 테이블은 외래키(Foreign Key)로 조인을 사용해서 연관된 테이블을 찾고
- Object :: 객체는 참조(객체 내 필드 객체)를 사용해서 연관된 객체를 찾는다.
분류 | 주인 |
1 : N or N : 1 일 경우 | N 이 연관관계 주인 |
1 : 1 일 경우 | 주 테이블이 연관관계 주인 |
M : N 일경우 | M과 N을 연결해주는 테이블 추가 1 : M 과 N : 1 을 만들어서 M 과 N 이 연관관계 주인 |
- 여기서 양방향 설정시 "mappedBy"를 설정
- 누가 외래키(FK)를 실제로 조작(INSERT, UPDATE)할 것인가를 정해주는 것
- 연관관계의 주인이 아닌 쪽에 mappedBy를 선언함
- 해당이 선언된 테이블은 읽기 전용(조회 전용)
참고
ASAC 수업자료
[Spring] JPA 연관관계 - ID 만 사용해서 save
다대일 연관관계를 맺고 있는 두 엔티티가 있다고 하자. @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity public class Member { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch =
gksdudrb922.tistory.com
'정리용 > DB' 카테고리의 다른 글
[DB 기초] 12. JPA의 영속성 전이 옵션(CascadeType) (0) | 2025.04.22 |
---|---|
[DB 기초] 11. 연관관계 객체 조회 시점 EAGER / LAZY (0) | 2025.04.20 |
[DB 기초] 9. ORM / JPA (0) | 2025.04.17 |
[DB 기초] 8. Spring AOP - Transaction (0) | 2025.04.01 |
[DB 기초] 7-2. 선언형 트랜잭션의 주의점 (0) | 2025.03.25 |
- Total
- Today
- Yesterday
- asac7#asac
- acac
- useEffect
- useReducer
- useLayoutEffect
- useCallback
- Nginx
- useContext
- useRef
- react
- ASAC
- acas#acas7기
- useState
- asac7
- memo
- ssh
- git
- asac7기
- asac#asac7기
- useMemo
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |