Spring Example: Community #3 Service, Repository 개발

함형주·2023년 1월 4일
0

질문, 피드백 등 모든 댓글 환영합니다.

Controller에 이어 Service 계층을 개발하겠습니다.

개발 순서는 PostService (PostRepository) -> HeartService -> CommentService -> MemberService 입니다.

기본적으로 Service에서 DTO 를 생성하고 반환하여 Controller 가 Entity를 의존하지 않도록 개발했습니다. 또한 잘못된 접근을 시도한다면 (조회한 엔티티가 null) 예외를 발생시켰습니다.

PostService

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

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를 페치 조인하였습니다.

Dto

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

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

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

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();
    }
}

DTO

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

CommentRepository

public interface CommentRepository extends JpaRepository<Comment, Long> {
}

CommentRepository는 쿼리메서드(ex) findById()...)만 사용합니다.

MemberService

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() 구문을 이용하여 변경 감지기능으로 수정해주었습니다.

DTO

MemberJoinDto

@Getter @Setter
public class MemberJoinDto {
    private String loginId;
    private String password;
    private String name;
}

MemberRepository

MemberRepository

public interface MemberRepository extends JpaRepository<Member, Long> {

    Optional<Member> findByLoginId(String loginId);

    boolean existsByLoginId(String loginId);
}

다음으로

Controller, Service, Repository 개발을 완료했으니 화면에 출력할 HTML 파일을 Thymeleaf와 Bootstrap을 사용하여 만들어주겠습니다.

github , 배포 URL (첫 접속 시 로딩이 걸릴 수 있습니다.)

profile
평범한 대학생의 공부 일기?

0개의 댓글