질문, 피드백 등 모든 댓글 환영합니다.
Controller에 이어 Service 계층을 개발하겠습니다.
개발 순서는 PostService (PostRepository) -> HeartService -> CommentService -> MemberService 입니다.
기본적으로 Service에서 DTO 를 생성하고 반환하여 Controller 가 Entity를 의존하지 않도록 개발했습니다. 또한 잘못된 접근을 시도한다면 (조회한 엔티티가 null) 예외를 발생시켰습니다.
PostService
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class PostService {
private final PostRepository postRepository;
private final MemberRepository memberRepository;
public List<PostListDto> findList() {
List<Post> find = postRepository.findPostList();
return find.stream().map(post -> {
return new PostListDto(post.getId(), post.getTitle(), post.getMember().getName(),
post.getHeartNum(), post.getCommentNum(), post.getCreatedDate());
}).collect(Collectors.toList());
}
public PostDto findPostAndComment(Long post_id) {
Post findPost = postRepository.findById(post_id).orElseThrow(IllegalArgumentException::new);
List<CommentDto> commentDtos = findPost.getComments().stream().map(comment -> {
return new CommentDto(comment.getId(), comment.getBody(), comment.getMember().getId(), comment.getMember().getName());
}).collect(Collectors.toList());
return new PostDto(findPost.getId(), findPost.getTitle(), findPost.getBody(), findPost.getMember().getId(), findPost.getMember().getName(), findPost.getHeartNum(), findPost.getCreatedDate(), commentDtos);
}
public WritePostDto findWritePostDto(Long post_id) {
Post findPost = postRepository.findById(post_id).orElseThrow(IllegalArgumentException::new);
return new WritePostDto(findPost.getId(), findPost.getTitle(), findPost.getBody());
}
}
기본적인 조회 로직은 Repository에서 엔티티 조회 후 DTO 스펙에 맞게 변환합니다.
public class PostService {
@Transactional
public Long createPost(WritePostDto writePostDto, Long id) {
Member findMember = memberRepository.findById(id).orElseThrow(IllegalArgumentException::new);
Post post = Post.createPost(writePostDto.getTitle(), writePostDto.getBody(), findMember);
return postRepository.save(post).getId();
}
@Transactional
public void updatePost(Long post_id, WritePostDto writePostDto) {
Post post = postRepository.findById(post_id).orElseThrow(IllegalArgumentException::new);
post.update(writePostDto.getTitle(), writePostDto.getBody());
}
@Transactional
public void delete(Long post_id) {
postRepository.deleteById(post_id);
}
}
updatePost() 는 게시글의 수정을 담당하는 로직으로 Post 엔티티를 조회하여 JPA의 변경 감지(더티 체킹) 기능으로 수정했습니다.
PostRepository
public interface PostRepository extends JpaRepository<Post, Long> {
@Query("select p from Post p left join fetch p.member")
List<Post> findPostList();
}
findPostList()
에서 JPQL을 이용하여 Post를 조회하며 연관된 Member를 페치 조인하였습니다.
PostDto
@Getter @Setter
@AllArgsConstructor
public class PostDto {
private Long id;
private String title;
private String body;
private Long member_id;
private String membername;
private Integer heartNum;
private LocalDateTime createdDate;
private List<CommentDto> commentDtos;
}
PostListDto
@Getter @Setter
@AllArgsConstructor
public class PostListDto {
private Long id;
private String title;
private String membername;
private int HeartNum;
private int commentNum;
private LocalDateTime createdDate;
}
WritePostDto
@Getter
@Setter
@AllArgsConstructor
public class WritePostDto {
private Long id;
private String title;
private String body;
}
HeartService
@Service
@RequiredArgsConstructor
public class HeartService {
private final PostRepository postRepository;
private final MemberRepository memberRepository;
private final HeartRepository heartRepository;
@Transactional
public void changeHeartStatus(Long post_id, Long member_id) {
heartRepository.findByMemberIdAndPostId(post_id, member_id).ifPresentOrElse(
heart -> {
heartRepository.delete(heart);
heart.getPost().minusHeartNum();
},
() -> {
Post post = postRepository.findById(post_id).orElseThrow(IllegalArgumentException::new);
Member member = memberRepository.findById(member_id).orElseThrow(IllegalArgumentException::new);
heartRepository.save(Heart.createHeart(post, member));
post.plusHeartNum();
});
}
}
게시글에 좋아요를 남기는 기능은 이 메서드 하나로 처리합니다.
기존에 '좋아요' 요청을 보낸 이력이 있는 경우 (조회 결과가 존재) Heart 테이블을 삭제하고 게시글 좋아요 개수를 -1 시킵니다.
반대의 경우 Heart 테이블을 생성하고 게시글 좋아요 개수를 +1 시킵니다.
HeartRepository
public interface HeartRepository extends JpaRepository<Heart, Long> {
@Query("select h from Heart h where h.post.id = :post_id and h.member.id = :member_id")
Optional<Heart> findByMemberIdAndPostId(@Param("post_id") Long post_id, @Param("member_id") Long member_id);
}
스프링 데이터 JPA는 엔티티를 외래키로 조회 시 Join 하여 조회하므로 JPQL로 직접 작성하였습니다. 해당 부분은 이전에 작성한 블로그 참고 바랍니다.
CommentService
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class CommentService {
private final CommentRepository commentRepository;
private final PostRepository postRepository;
private final MemberRepository memberRepository;
public EditCommentDto findCommentDto(Long comment_id) {
Comment comment = commentRepository.findById(comment_id).orElseThrow(IllegalArgumentException::new);
return new EditCommentDto(comment.getId(), comment.getBody());
}
@Transactional
public void save(CommentDto commentDto, Long post_id, Long member_id) {
Post post = postRepository.findById(post_id).orElseThrow(IllegalArgumentException::new);
Member member = memberRepository.findById(member_id).orElseThrow(IllegalArgumentException::new);
Comment comment = Comment.createComment(commentDto.getBody(), member, post);
commentRepository.save(comment);
post.plusCommentNum();
}
@Transactional
public void update(Long comment_id, CommentDto commentDto) {
Comment comment = commentRepository.findById(comment_id).orElseThrow(IllegalArgumentException::new);
comment.update(commentDto.getBody());
}
@Transactional
public void delete(Long comment_id, Long post_id) {
Post post = postRepository.findById(post_id).orElseThrow(IllegalArgumentException::new);
commentRepository.deleteById(comment_id);
post.minusCommentNum();
}
}
CommentDto
@Getter @Setter
@AllArgsConstructor
public class CommentDto {
private Long id;
private String body;
private Long member_id;
private String membername;
}
EditCommentDto
@Getter @Setter
@AllArgsConstructor
public class EditCommentDto {
private Long id;
private String body;
}
CommentRepository
public interface CommentRepository extends JpaRepository<Comment, Long> {
}
CommentRepository는 쿼리메서드(ex) findById()...
)만 사용합니다.
MemberService
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
@Transactional
public void save(MemberJoinDto memberJoinDto) {
if (memberRepository.existsByLoginId(memberJoinDto.getLoginId())) throw new IllegalArgumentException();
memberRepository.save(Member.createMember(memberJoinDto.getLoginId(),
bCryptPasswordEncoder.encode(memberJoinDto.getPassword()),
memberJoinDto.getName().strip()));
}
@Transactional
public void delete(Long member_id) {
Member member = memberRepository.findById(member_id).orElseThrow(IllegalArgumentException::new);
List<Comment> comments = member.getComments();
comments.forEach(comment -> comment.getPost().minusCommentNum());
List<Heart> hearts = member.getHearts();
hearts.forEach(like -> like.getPost().minusHeartNum());
memberRepository.delete(member);
}
}
회원을 삭제할 때에는 연관된 Post, Comment, Heart 가 같이 삭제되므로(CascadeType.REMOVE
) 그에 따라 게시글의 댓글 수, 좋아요 수를 수정해야합니다.
회원과 연관된 Comment, Heart를 forEach() 구문을 이용하여 변경 감지기능으로 수정해주었습니다.
MemberJoinDto
@Getter @Setter
public class MemberJoinDto {
private String loginId;
private String password;
private String name;
}
MemberRepository
public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findByLoginId(String loginId);
boolean existsByLoginId(String loginId);
}
Controller, Service, Repository 개발을 완료했으니 화면에 출력할 HTML 파일을 Thymeleaf와 Bootstrap을 사용하여 만들어주겠습니다.
Escape to a realm of leisure and indulgence with the finest Noida Escorts. We recognize the demand for real connection and provide a sanctuary where your fantasies are fulfilled with elegance and discretion. Ditch the moments and adopt the impressions. Our agency is committed to delivering an experience beyond compare, ensuring each meeting is designed to your specific tastes. Find the sophistication of adult companionship here in Noida.