티스토리 뷰
자바와 스프링에 대한 기본 지식을 기르기 위해 토이 프로젝트를 시작했습니다.
프론트 코드 : https://github.com/laboratory-kkoon9/connector_front
백엔드 코드 : https://github.com/laboratory-kkoon9/connector_back
배경
이번에는 연관관계 추가 및 제거하는 방법 대해 알아보겠습니다.
연관관계 추가하는 방법
연관관계 편의 메서드
프로필(Profile)과 기술 스택(Skill)과 같이 양방향 연관관계를 가지고 있는 양쪽 모두 관계를 맺어주어야 합니다.
profile.getSkills().add(skill);
skill.setProfile(profile);
연관관계 편의 메서드란, 위 코드를 각각 호출하다 보면 실수로 하나만 호출해서 양방향이 깨질 수 있는 걸 방지해주는 메서드입니다.
아래 코드는 Profile Entity 내부에 changeSkills() 메서드로 양방향 관계를 모두 설정하도록 해준 코드입니다.
package com.connector.domain;
@Entity
@Table(name = "profiles")
@NoArgsConstructor
@Getter
public class Profile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", updatable = false)
private Long id;
@OneToOne(fetch= FetchType.EAGER, optional = false)
@JoinColumn(name = "user_id")
private User user;
@Column(name = "company")
private String company;
@Column(name = "status")
private String status;
@Column(name = "location")
private String location;
@Column(name = "bio")
private String bio;
@Column(name = "website")
private String website;
@OneToMany(mappedBy = "profile", cascade = CascadeType.ALL) // 참조를 당하는 쪽에서 읽기만 가능
private List<Skill> skills = new ArrayList<>();
public void changeSkills(List<Skill> skills) {
this.skills = skills;
for(Skill skill : skills) {
this.addSkill(skill);
}
}
public void addSkill(Skill skill) {
if (skill.getProfile() != this) {
skill.setProfile(this);
}
}
}
@Getter
@Entity
@Table(name = "skills")
@NoArgsConstructor
public class Skill {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", updatable = false)
private Long id;
@ManyToOne(fetch= FetchType.LAZY, optional = false)
@JoinColumn(name = "profile_id")
private Profile profile;
@Column(name = "name")
private String name;
@Builder
public Skill(Long id, Profile profile, String name) {
this.id = id;
this.profile = profile;
this.name = name;
}
public void setProfile(Profile profile) {
if (this.profile != null) {
this.profile.getSkills().remove(this);
}
this.profile = profile;
//무한루프에 빠지지 않도록 체크
if(!profile.getSkills().contains(this)) {
profile.getSkills().add(this);
}
}
public static Skill of(String name) {
return Skill.builder()
.name(name)
.build();
}
}
연관관계를 변경할수도 있으니 기존 프로필이 있다면 기존 프로필과 스킬의 연관관계를 삭제하는 코드를 추가해줘야 합니다.
if (this.profile != null) {
this.profile.getSkills().remove(this);
}
연관관계 삭제하는 방법
1. jpaRepository deleteBy
말 그대로 skillRepository.deleteByProfile()로 프로필 관련 엔티티를 제거해준 뒤에 profile.skills를 초기화해주는 방법입니다.
skillRepository.deleteAllByProfile(profile);
changeSkills(profileDto, profile);
2. 고아 객체
JPA는 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능을 제공합니다.
이를 고아 객체(orphan) 제거라고 합니다.
@Entity
@Table(name = "profiles")
@NoArgsConstructor
@Getter
public class Profile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", updatable = false)
private Long id;
@OneToOne(fetch= FetchType.EAGER, optional = false)
@JoinColumn(name = "user_id")
private User user;
@Column(name = "company")
private String company;
@Column(name = "status")
private String status;
@Column(name = "location")
private String location;
@Column(name = "bio")
private String bio;
@Column(name = "website")
private String website;
@OneToMany(mappedBy = "profile", cascade = CascadeType.ALL) // 참조를 당하는 쪽에서 읽기만 가능
private List<Skill> skills = new ArrayList<>();
@OneToMany(mappedBy = "profile", cascade = CascadeType.ALL) // 참조를 당하는 쪽에서 읽기만 가능
private List<Experience> experiences = new ArrayList<>();
@OneToMany(mappedBy = "profile", cascade = CascadeType.ALL) // 참조를 당하는 쪽에서 읽기만 가능
private List<Education> educations = new ArrayList<>();
@Builder
public Profile(Long id, User user, String company, String status, String location, String bio, String website, List<Skill> skills, List<Experience> experiences, List<Education> educations) {
this.id = id;
this.user = user;
this.company = company;
this.status = status;
this.location = location;
this.bio = bio;
this.website = website;
this.skills = skills;
this.experiences = experiences;
this.educations = educations;
}
}
고아 객체 제거 기능을 활성화하기 위해서는 @OneToMany 혹은 @OneToOne에 orphanRemoval 옵션을 true로 주면 됩니다.
@OneToMany(mappedBy = "profile", cascade = CascadeType.ALL, orphanRemoval=true)
private List<Experience> experiences = new ArrayList<>();
@OneToMany(mappedBy = "profile", cascade = CascadeType.ALL, orphanRemoval=true)
private List<Education> educations = new ArrayList<>();
고아 객체 제거는 참조가 제거된 엔티티는 다른 곳에서 참조하지 않은 고아 객체로 보고 삭제하는 기능이기 때문에 참조하는 곳이 하나일 때만 사용해야 합니다.
특정 엔티티가 개인 소유하는 엔티티에만 이 기능을 적용해야 합니다.
그렇기 때문에 @OneToMany 혹은 @OneToOne에만 사용할 수 있는 겁니다.
'개발 노트 > 토이 프로젝트로 배우는 스프링+자바' 카테고리의 다른 글
토이 프로젝트로 배우는 자바 스프링 [5]. 엔티티의 생성시각을 자동으로 저장하기 (JPA Auditing) (0) | 2024.04.23 |
---|---|
토이 프로젝트로 배우는 자바 스프링 [4]. WebSecurityConfigurerAdapter deprecated (1) | 2024.01.28 |
토이 프로젝트로 배우는 자바 스프링 [3]. API path와 HTTP Method로 권한 분리하기 (0) | 2024.01.23 |
토이 프로젝트로 배우는 자바 스프링 [1]. N+1 문제 (1) | 2023.12.19 |
토이 프로젝트로 배우는 자바 스프링 [0]. prologue (1) | 2023.11.27 |
- Total
- Today
- Yesterday
- Effective Java
- 코테
- MSA
- 백준
- 클린 코드
- Spring
- Java
- BOJ
- C++
- programmers
- kkoon9
- 객체지향
- 이팩티브 자바
- JPA
- 알고리즘
- 이펙티브 자바
- 디자인 패턴
- 프로그래머스
- 클린 아키텍처
- kotest
- Olympiad
- 정규표현식
- Algorithm
- Spring Boot
- 테라폼
- Kotlin
- 디자인패턴
- AWS
- node.js
- BAEKJOON
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |