스프링부트+JPA 로 게시판에 해시태그 기능을 구현해보자 -2
이전 글에서 ManyToMany 의 한계를 느끼고 중간 엔티티를 만들어주는 방향으로 구조를 바꿔보기로 했었다.
기존에 게시글에선 Hashtag타입이 담긴 hashset, 해시태그에선 Article타입이 담긴 Articles 로 ManyToMany 관계를 맺어주는 구조에서,
해당 부분을 지우고, ArticleHashtag 엔티티를 생성해줘 해당 엔티티 필드에 Article 과 Hashtag 를 @JoinColumn 으로 각각 게시글과 해시태그의 PK를 참조하는 방식으로 관계를 맺어주었다.
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "article_id")
private Article article;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "hashtag_id")
private Hashtag hashtag;
영속성 설정을 꼭 해주자. 중간 엔티티는 게시글과 해시태그에서 참조를 받기 때문에 ManyToOne 으로 설정해주면 된다.
@OneToMany(mappedBy = "article")
@ToString.Exclude
@Setter
private Set<ArticleHashtag> hashtags = new HashSet<>();
@OneToMany(mappedBy = "hashtag")
@ToString.Exclude
private Set<ArticleHashtag> articles = new HashSet<>();
이렇게 기존에 각각 해시태그와 게시글 타입의 HashSet 에서 중간 엔티티 타입을 넣어주고 관계를 맺게 해준다. 중간 엔티티 에서 각각의 엔티티를 참조하므로 OneToMany 관계가 된다.
그리고 필요에 따라서 Dto를 수정해주어야 한다. 중간관계 엔티티를 따로 생성해주었기 때문에, 게시글을 불러올때 따로 hashtagDto 를 가져오도록 구성할 필요 없이
비즈니스 로직에 중간관계 엔티티에서 게시글 아이디를 입력하면 게시글과 해시태그를 같이 가져오도록 추가시킨 후에 map 에 해시태그를 addattribute 하는 방식으로 설계하면 된다.
리스폰스 Dto에 불필요하게 껴있는 hashtagDto 타입의 hashSet들을 지워주면 된다 .
일단 게시글을 등록하는 것부터 수정해주자.
public void saveArticle(ArticleDto dto, Set<HashtagDto> hashtagDto) {
Article article =articleRepository.save(dto.toEntity());
for (HashtagDto hashtag : hashtagDto) {
Hashtag hashtag1= hashtagRepository.findByHashtag(hashtag.hashtag())
.orElseGet(()-> hashtagRepository.save(hashtag.toEntity()));
articlehashtagrepository.save(ArticleHashtag.of(article,hashtag1));
}
}
ArticleRequest 는 게시글 내용과 문자열타입의 Hashtag를 입력했을때, 게시글은 Dto로 가져와주고, Hashtag는 '#'별로 나눠서 Set에 추가해 HashtagDto가 담긴 HashSet으로 반환해준다.
사실 웃긴건 기존에 내가 작성했던 코드는 이러했다.
ㅋㅋ(할말잃음) 이렇게 작성하니 영속성관련 오류가 발생했었는데 위의 코드로 수정하니 오류가 사라졌다.
일단 게시글 먼저 저장해주고 맵핑을 해주면 된다.
hashtag를 반복문을 통해 이미 존재하는 해시태그라면 해당 엔티티를 가져오고, 존재하지 않는다면 저장한후 반환시켜 그것을 중간엔티티에 각각 저장한 후에 중간엔티티까지 레포지토리에 저장하면
게시글과 해시태그가 등록이 되고, 그 각각 엔티티들을 맵핑한 중간 엔티티가 저장되어 양호한 관계를 맺게 된다.
게시글을 등록할때 게시글DTO와 해시태그DTO셋을 가져오도록 변경했기 때문에 컨트롤러 또한 리퀘스트Dto에서 해시태그 셋과 게시글 Dto 를 뽑아 매개값으로 입력하도록 변경해주어야 한다.
@PostMapping("/post")
public String articleSave(@Valid ArticleForm articleForm,BindingResult bindingResult,
ArticleRequest dto,
@AuthenticationPrincipal BoardPrincipal boardPrincipal) {
if(bindingResult.hasErrors()){
return "articles/post/article_form";
}
articleService.saveArticle(dto.toDto(boardPrincipal.toDto()),dto.getHashtags());
return "redirect:/articles";
}
이렇게 도메인과 비즈니스 로직을 재설계하고 컨트롤러까지 해당 구조에 맞게 변경을 해주었다면, 내가 직접 뷰에서 입력을 했을때 DB들이 잘 들어가는지 직접 실행해보자
이렇게 입력하고 저장을 해보자.
잘 저장되는 모습이다. (리스폰스Dto가 수정된후 뷰템플릿수정은 단순히 해당 해시태그가 출력되는 부분에 th:each 로 값을 넣어주면 된다.)
중간 테이블에도 저장이 잘 된다.
이젠 복수저장을 해볼까 ?
이렇게 안녕 부터 안녕하세요 까지 총 4개의 해시태그를 등록했다.
정상적으로 등록이 되는 모습이다.
이렇게 중간 엔티티 또한 하나의 게시글에 4개의 해시태그가 등록됨으로써 총 4개의 PK가 생성된 모습이다.
게시글을 수정 하고 삭제 할때는 단순하다. 중간 엔티티를 삭제하지말고 각각 게시글id와 해시태그id 값을 null 로 변경한후에 수정할때는 변경값을 먼저 저장하고, 또 새 엔티티를 저장해주면 된다.
삭제시에는 단순하게 중간엔티티값을 null로 변경한후에 게시글을 삭제하면 된다.
또한 수정할때도 기존에 입력되었던 해시태그 값도 #해시태그 #해시태그1 같은 패턴으로 받아오면 보기 편하기 때문에 컨트롤러에서 StringBuilder 같은것을 활용하여 각각 해시태그들을 불러온후에 #와 공백을 붙여서 addattribute 해주면 된다.
이렇게 해당 글의 수정버튼을 누르면 값을 완전히 받아오고
이렇게 변경하고 입력하면
수정이 된다.
중간테이블에는 기존 값들이 null로 변경된후 새로운 해시태그와 관계가 맺어진 모습이다.
해시태그 기능 구현은 단순히 방법 정도만 제시해주고 내가 직접 구현할때 참고할만한 자료가 크게 없었다.
해당 기능을 구현한 글들이 어느분의 개발 실력 향상에 도움이 되었으면 하는 마음과 내가 해결했던 문제들을 기록하고자 하는 마음에 글을 작성하게 되었다.