티스토리 뷰

자바와 스프링에 대한 기본 지식을 기르기 위해 토이 프로젝트를 시작했습니다.

 

토이 프로젝트로 배우는 자바 스프링 [0]. prologue

자바와 스프링에 대한 기본 지식을 기르기 위해 토이 프로젝트를 시작했습니다. 프론트 코드 : https://github.com/laboratory-kkoon9/connector_front GitHub - laboratory-kkoon9/connector_front Contribute to laboratory-kkoon9/co

kkoon9.tistory.com

프론트 코드 : https://github.com/laboratory-kkoon9/connector_front

 

GitHub - laboratory-kkoon9/connector_front

Contribute to laboratory-kkoon9/connector_front development by creating an account on GitHub.

github.com

백엔드 코드 : https://github.com/laboratory-kkoon9/connector_back

 

GitHub - laboratory-kkoon9/connector_back

Contribute to laboratory-kkoon9/connector_back development by creating an account on GitHub.

github.com

배경

이번에는 연관관계 추가 및 제거하는 방법 대해 알아보겠습니다.

연관관계 추가하는 방법

연관관계 편의 메서드

프로필(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에만 사용할 수 있는 겁니다.

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