본문 바로가기
프로젝트/Recipository

[Dev] 22.12.04. 게시글 작성 항목 중 링크에 대하여(2) : 작업

by 규글 2022. 12. 4.

 사실 직전 게시글에서 연관 관계에 대한 이런 저런 내용을 다뤘지만 사실 게시글에 작성하는 link 정보는 양방향 관계가 필요 없었다. 게시글은 작성한 link의 정보가 필요하지만, link 정보는 자신이 어떤 게시글에 작성되었는지에 대한 정보가 필요 없다는 말이다.

 지난 게시글에 생각보다 시간을 많이 들인 이유는 언젠가 양방향 연관 관계에 대해 알아야 할 필요가 있을 것이라 생각해서 에러도 직접 온몸으로 마주치기 위함이었다. 조금 더 생각해보면 어떤 유저가 좋아요를 눌렀는지 그 최근 정보를 mouse over 시에 보이면 좋겠다는 생각도 했지만, 계획했던 기능 중에서는 아직 양방향 연관 관계를 필요로하지 않는다.

 

작업 전개

 또 습관적으로 하나의 객체로 DTO의 역할과 Entity의 역할을 동시에 수행하도록 만들고 있었다. 하지만 작성한 class의 가독성 측면에서도 그렇고, 하나의 객체가 하나의 역할을 수행하도록 하는 것이 좋다. DTO와 Entity로 구분해서 사용해야하는 이유는 따로 게시글을 작성할 예정이다. 당장 직전의 게시글에서도 볼 수 있듯 순환 참조를 피하기 위해서도 둘은 구분해서 사용할 필요가 있다.

 아직은 저장 용도로만 작성했기때문에 추후 게시글 수정 기능에 대한 작업 시 수정될 수 있다.

 

 

RecipeDto.java

package com.example.recipository.domain;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class RecipeDto {
    private Long contentId;
    private String title;
    private String writer;
    private String content;
    private String imagePath;
    private Long viewCount;
    private Long likeCount;
    private List<String> link;
    private String category;
    private boolean bePublic;

    public Recipe toEntity(){
        List<Link> linkList = new ArrayList<>();
        if(link != null) {
            link.forEach(tmp -> {
                Link link = Link.builder()
                        .link(tmp)
                        .build();
                linkList.add(link);
            });
        }

        return Recipe.builder()
                .contentId(contentId)
                .title(title)
                .writer(writer)
                .content(content)
                .imagePath(imagePath)
                .viewCount(viewCount)
                .likeCount(likeCount)
                .link(linkList)
                .category(category)
                .bePublic(bePublic)
                .build();
    }
}

 Client의 form data를 받기 위한 Dto class이다. Dto에서 Entity 객체로 바꿔서 return 하는 method를 작성했으며, 게시글에 link data를 작성하지 않을 수도 있어서 null check를 우선으로 해주었다. 이렇게 전환된 Entity의 List<Link> 정보에는 Recipe 정보가 없다. 아직은 저장 용도로만 작성했기때문에 추후 수정될 수 있다.

 

Recipe.java

