북 스터디/스프링 부트 핵심가이드

연관관계 매핑

EnoughTT 2023. 4. 9. 16:13

연관관계 매핑

RDBMS를 사용할 때는 각 도메인에 맞는테이블을 설계하고 연관관계를 설정해서 조인(Join) 등 기능을 활용함

JPA를 사용하는 어플리케이션에서도 테이블의 연관관계를 엔티티 간의 연관관계로 표현할 수 있음

다만, 정확한 연관관계를 표현할 수는 없음

 

연관관계 매핑 종류와 방향

  • One To One : 일대일 (1 : 1)
  • One To Many : 일대다 (1 : N)
  • Many To One : 다대일 (N : 1)
  • Many To Many : 다대다 (N : M)

어떤 엔티티를 중심으로 연관 엔티티를 보느냐에 따라 연관관계의 상태가 달라짐

JPA를 사용하는 객체지향 모델링에서는 엔티티 간 참조 방향을 설정할 수 있음

비즈니스 로직의 관점에서 봤을 경우 단방향 관계만 설정해도 해결되는 경우가 있음

  • 단방향 : 두 엔티티의 관계에서 한쪽의 엔티티만 참조하는 형식
  • 양방향 : 두 엔티티의 관계에서 각 엔티티가 서로의 엔티티를 참조하는 형식

연관관계가 설정되면 한 테이블에서 다른 테이블의 기본값을 외래키로 갖게됨

이런 관계에서 '주인 (Owner)'라는 개념이 사용됨

외래키를 가진 테이블이 그 관계의 주인이 되며, 주인은 외래키를 사용할 수 있으나 상대 엔티티는 읽는 작업만 수행 할 수 있음.

 

일대일 매핑

일대일 단방향 맵핑

@One To One 어노테이션 사용 : 다른 엔티티 객체를 필드로 정의했을 때 일대일 연관관계로 매핑하기 위해 사용

@JoinColumn 어노테이션 사용 : 매핑할 외래키를 설정, 의도한 이름이 들어가지 않기 때문에

                                                        name 속성을 사용해 원하는 칼럼명을 지정하는것이 좋음

  • @JoinColumn 어노테이션 속성
    • name : 매핑할 외래키의 이름을 설정
    • referencedColumnName : 외래키가 참조할 상대 테이블의 칼럼명 지정
    • foreignKey : 외래키를 생성하면서 제약조건을 설정 (unique, nullable, insertable, updatable 등)

ex)

하나의 사용자(User)는 하나의 주소(Address)를 가질 수 있지만, 하나의 주소는 항상 하나의 사용자에 속한다는 제약 조건이 있을 경우에 일대일 단방향 매핑을 사용할 수 있음

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String username;
    private String password;
    
    @OneToOne
    private Address address;
}

@Entity
public class Address {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String city;
    private String street;
}

위 코드에서 User 엔티티 클래스의 address 속성은 Address 엔티티를 참조하는데 사용

이 속성은 @OneToOne 어노테이션을 사용하여 매핑되어 있으며, 이를 통해 하나의 사용자는 하나의 주소를 가지며,

이 주소는 해당 사용자에게만 속한다는 관계를 정의함.

User 엔티티를 저장할 때 해당 User가 소유한 Address엔티티를 자동으로 저장함

 

일대일 양방향 맵핑

단방향으로 서로 매핑하는 것

JPA에서도 실제 데이터베이스의 연관관계를 반영해 한쪽의 테이블에서만 외래키를 바꿀 수 있도록 정하는 것이 좋음

mappedBy : 양방향으로 매핑하되 한쪽에게만 외래키를 주는 속성값

@OneToOne(mappedBy = "product")

 

양방향으로 설정되면 ToString을 실행하는 시점에 순환참조가 발생하기 때문에 StackOverflowError가 발생함.

제거를 위해 exclude 사용해 ToString에서 제외해주는 것이 좋음

@ToString.Exclude

 

ex)

하나의 사용자(User)는 하나의 주소(Address)를 가지며, 하나의 주소는 항상 하나의 사용자에 속할 경우에 일대일 양방향 매핑을 사용할 수 있음

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String username;
    private String password;
    
    @OneToOne(mappedBy = "user")
    private Address address;
}

@Entity
public class Address {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String city;
    private String street;
    
    @OneToOne
    @JoinColumn(name = "user_id")
    private User user;
}

