우주먼지

💡 공지사항 기능

 

요구사항

PostMan API Docs 작성
https://documenter.getpostman.com/view/23682055/2s8ZDU6jkR


Open-Session-in-view 옵션: false (트랜잭션 내부의 프록시객체 초기화 필요)
연관관계 : Member 1   < - >    Notice N

@ManyToOne 단방향 매핑으로 진행
FetchType : Lazy

1. 공지사항 생성
2. 공지사항 수정
3. 공지사항 단건조회
4. 공지사항 전체조회(Pagenation)
5. 공지사항 삭제
6. 조회수 카운트(Update Query)

공지사항 생성

  • 공지사항 생성 시, HttpServletRequest를 파라미터로 받아
    유저의 Role (Admin, User, Counselor)을 검증 후 Admin Role이 아니면 Exception을 던짐

 

공지사항 수정

  • 쿼리파라미터로 Notice의 식별자를 입력받아 Entity에서 생성한 update 메서드를 이용해 필드값 수정
  • Member 프록시 객체 초기화 로직 필요

 

공지사항 단건조회

  • 쿼리파라미터로 Notice의 식별자를 입력받아 조회
  • Member 프록시 객체 초기화 로직 필요
  • @Transactional(readOnly = true)

 

공지사항 전체 조회

  • Pagenation을 이용하여 쿼리파라미터로 page, size를 입력받아 페이지 출력
  • Member 프록시 객체 초기화 로직 필요
  • @Transactional(readOnly = true)

 

공지사항 삭제

  • 쿼리파라미터로 Notice의 식별자를 입력받아 삭제

 

조회수 증가 로직

  • Repository 인터페이스에 JQPL을 이용한 Update Query 작성

💡 소스코드

 

Entity

@Getter @Setter
@Entity
@NoArgsConstructor
@AllArgsConstructor
public class Notice extends BaseEntity {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long noticeId;

    @Column(nullable = false, length = 50)
    private String title;

    @Column(nullable = false)
    private String content;

    @Column(columnDefinition = "integer default 0" ,nullable = false)
    private int views;

    // ------------------ 연관관계 매핑 ------------------
    @ManyToOne(optional = false, fetch = FetchType.LAZY)
    @JoinColumn(name = "MEMBER_ID")
    @JsonIgnore
    private Member member;

    public void setMember(Member member) {
        this.member = member;
    }

    public void update(String title, String content) {
        this.title = title;
        this.content = content;
    }
}

 

DTO

public class NoticeDto {

    @Getter
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Post {
        private String title;
        private String content;
    }

    @Getter
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Patch {
        private long noticeId;
        private String title;
        private String content;

        public void setNoticeId(long noticeId) {
            this.noticeId = noticeId;
        }
    }

    @Getter
    @Setter
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class SingleResponse {
        private long noticeId;
        private String title;
        private String content;
        private int views;
        private String writer;
        private LocalDateTime createdTime;

        public static SingleResponse of(Notice notice) {
            return SingleResponse.builder()
                    .noticeId(notice.getNoticeId())
                    .title(notice.getTitle())
                    .content(notice.getContent())
                    .views(notice.getViews())
                    .writer(notice.getMember().getMemberName())
                    .createdTime(notice.getCreateTime())
                    .build();
        }
    }

    @Getter
    @Setter
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class MultiResponse {
        private long noticeId;
        private String title;
        private int views;
        private String writer;
        private LocalDateTime createdTime;

        public static MultiResponse of(Notice notice) {
            return MultiResponse.builder()
                    .noticeId(notice.getNoticeId())
                    .title(notice.getTitle())
                    .views(notice.getViews())
                    .writer(notice.getMember().getMemberName())
                    .createdTime(notice.getCreateTime())
                    .build();
        }
    }
}

 

Mapper

@Mapper(componentModel = "spring")
public interface NoticeMapper {

    Notice postToEntity(NoticeDto.Post post);

    Notice patchToEntity(NoticeDto.Patch patch);

    default NoticeDto.SingleResponse entityToSingleResponse(Notice notice) {
        return NoticeDto.SingleResponse.of(notice);
    }

    default List<NoticeDto.MultiResponse> entityToMultiResponse(List<Notice> notices) {
        List<NoticeDto.MultiResponse> list = new ArrayList<NoticeDto.MultiResponse>(notices.size());

        for (Notice a : notices) {
            list.add(NoticeDto.MultiResponse.of(a));
        }

        return list;
    }
}

 

Repository

public interface NoticeRepository extends JpaRepository<Notice, Long> {


//    @Modifying
//    @Query("update Notice n set n.views = n.views + 1 where n.noticeId = :noticeId")
//    void updateViews(@Param("noticeId") Long noticeId);