package com.example.recipository.domain;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class RecipeDto {
    private Long contentId;
    private String title;
    private String writer;
    private String content;
    private String imagePath;
    private Long viewCount;
    private Long likeCount;
    @Transient
    private List<Link> link;
    private String category;
    private boolean bePublic;

    public Recipe toEntity(){
        List<Link> linkList = new ArrayList<>();
        if(link != null) {
            link.forEach(tmp -> {
                Link link = Link.builder()
                        .link(tmp)
                        .build();
                linkList.add(link);
            });
        }

        return Recipe.builder()
                .contentId(contentId)
                .title(title)
                .writer(writer)
                .content(content)
                .imagePath(imagePath)
                .viewCount(viewCount)
                .likeCount(likeCount)
                .link(linkList)
                .category(category)
                .bePublic(bePublic)
                .build();
    }
}

 Dto 객체에서 전환될 Entity 객체를 위한 class이다. Dto와의 다른 점은 Link 정보를 List로 담고 있는 field가 String의 List가 아니라는 것 뿐이다. 이곳에서는 반대로 Entity를 Dto 객체로 바꿔서 return하는 method를 작성했으며, Dto에서 Entity로 전환된 객체의 List<Link> 의 각 Link 정보에 Recipe 객체인 자신을 setting 하는 method를 작성했다. 이렇게 setting 해주어야 repository를 통해서 link 정보가 DB에 저장될 때 연결되어 있는 content_id 정보가 누락되지 않을 수 있다.

 

 이어서 생각해볼 것은 Recipe entity와 Link entity의 관계 구성이다. 하나의 Recipe 객체는 여러 Link 정보를 가질 수 있으므로 Link와 Recipe를 N:1(다대일) 관계로 구성했다. 그럼 단방향과 양방향 연관 관계에 있어서는 어떨까? 단방향 연관 관계로 구성하게 되면 Recipe에서는 Link 정보를 가져올 수 없지만, Link에서는 Recipe 정보를 가져올 수 있다. 물론 각 repository에 따로 data를 요청해서 가져와도 된다. 반대로 양방향 연관 관계로 구성하게 되면 Recipe를 요청하면서 동시에 Link 정보를 가져올 수 있지만 결국 DB에서 link table을 조회한다는 것은 동일하다. 따라서 굳이 양방향 연관 관계로 묶지 않으려고 한다.

 

Link.java

package com.example.recipository.domain;

import lombok.*;

import javax.persistence.*;

@Getter
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Table(name = "link")
@Entity
public class Link {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String link;
    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "content_id")
    private Recipe recipe;

    public void setRecipe(Recipe recipe) {
        this.recipe = recipe;
    }
}

 RecipeDto로 넘겨받은 client에서 입력한 Link 정보를 저장하기 위한 Entity class이다. 하나의 게시글(Recipe)은 여러 개의 Link 정보를 가질 수 있기때문에 Link와 Recipe를 N:1(다대일) 단방향 연관 관계로 설정하기 위해 @ManyToOne annotation을 작성했다. 이때 Recipe 쪽에서는 Link 정보를 호출할 수 없으나, 따로 Link 정보를 호출하는 방식으로 게시글의 data를 불러오도록 할 예정이다.

 이때 작성된 @ManyToOne의 fetch 속성은 data를 어떻게 불러오는가에 대한 방식을 명시하는데, EAGER type이 default이며 이는 연관된 table의 data까지 한 번에 가져오는 option이다. Cascade 속성은 해당 entity와 연관된 entity의 생명 주기를 명시하는데, 저장과 삭제 등 모든 생명 주기를 함께하도록 ALL로 option을 택했다.

 이어서 @JoinColumn의 name 속성은 foreign key column의 name을 명시하는데, 해당 table의 column name이 된다. 속성 중에 referenceColumnName이 있는데, 이 속성을 작성해주지 않는다면 default로 referenced table의 primary key 의 column name이 들어간다. 따라서 Recipe의 content_id가 되겠다.

 

RecipeServiceImpl.java

    @Transactional
    @Override
    public boolean write(RecipeDto recipeDto) {
        Recipe recipe = recipeDto.toEntity();
        recipe.setMenuAtLink();

        recipeRepository.save(recipe);
        linkRepository.saveAll(recipe.getLink());

        return false;
    }

 

 Service logic은 간단하다. Controller가 client로부터 받은 form data를 RecipeDto에 담아 service로 넘겨받은 뒤, 해당 Dto 객체를 Entity 객체로 바꿔주고 비어있는 Link의 Recipe에 자기 자신을 넣어주는 것으로 Entity 구성을 완료한 것이다. 그리고 Recipe와 Link를 각각 table에 저장하도록 했다. Return type과 그 value는 임시로 작성해둔 것이다.

 

 

 이제 다음 게시글에서는 File 저장에 대한 작업을 할 것이다.

댓글