User 엔티티 클래스의 address 속성은 Address 엔티티를 참조하는데 사용

이 속성은 mappedBy 속성을 사용하여 Address 엔티티 클래스의 user 속성과 매핑되어 있어 사용자는 해당 주소를 참조할 수 있음

Address 엔티티 클래스의 user 속성은 @OneToOne 어노테이션을 사용하여 User 엔티티 클래스의 id 속성과 매핑되어 있음. 주소는 해당 사용자에 속하며, 이 사용자는 해당 주소를 참조함

User 엔티티를 저장할 때 해당 User가 소유한 Address 엔티티를 자동으로 저장하고, Address 엔티티를 저장할 때 해당 Address가 속한 User 엔티티를 자동으로 저장함

 

다대일, 일대다 매핑

다대일 단방향 매핑

하나의 엔티티가 다른 엔티티를 참조하는 관계를 정의하는데 사용됨.

 

ex)

하나의 게시글(Post)은 여러 개의 댓글(Comment)을 가질 수 있지만, 하나의 댓글은 항상 하나의 게시글에 속함.

게시글(Post) 엔티티와 댓글(Comment) 엔티티를 다대일 관계로 매핑할 수 있음

다대일 단방향 매핑에서는 다수의 Comment 엔티티가 하나의 Post 엔티티를 참조할 수 있음

이 관계를 정의하려면 Comment 엔티티 클래스에 Post 엔티티를 참조하는 속성을 추가하고, @ManyToOne 어노테이션을 사용하여 매핑함

@Entity
public class Post {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String title;
    private String content;
}

@Entity
public class Comment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String content;
    
    @ManyToOne
    private Post post;
}

Comment 엔티티 클래스의 post 속성은 Post 엔티티를 참조하는데 사용

@ManyToOne 어노테이션을 사용하여 매핑되어 있으며, 이를 통해 하나의 댓글은 항상 하나의 게시글에 속한다는 관계를 정의할 수 있음

Comment 엔티티를 저장할 때 해당 Comment가 속한 Post 엔티티를 자동으로 저장함

 

다대일 양방향 매핑

하나의 엔티티가 다른 엔티티를 참조하며, 해당 엔티티도 다시 참조하는 경우에 사용

 

ex)

하나의 부서(Department)는 여러 개의 직원(Employee)을 가질 수 있지만, 하나의 직원은 항상 하나의 부서에 속할 경우에 다대일 양방향 매핑을 사용할 수 있음

부서(Department) 엔티티와 직원(Employee) 엔티티를 다대일 단방향 매핑으로 매핑하고, 이를 양방향 매핑으로 설정함

Department 엔티티 클래스에는 해당 관계의 주인이 되는 직원(Employee) 엔티티를 참조하는 속성을 추가하고, Employee 엔티티 클래스에는 해당 관계의 주인이 되는 부서(Department) 엔티티를 참조하는 속성을 추가함

그리고 각각의 속성에 @ManyToOne 어노테이션을 사용하여 매핑

@Entity
public class Department {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    @OneToMany(mappedBy = "department")
    private List<Employee> employees = new ArrayList<>();
}

@Entity
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    @ManyToOne
    @JoinColumn(name = "department_id")
    private Department department;
}

Department 엔티티 클래스의 employees 속성은 Employee 엔티티를 참조하는데 사용

mappedBy 속성을 사용하여 Employee 엔티티 클래스의 department 속성과 매핑되어 있음

부서는 해당 직원들을 참조할 수 있음

Employee 엔티티 클래스의 department 속성은 @ManyToOne 어노테이션을 사용하여 Department 엔티티 클래스의 id 속성과 매핑되어 있음

이를 통해 직원은 해당 부서를 참조할 수 있음

Department 엔티티를 저장할 때 해당 Department가 소유한 Employee 엔티티를 자동으로 저장하고, Employee 엔티티를 저장할 때 해당 Employee가 속한 Department 엔티티를 자동으로 저장함

 

지연로딩 (Lazy loading) 과 즉시로딩 (Eager loading)

지연로딩

연관된 객체를 실제 사용할 때 DB에서 가져오는 방식
연관된 객체가 필요한 시점에 실제로 필요한 만큼 DB에서 가져오는 방식
불필요한 데이터베이스 작업을 줄일 수 있어 효율적
객체를 사용하는 시점에 DB에 접근하는데서 생기는 지연으로 인한 성능 저하 발생

 

즉시로딩

