์ฌ์ค ์์์์ผ๋ก๋ ํ์ -> ๊ฒ์ํ -> ๋๊ธ ์์๋ก ๊ตฌํํ๋๊ฒ ๋ง์ผ๋, ์ ๊ฐ ๋๋ฌด ๋๊ธ์ ๊ตฌํํด๋ณด๊ณ ์ถ์ ๋ง์์ด ์ปค์ ๋๊ธ๋ถํฐ ๊ตฌํํ๋๋ก ํ๊ฒ ์ต๋๋ค.
๋ค๋ฅธ๊ฑฐ ๊ตฌํํ๊ณ ์ค์ ์ ๋ด๋ ๊ด์ฐฎ์ต๋๋ค.
- ์ํ๋ฆฌํฐ๋ฅผ ์ด์ฉํ JSON ๋ฐ์ดํฐ๋ก ๋ก๊ทธ์ธ (์๋ฃ)
- JWT๋ฅผ ์ด์ฉํ ์ธ์ฆ (์๋ฃ)
- ๋๋ฉ์ธ, ํ ์ด๋ธ ์ค๊ณ, ์ํฐํฐ ์์ฑ (์๋ฃ)
- ๋๊ธ ์ญ์ ๋ก์ง ๊ตฌํ (์งํ ์ค)
- ํ์๊ฐ์ + ์ ๋ณด์์ ๋ฑ ํ์ ์๋น์ค ๊ตฌํ
- ๊ฒ์ํ ์๋น์ค ๊ตฌํ
- ๋๊ธ ์๋น์ค ๊ตฌํ (1๋๊ธ -> *(๋ฌดํ) ๋๋๊ธ ๊ตฌ์กฐ)
- ์์ธ ์ฒ๋ฆฌ
- ์์ธ ๋ฉ์ธ์ง ๊ตญ์ ํ
- ์นดํ ๊ณ ๋ฆฌ๋ณ ๊ฒ์ํ ๋ถ๋ฅ
- ๊ฒ์๊ธ ํ์ด์ง
- ๋์ ์ธ ๊ฒ์ ์กฐ๊ฑด์ ์ฌ์ฉํ ๊ฒ์
- ์ฌ์ฉ์ ๊ฐ ์ชฝ์ง ๊ธฐ๋ฅ
- ๋ฌดํ ์ชฝ์ง ์คํฌ๋กค
- ๊ฒ์๋ฌผ & ๋๊ธ์ ๋ํ ์๋
- ์ชฝ์ง์ ๋ํ ์๋
- ์ ์ํ ์ฌ์ฉ์ ๊ฐ ์ค์๊ฐ ์ฑํ
- ํ์๊ฐ์ ์ ๊ฒ์ฆ(์: XX๋ํ๊ต XX๊ณผ๊ฐ ์๋๋ฉด ๊ฐ์ ํ ์ ์๊ฒ)
- Swagger๋ฅผ ์ฌ์ฉํ API ๋ฌธ์ ๋ง๋ค๊ธฐ
- ์ ๊ณ & ๋ธ๋๋ฆฌ์คํธ ๊ธฐ๋ฅ
- AOP๋ฅผ ํตํ ๋ก๊ทธ
- ์ด๋๋ฏผ ํ์ด์ง
- ์บ์
- ๋ฐฐํฌ (+ ๋ฌด์ค๋จ ๋ฐฐํฌ)
- ๋ฐฐํฌ ์๋ํ
- ํฌํธ์ ์ด๋ํฐ ์ค๊ณ๋ฅผ ๋ฐ๋ฅด๋ ํจํค์ง ๊ตฌ์กฐ ์ค๊ณํ๊ธฐ
- ...
๋๊ธ์ ๋ก์ง
๋๊ธ์ ์ ๋ฒ์๋ ์ค๋ช ๋๋ ธ๋ฏ์ด ๋ค์์ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค.
๊ตฌํ์ ์ด๋ ต์ง ์์๋ณด์ด๋, ์ญ์ ์ ๋๊ธ์ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ์ง๊ฐ ์กฐ๊ธ ๋ง๋งํฉ๋๋ค.
์ ๋ "์๋ธ๋ฆฌํ์"์ ๊ฒ์ํ ๊ตฌ์กฐ๋ฅผ ๋ฐ๋ฅผ ๊ฒ์ด๋ฏ๋ก, ๋๊ธ ์ญ์ ์ ๋ฐ์ํ ์ ์๋ ์ํฉ๊ณผ ๊ทธ์ ๋ฐ๋ฅธ ๋ก์ง์ ์ ์ด๋ณด๊ฒ ์ต๋๋ค.
- ๋๊ธ์ ์ญ์ ํ๋ ๊ฒฝ์ฐ
- ์ญ์ ํ๋ ๋๊ธ์ด ๋๊ธ์ธ ๊ฒฝ์ฐ(๋ถ๋ชจ)
- ๋๋๊ธ์ด ๋จ์์๋ ๊ฒฝ์ฐ : ๋๊ธ์ ๋ด์ฉ์ด ์ง์์ง๋, DB์์ ํ๋ฉด์์๋ ์ง์์ง์ง ์๊ณ , "์ญ์ ๋ ๋๊ธ์ ๋๋ค"๋ผ๊ณ ํ์
- ๋๋๊ธ์ด ์์ ์กด์ฌํ์ง ์๋ ๊ฒฝ์ฐ : ๊ณง๋ฐ๋ก DB์์ ์ญ์
- ๋๋๊ธ์ด ์กด์ฌํ๋ ๋ชจ๋ ์ญ์ ๋ ๋๋๊ธ์ธ ๊ฒฝ์ฐ : ๋๊ธ๊ณผ, ๋ฌ๋ ค์๋ ๋๋๊ธ ๋ชจ๋ DB์์ ์ผ๊ด ์ญ์ , ํ๋ฉด์์๋ ํ์๋์ง ์์
- ์ญ์ ํ๋ ๋๊ธ์ด ๋๋๊ธ์ธ ๊ฒฝ์ฐ(์์)
- ๋๋๊ธ์ ๋ถ๋ชจ๋๊ธ์ด ๋จ์์๋ ๊ฒฝ์ฐ : ๋๋๊ธ๋ง ์ญ์ , ๊ทธ๋ฌ๋ DB์์ ์์ ํ ์ง์์ง์ง๋ ์๊ณ , ํ๋ฉด์์๋ "์ญ์ ๋ ๋๊ธ์ ๋๋ค" ๋ผ๊ณ ๋ง ํ์
- ๋๋๊ธ์ ๋ถ๋ชจ๋๊ธ์ด ์ญ์ ๋ ๋๊ธ์ธ ๊ฒฝ์ฐ
- ํ์ฌ ์ง์ด ๋๋๊ธ๋ก ์ธํด, ๋ถ๋ชจ์ ๋ฌ๋ ค์๋ ๋๋๊ธ์ด ๋ชจ๋ ์ญ์ ๋ ๊ฒฝ์ฐ -> ๋ถ๋ชจ๋ฅผ ํฌํจํ ๋ชจ๋ ๋๋๊ธ์ DB์์ ์ผ๊ด ์ญ์ , ํ๋ฉด์์์๋ ์ง์
- ๋ค๋ฅธ ๋๋๊ธ์ด ์์ง ์ญ์ ๋์ง ์๊ณ ๋จ์์๋ ๊ฒฝ์ฐ -> ํด๋น ๋๋๊ธ๋ง ์ญ์ , ๊ทธ๋ฌ๋ DB์์ ์ญ์ ๋์ง๋ ์๊ณ , ํ๋ฉด์์๋ "์ญ์ ๋ ๋๊ธ์ ๋๋ค"๋ผ๊ณ ํ์
- ์ญ์ ํ๋ ๋๊ธ์ด ๋๊ธ์ธ ๊ฒฝ์ฐ(๋ถ๋ชจ)
์ต์ข ์์ฑ๋ณธ
Comment
๋ค์ ์ฝ๋๊ฐ ์ถ๊ฐ๋์์ต๋๋ค.
//== ๋น์ฆ๋์ค ๋ก์ง ==//
public List<Comment> findRemovableList() {
List<Comment> result = new ArrayList<>();
Optional.ofNullable(this.parent).ifPresentOrElse(
parentComment ->{//๋๋๊ธ์ธ ๊ฒฝ์ฐ (๋ถ๋ชจ๊ฐ ์กด์ฌํ๋ ๊ฒฝ์ฐ)
if( parentComment.isRemoved()&& parentComment.isAllChildRemoved()){
result.addAll(parentComment.getChildList());
result.add(parentComment);
}
},
() -> {//๋๊ธ์ธ ๊ฒฝ์ฐ
if (isAllChildRemoved()) {
result.add(this);
result.addAll(this.getChildList());
}
}
);
return result;
}
//๋ชจ๋ ์์ ๋๊ธ์ด ์ญ์ ๋์๋์ง ํ๋จ
private boolean isAllChildRemoved() {
return getChildList().stream()//https://kim-jong-hyun.tistory.com/110 ํน์ข
ํ๋ ์ฌ๋ํฉ๋๋ค.
.map(Comment::isRemoved)//์ง์์ก๋์ง ์ฌ๋ถ๋ก ๋ฐ๊พผ๋ค
.filter(isRemove -> !isRemove)//์ง์์ก์ผ๋ฉด true, ์์ง์์ก์ผ๋ฉด false์ด๋ค. ๋ฐ๋ผ์ filter์ ๊ฑธ๋ฌ์ง๋ ๊ฒ์ false์ธ ๋
์๋ค์ด๊ณ , ์๋ค๋ฉด false๋ฅผ ์๋ค๋ฉด orElse๋ฅผ ํตํด true๋ฅผ ๋ฐํํ๋ค.
.findAny()//์ง์์ง์ง ์์๊ฒ ํ๋๋ผ๋ ์๋ค๋ฉด false๋ฅผ ๋ฐํ
.orElse(true);//๋ชจ๋ ์ง์์ก๋ค๋ฉด true๋ฅผ ๋ฐํ
}
์ ์ฒด ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค
@Table(name="COMMENT")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Comment extends BaseTimeEntity {
@Id @GeneratedValue
@Column(name = "comment_id")
private Long id;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "writer_id")
private Member writer;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "post_id")
private Post post;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "parent_id")
private Comment parent;
@Lob
@Column(nullable = false)
private String content;
private boolean isRemoved= false;
//== ๋ถ๋ชจ ๋๊ธ์ ์ญ์ ํด๋ ์์ ๋๊ธ์ ๋จ์์์ ==//
@OneToMany(mappedBy = "parent")
private List<Comment> childList = new ArrayList<>();
//== ์ฐ๊ด๊ด๊ณ ํธ์ ๋ฉ์๋ ==//
public void confirmWriter(Member writer) {
this.writer = writer;
writer.addComment(this);
}
public void confirmPost(Post post) {
this.post = post;
post.addComment(this);
}
public void confirmParent(Comment parent){
this.parent = parent;
parent.addChild(this);
}
public void addChild(Comment child){
childList.add(child);
}
//== ์์ ==//
public void updateContent(String content) {
this.content = content;
}
//== ์ญ์ ==//
public void remove() {
this.isRemoved = true;
}
@Builder
public Comment( Member writer, Post post, Comment parent, String content) {
this.writer = writer;
this.post = post;
this.parent = parent;
this.content = content;
this.isRemoved = false;
}
//== ๋น์ฆ๋์ค ๋ก์ง ==//
public List<Comment> findRemovableList() {
List<Comment> result = new ArrayList<>();
Optional.ofNullable(this.parent).ifPresentOrElse(
parentComment ->{//๋๋๊ธ์ธ ๊ฒฝ์ฐ (๋ถ๋ชจ๊ฐ ์กด์ฌํ๋ ๊ฒฝ์ฐ)
if( parentComment.isRemoved()&& parentComment.isAllChildRemoved()){
result.addAll(parentComment.getChildList());
result.add(parentComment);
}
},
() -> {//๋๊ธ์ธ ๊ฒฝ์ฐ
if (isAllChildRemoved()) {
result.add(this);
result.addAll(this.getChildList());
}
}
);
return result;
}
//๋ชจ๋ ์์ ๋๊ธ์ด ์ญ์ ๋์๋์ง ํ๋จ
private boolean isAllChildRemoved() {
return getChildList().stream()//https://kim-jong-hyun.tistory.com/110 ํน์ข
ํ๋ ์ฌ๋ํฉ๋๋ค.
.map(Comment::isRemoved)//์ง์์ก๋์ง ์ฌ๋ถ๋ก ๋ฐ๊พผ๋ค
.filter(isRemove -> !isRemove)//์ง์์ก์ผ๋ฉด true, ์์ง์์ก์ผ๋ฉด false์ด๋ค. ๋ฐ๋ผ์ filter์ ๊ฑธ๋ฌ์ง๋ ๊ฒ์ false์ธ ๋
์๋ค์ด๊ณ , ์๋ค๋ฉด false๋ฅผ ์๋ค๋ฉด orElse๋ฅผ ํตํด true๋ฅผ ๋ฐํํ๋ค.
.findAny()//์ง์์ง์ง ์์๊ฒ ํ๋๋ผ๋ ์๋ค๋ฉด false๋ฅผ ๋ฐํ
.orElse(true);//๋ชจ๋ ์ง์์ก๋ค๋ฉด true๋ฅผ ๋ฐํ
}
}
CommentRepository
public interface CommentRepository extends JpaRepository<Comment, Long> {
}
CommentService
public interface CommentService {
void save(Comment comment);
Comment findById(Long id) throws Exception;
List<Comment> findAll();
void remove(Long id) throws Exception;
}
CommentServiceImpl
@Service
@RequiredArgsConstructor
@Transactional
public class CommentServiceImpl implements CommentService{
private final CommentRepository commentRepository;
@Override
public void save(Comment comment) {
commentRepository.save(comment);
}
@Override
@Transactional(readOnly = true)
public Comment findById(Long id) throws Exception {
return commentRepository.findById(id).orElseThrow(() -> new Exception("๋๊ธ์ด ์์ต๋๋ค."));
}
@Override
@Transactional(readOnly = true)
public List<Comment> findAll() {
return commentRepository.findAll();
}
@Override
public void remove(Long id) throws Exception {
Comment comment = commentRepository.findById(id).orElseThrow(() -> new Exception("๋๊ธ์ด ์์ต๋๋ค."));
comment.remove();
List<Comment> removableCommentList = comment.findRemovableList();
removableCommentList.forEach(removableComment -> commentRepository.delete(removableComment));
}
}
์ ์ฒด ํ ์คํธ์ฝ๋
@SpringBootTest
@Transactional
class CommentServiceTest {
@Autowired
CommentService commentService;
@Autowired CommentRepository commentRepository;
@Autowired EntityManager em;
private void clear(){
em.flush();
em.clear();
}
private Long saveComment(){
Comment comment = Comment.builder().content("๋๊ธ").build();
Long id = commentRepository.save(comment).getId();
clear();
return id;
}
private Long saveReComment(Long parentId){
Comment parent = commentRepository.findById(parentId).orElse(null);
Comment comment = Comment.builder().content("๋๊ธ").parent(parent).build();
Long id = commentRepository.save(comment).getId();
clear();
return id;
}
// ๋๊ธ์ ์ญ์ ํ๋ ๊ฒฝ์ฐ
// ๋๋๊ธ์ด ๋จ์์๋ ๊ฒฝ์ฐ
// DB์ ํ๋ฉด์์๋ ์ง์์ง์ง ์๊ณ , "์ญ์ ๋ ๋๊ธ์
๋๋ค"๋ผ๊ณ ํ์
@Test
public void ๋๊ธ์ญ์ _๋๋๊ธ์ด_๋จ์์๋_๊ฒฝ์ฐ() throws Exception {
//given
Long commentId = saveComment();
saveReComment(commentId);
saveReComment(commentId);
saveReComment(commentId);
saveReComment(commentId);
Assertions.assertThat(commentService.findById(commentId).getChildList().size()).isEqualTo(4);
//when
commentService.remove(commentId);
clear();
//then
Comment findComment = commentService.findById(commentId);
assertThat(findComment).isNotNull();
assertThat(findComment.isRemoved()).isTrue();
assertThat(findComment.getChildList().size()).isEqualTo(4);
}
// ๋๊ธ์ ์ญ์ ํ๋ ๊ฒฝ์ฐ
//๋๋๊ธ์ด ์์ ์กด์ฌํ์ง ์๋ ๊ฒฝ์ฐ : ๊ณง๋ฐ๋ก DB์์ ์ญ์
@Test
public void ๋๊ธ์ญ์ _๋๋๊ธ์ด_์๋_๊ฒฝ์ฐ() throws Exception {
//given
Long commentId = saveComment();
//when
commentService.remove(commentId);
clear();
//then
Assertions.assertThat(commentService.findAll().size()).isSameAs(0);
assertThat( assertThrows(Exception.class, () -> commentService.findById(commentId)).getMessage()).isEqualTo("๋๊ธ์ด ์์ต๋๋ค.");
}
// ๋๊ธ์ ์ญ์ ํ๋ ๊ฒฝ์ฐ
// ๋๋๊ธ์ด ์กด์ฌํ๋ ๋ชจ๋ ์ญ์ ๋ ๊ฒฝ์ฐ
//๋๊ธ๊ณผ, ๋ฌ๋ ค์๋ ๋๋๊ธ ๋ชจ๋ DB์์ ์ผ๊ด ์ญ์ , ํ๋ฉด์์๋ ํ์๋์ง ์์
@Test
public void ๋๊ธ์ญ์ _๋๋๊ธ์ด_์กด์ฌํ๋_๋ชจ๋_์ญ์ ๋_๋๋๊ธ์ธ_๊ฒฝ์ฐ() throws Exception {
//given
Long commentId = saveComment();
Long reCommend1Id = saveReComment(commentId);
Long reCommend2Id = saveReComment(commentId);
Long reCommend3Id = saveReComment(commentId);
Long reCommend4Id = saveReComment(commentId);
Assertions.assertThat(commentService.findById(commentId).getChildList().size()).isEqualTo(4);
clear();
commentService.remove(reCommend1Id);
clear();
commentService.remove(reCommend2Id);
clear();
commentService.remove(reCommend3Id);
clear();
commentService.remove(reCommend4Id);
clear();
Assertions.assertThat(commentService.findById(reCommend1Id).isRemoved()).isTrue();
Assertions.assertThat(commentService.findById(reCommend2Id).isRemoved()).isTrue();
Assertions.assertThat(commentService.findById(reCommend3Id).isRemoved()).isTrue();
Assertions.assertThat(commentService.findById(reCommend4Id).isRemoved()).isTrue();
clear();
//when
commentService.remove(commentId);
clear();
//then
LongStream.rangeClosed(commentId, reCommend4Id).forEach(id ->
assertThat(assertThrows(Exception.class, () -> commentService.findById(id)).getMessage()).isEqualTo("๋๊ธ์ด ์์ต๋๋ค.")
);
}
// ๋๋๊ธ์ ์ญ์ ํ๋ ๊ฒฝ์ฐ
// ๋ถ๋ชจ ๋๊ธ์ด ์ญ์ ๋์ง ์์ ๊ฒฝ์ฐ
// ๋ด์ฉ๋ง ์ญ์ , DB์์๋ ์ญ์ X
@Test
public void ๋๋๊ธ์ญ์ _๋ถ๋ชจ๋๊ธ์ด_๋จ์์๋_๊ฒฝ์ฐ() throws Exception {
//given
Long commentId = saveComment();
Long reCommend1Id = saveReComment(commentId);
//when
commentService.remove(reCommend1Id);
clear();
//then
Assertions.assertThat(commentService.findById(commentId)).isNotNull();
Assertions.assertThat(commentService.findById(reCommend1Id)).isNotNull();
Assertions.assertThat(commentService.findById(commentId).isRemoved()).isFalse();
Assertions.assertThat(commentService.findById(reCommend1Id).isRemoved()).isTrue();
}
// ๋๋๊ธ์ ์ญ์ ํ๋ ๊ฒฝ์ฐ
// ๋ถ๋ชจ ๋๊ธ์ด ์ญ์ ๋์ด์๊ณ , ๋๋๊ธ๋ค๋ ๋ชจ๋ ์ญ์ ๋ ๊ฒฝ์ฐ
// ๋ถ๋ชจ๋ฅผ ํฌํจํ ๋ชจ๋ ๋๋๊ธ์ DB์์ ์ผ๊ด ์ญ์ , ํ๋ฉด์์์๋ ์ง์
@Test
public void ๋๋๊ธ์ญ์ _๋ถ๋ชจ๋๊ธ์ด_์ญ์ ๋_๊ฒฝ์ฐ_๋ชจ๋ _๋๋๊ธ์ด_์ญ์ ๋_๊ฒฝ์ฐ() throws Exception {
//given
Long commentId = saveComment();
Long reCommend1Id = saveReComment(commentId);
Long reCommend2Id = saveReComment(commentId);
Long reCommend3Id = saveReComment(commentId);
commentService.remove(reCommend2Id);
clear();
commentService.remove(commentId);
clear();
commentService.remove(reCommend3Id);
clear();
Assertions.assertThat(commentService.findById(commentId)).isNotNull();
Assertions.assertThat(commentService.findById(commentId).getChildList().size()).isEqualTo(3);
//when
commentService.remove(reCommend1Id);
//then
LongStream.rangeClosed(commentId, reCommend3Id).forEach(id ->
assertThat(assertThrows(Exception.class, () -> commentService.findById(id)).getMessage()).isEqualTo("๋๊ธ์ด ์์ต๋๋ค.")
);
}
// ๋๋๊ธ์ ์ญ์ ํ๋ ๊ฒฝ์ฐ
// ๋ถ๋ชจ ๋๊ธ์ด ์ญ์ ๋์ด์๊ณ , ๋ค๋ฅธ ๋๋๊ธ์ด ์์ง ์ญ์ ๋์ง ์๊ณ ๋จ์์๋ ๊ฒฝ์ฐ
//ํด๋น ๋๋๊ธ๋ง ์ญ์ , ๊ทธ๋ฌ๋ DB์์ ์ญ์ ๋์ง๋ ์๊ณ , ํ๋ฉด์์๋ "์ญ์ ๋ ๋๊ธ์
๋๋ค"๋ผ๊ณ ํ์
@Test
public void ๋๋๊ธ์ญ์ _๋ถ๋ชจ๋๊ธ์ด_์ญ์ ๋_๊ฒฝ์ฐ_๋ค๋ฅธ_๋๋๊ธ์ด_๋จ์์๋_๊ฒฝ์ฐ() throws Exception {
//given
Long commentId = saveComment();
Long reCommend1Id = saveReComment(commentId);
Long reCommend2Id = saveReComment(commentId);
Long reCommend3Id = saveReComment(commentId);
commentService.remove(reCommend3Id);
commentService.remove(commentId);
clear();
Assertions.assertThat(commentService.findById(commentId)).isNotNull();
Assertions.assertThat(commentService.findById(commentId).getChildList().size()).isEqualTo(3);
//when
commentService.remove(reCommend2Id);
Assertions.assertThat(commentService.findById(commentId)).isNotNull();
//then
Assertions.assertThat(commentService.findById(reCommend2Id)).isNotNull();
Assertions.assertThat(commentService.findById(reCommend2Id).isRemoved()).isTrue();
Assertions.assertThat(commentService.findById(reCommend1Id).getId()).isNotNull();
Assertions.assertThat(commentService.findById(reCommend3Id).getId()).isNotNull();
Assertions.assertThat(commentService.findById(commentId).getId()).isNotNull();
}
}
์ด๋ ๊ฒ ์ง๊ธ๊น์ง ์๊ฐ๋ง ํ๊ณ , ํ๋ฒ๋ ๊ตฌํํด ๋ณธ ์ ์๋ ๋๊ธ ์ญ์ ๋ก์ง์ ๊ตฌํํด ๋ณด์์ต๋๋ค.
๋ค์ ์๊ฐ์๋ ํ์ ์๋น์ค, ๊ฒ์๊ธ, ๋๊ธ ์๋น์ค ๋ชจ๋ ๊ตฌํํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
์ ์ฒด ์ฝ๋๋ ๊นํ๋ธ์์ ํ์ธํ์ค ์ ์์ต๋๋ค.
https://github.com/ShinDongHun1/SpringBoot-Board-API