    @Query(value = "select n FROM Notice n WHERE n.noticeId = :noticeId")
    Optional<Notice> findById(long noticeId);
}

 

Service

@Service
@Transactional
@RequiredArgsConstructor
public class NoticeService {
    // ----------------- DI ---------------------
    private final NoticeRepository noticeRepository;
    private final MemberService memberService;
    // ----------------- DI ---------------------

    // 공지 등록
    public Notice create(Notice notice, Member member) {

        notice.setMember(member);

        if (!member.getRole().equals(Role.ADMIN)) {
            throw new BusinessException(ErrorCode.FORBIDDEN_ADMIN);
        } else {
            notice.setMember(member);
            return noticeRepository.save(notice);
        }
    }

    // 공지 수정
    public Notice update(Notice notice) {
        Notice findNotice = findVerifiedNotice(notice.getNoticeId());
        String memberName = findNotice.getMember().getMemberName();

        findNotice.update(notice.getTitle(), notice.getContent());

        return noticeRepository.save(findNotice);
    }

    // 공지 1건 조회
    @Transactional(readOnly = true)
    public Notice find(long noticeId) {
        Notice notice = findVerifiedNotice(noticeId);

        String writer = notice.getMember().getMemberName();
        updateViews(noticeId);

        return notice;
    }

    // 공지 전체 조회
    @Transactional(readOnly = true)
    public Page<Notice> findAll(int page, int size) {
        Page<Notice> pageNotice = noticeRepository.findAll(PageRequest.of(page, size, Sort.by("noticeId").descending()));
        List<Notice> listNotice = pageNotice.getContent();

        for (Notice a : listNotice) {
            a.getMember().getMemberName();
        }

        return pageNotice;
    }

    // 공지 삭제
    public void delete(long noticeId) {

        Notice notice = findVerifiedNotice(noticeId);

        if (!notice.getMember().getRole().equals(Role.ADMIN)) {
            throw new BusinessException(ErrorCode.FORBIDDEN_ADMIN);
        } else {
            noticeRepository.deleteById(noticeId);
        }
    }

    // 조회수 증가 로직
    public void updateViews(Long id) {
        Notice notice = findVerifiedNotice(id);

        int findViews = notice.getViews() + 1;

        notice.setViews(findViews);

        noticeRepository.save(notice);
    }

    // 공지 검증
    public Notice findVerifiedNotice(long noticeId) {
        Optional<Notice> optNotice = noticeRepository.findById(noticeId);

        return optNotice.orElseThrow(() -> new EntityNotFoundException("글이 없습니다"));
    }
}

 

Controller

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/notices")
public class NoticeController {
    private final NoticeMapper mapper;
    private final NoticeService noticeService;
    private final MemberService memberService;

    @PostMapping("/post")
    public ResponseEntity postNotice(@Valid @RequestBody NoticeDto.Post post,
                                     HttpServletRequest request) {
        Member member = memberService.getLoginMember(request);

        Notice findNotice = noticeService.create(mapper.postToEntity(post), member);

        return new ResponseEntity<>(new SingleResponseDto<>(mapper.entityToSingleResponse(findNotice)), HttpStatus.CREATED);
    }

    @PatchMapping("/patch/{notice-id}")
    public ResponseEntity patchNotice(@PathVariable("notice-id") @Positive long noticeId,
                                      @Valid @RequestBody NoticeDto.Patch patch) {
        patch.setNoticeId(noticeId);

        Notice update = noticeService.update(mapper.patchToEntity(patch));

        return new ResponseEntity<>(new SingleResponseDto<>(mapper.entityToSingleResponse(update)), HttpStatus.OK);
    }

    @GetMapping("/lookup/{notice-id}")
    public ResponseEntity get(@PathVariable("notice-id") @Positive long noticeId) {
        Notice findNotice = noticeService.find(noticeId);

        return new ResponseEntity(new SingleResponseDto<>(mapper.entityToSingleResponse(findNotice)), HttpStatus.OK);
    }

    @GetMapping("/lookup/list")
    public ResponseEntity getAll(@Positive @RequestParam(defaultValue = "1") int page,
                                 @Positive @RequestParam(defaultValue = "10") int size) {
        Page<Notice> pageNotice = noticeService.findAll(page-1, size);
        List<Notice> notices = pageNotice.getContent();

        return new ResponseEntity<>(new MultiResponseDto<>(mapper.entityToMultiResponse(notices), pageNotice), HttpStatus.OK);
    }

    @DeleteMapping("/delete/{notice-id}")
    public ResponseEntity delete(@PathVariable("notice-id") @Positive long noticeId) {
        noticeService.delete(noticeId);
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }
}

 

profile

우주먼지

@o귤o

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

검색 태그