연관된 객체를 함께 조회하는 방식
엔티티 객체를 조회할 때 연관된 객체도 함께 조회함
객체 그래프를 한 번에 로드할 수 있어 성능상 이점
불필요한 데이터까지 함께 가져오기 때문에 메모리 낭비, 불필요한 데이터베이스 작업이 발생 할 수 있음
@Entity
public class Post {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String title;
    private String content;
    
    @OneToMany(fetch = FetchType.EAGER)		// 즉시로딩
    private List<Comment> comments = new ArrayList<>();
}

@Entity
public class Comment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String content;
}

 

일대다 단방향 매핑

하나의 엔티티가 다른 여러 개의 엔티티를 참조하는 경우에 사용

 

ex)

하나의 게시물(Post)은 여러 개의 댓글(Comment)을 가질 수 있지만, 하나의 댓글은 항상 하나의 게시물에 속한다는 제약 조건이 있을 경우에 일대다 단방향 매핑을 사용할 수 있음

게시물(Post) 엔티티 클래스와 댓글(Comment) 엔티티 클래스를 일대다 관계로 매핑할 수 있음

Post 엔티티 클래스에는 해당 게시물(Post)이 가지고 있는 댓글(Comment) 엔티티를 저장하는 Collection 형태의 속성을 추가하고, @OneToMany 어노테이션을 사용하여 매핑함

@Entity
public class Post {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String title;
    private String content;
    
    @OneToMany
    private List<Comment> comments = new ArrayList<>();
}

@Entity
public class Comment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String content;
}

Post 엔티티 클래스의 comments 속성은 List 형태의 Collection을 사용하여 댓글(Comment) 엔티티를 저장하는데 사용됨

이 속성은 @OneToMany 어노테이션을 사용하여 매핑되어 있으며, 이를 통해 하나의 게시물이 여러 개의 댓글을 가지며, 이 댓글들은 해당 게시물에 속한다는 관계를 정의할 수 있음

Post 엔티티를 저장할 때 해당 Post가 소유한 Comment 엔티티를 자동으로 저장함

 

영속성 전이 (cascade)

특정 엔티티의 영속성 상태를 변경할 때
그 엔티티와 연관된 엔티티의 영속성에도 영향을 미쳐 영속성 상태를 변경하는것을 의미

 

종류 설명
ALL 모든 영속 상태 변경에 대해 영속성 전이를 적용
PRESIST 엔티티가 영속화할 때 연관된 엔티티도 함께 영속화
MERGE 엔티티를 영속성 컨텍스트에 병함할 때 연관된 엔티티도 병함
REMOVE 엔티티를 제거할 때 연관된 엔티티도 제거
REFRESH 엔티티를 새로고침할 때 연관된 엔티티도 새로고침
DETACH 엔티티를 영속성 컨텍스트에서 제외하면 연관된 엔티티도 제외

 

고아 객체 (orphan)

부모 엔티티와 연관관계가 끊어진 엔티티를 의미
JPA에는 이러한 고아 객체를 자동으로 제거하는 기능이 있음
자식 엔티티가 다른 엔티티와 연관관계를 가지고 있다면 이 기능은 사용하지 않는 것이 좋음

 

ex)

게시글(Post)과 댓글(Comment)이 일대다 관계일 때, 게시글을 삭제할 때 해당 게시글과 연관된 댓글도 함께 삭제하고 싶을 경우

@Entity
public class Post {
    @Id
    @GeneratedValue
    private Long id;
    
    private String title;
    
    @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Comment> comments = new ArrayList<>();
}

@Entity
public class Comment {
    @Id
    @GeneratedValue
    private Long id;
    
    private String content;
    
    @ManyToOne(fetch = FetchType.LAZY)
    private Post post;
}

@OneToMany 어노테이션의 orphanRemoval 속성을 true로 설정하면, 게시글을 삭제할 때 해당 게시글과 연관된 댓글도 함께 삭제됨

 

아직 RDB랑 달라서 모르겠지만.. 써봐야 알 것 같음

'북 스터디 > 스프링 부트 핵심가이드' 카테고리의 다른 글

액추에이터 & 서버 간 통신  (0) 2023.04.23
유효성 검사와 예외처리  (0) 2023.04.16
Spring Data JPA  (0) 2023.04.01
데이터베이스 연동 - 2  (0) 2023.03.26
데이터베이스 연동 - 1  (0) 2023.03.25