티스토리 뷰

0. 들어가며

:: Entity 객체가 DB 테이블과 다른 점 == JOIN이 아닌 객체 내 객체로 연관 관계를 가짐

:: JPA에서는 Entity 객체에 DB 테이블의 모든 연관 관계를 객체와 어노테이션으로 명시해야 함

  • 고려 요소
    1. 다중성 :: N:M 관계
    2. 방향 :: 단 / 양 방향(객체 참조)
    3. 연관 관계의 주인 :: 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 :: 객체는 참조(객체 내 필드 객체)를 사용해서 연관된 객체를 찾는다.

출처 : ASAC 수업자료

분류 주인
1 : N or N : 1 일 경우 N 이 연관관계 주인
1 : 1 일 경우 주 테이블이 연관관계 주인
M : N 일경우 M과 N을 연결해주는 테이블 추가
1 : M 과 N : 1 을 만들어서 M 과 N 이 연관관계 주인
  • 여기서 양방향 설정시 "mappedBy"를 설정
    1. 누가 외래키(FK)를 실제로 조작(INSERT, UPDATE)할 것인가를 정해주는 것
    2. 연관관계의 주인이 아닌 쪽에 mappedBy를 선언함
    3. 해당이 선언된 테이블은 읽기 전용(조회 전용)

 


참고

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

 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/05   »
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
글 보관함