본문 바로가기

Spring

일정 관리 서버 Develop : 댓글, 일정 페이징 조회 기능 추가

생성 시간 및 수정시간 관리 : JPA Auditing

  • JPA Auditing : 엔티티의 생성, 수정 시간을 자동으로 기록하고 관리해주는 기능
  • @CreatedDate : 엔티티가 처음 저장될 때 생성 시간 저장
  • @LastModifiedDate : 엔티티가 업데이트될 때 수정 시간 저장 

설정방법

  1. @EnableJpaAuditing를 활성화
  2. @EntityListeners(AuditingEntityListener.class)를 엔티티에 추가
  3. 추상 클래스 필드에 @CreatedDate, @LastModifiedDate 등의 어노테이션 사용
  4. 나머지 객체에서 추상클래스를 extends해서 사용
@EnableJpaAuditing // Auditing 기능(entity 생성, 수정 시간 자동으로 기록) 활성화
@SpringBootApplication
public class ScheduleDevelopApplication {
}
@Getter
@MappedSuperclass // 해당 클래스를 구현했을 때 데이터베이스에 잘 적용되도록 해줌
@EntityListeners(AuditingEntityListener.class) // Auditing 리스너 등록, 생성되거나 수정될 때 값을 업데이트 함
public abstract class BaseEntity {

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime modifiedAt;
}

 

Comment Entity

  • 생성 및 수정 시간을 기록하는 클래스인 BaseEntity를 상속 
  • Schedule클래스와 Member클래스를 단방향 연관관계로 알고 있음 (어떤 게시물의 댓글인지, 누가 썼는지 알아야 하므로!)
@Getter
@Entity
@Table(name = "comment")
public class Comment extends BaseEntity{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String content;

    @ManyToOne()
    @JoinColumn(name = "schedule_id")
    private Schedule schedule;

    @ManyToOne()
    @JoinColumn(name = "member_id")
    private Member member;

    @Builder
    public Comment(String content) {
        this.content = content;
    }

    public Comment() {
    }

    public void setSchedule(Schedule schedule){
        this.schedule = schedule;
    }

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

    public void updateComment(String content){
        this.content = content;
    }
}

 

댓글 생성

  • CommentSaveRequestDto : scheduleId, memberId, comment
  • Dto에서 받아온 값으로 Member, Schedule객체를 조회하고 Comment객체를 생성해서 set해준 후 저장함
  • 모든 작업 단위는 @Transactional을 붙여줘야 확실하게 실행 단위 관리가 되기 때문에 붙여줌 (조회는 (readOnly = true))
@Transactional
    public CommentResponseDto save(CommentSaveRequestDto requestDto) {
        Member member = memberRepository.findById(requestDto.getMemberId()).orElseThrow(() -> new MemberNotFoundException());
        Schedule schedule = scheduleRepository.findById(requestDto.getScheduleId()).orElseThrow(() -> new ScheduleNotFoundException());

        Comment comment = Comment.builder()
                                .content(requestDto.getComment())
                                .build();

        comment.setMember(member);
        comment.setSchedule(schedule);

        schedule.addComment(comment);

        Comment savedComment = commentRepository.save(comment);

        return CommentResponseDto.buildDto(savedComment);
    }

 

댓글 조회

  • 댓글 id 값을 통해서 Comment객체 조회
  • 조회이므로 @Transactional(readOnly = true)를 붙여주면 성능이 향상!
@Transactional(readOnly = true)
    public CommentResponseDto findById(Long id) {
        Comment comment = commentRepository.findById(id).orElseThrow(() -> new CommentNotFoundException());
        return CommentResponseDto.buildDto(comment);
    }

 

댓글 수정

  • CommentUpdateRequestDto : comment (댓글 내용)
  • Comment 엔티티 내부에 있는 updateComment 메서드를 사용하여 set 해줌
  • @Transactional을 무조건 명시해 줘야 함! -> JPA의 변경 감지가 작동하는 범위, 명시해주지 않는다면 따로 save()를 해줘야 함!
 @Transactional
    public CommentResponseDto update(Long id, CommentUpdateRequestDto requestDto) {
        Comment comment =  commentRepository.findById(id).orElseThrow(() -> new CommentNotFoundException());
        comment.updateComment(requestDto.getComment());
        return CommentResponseDto.buildDto(comment);
    }

 

댓글 삭제

@Transactional
    public void delete(Long id) {
        Comment comment =  commentRepository.findById(id).orElseThrow(() -> new CommentNotFoundException());
        commentRepository.delete(comment);
    }

 

일정 페이징 조회

ScheduleEntity

  • 조회할 데이터 중에서 "댓글의 개수"가 있었기 때문에 Comment와 Schedule을 양방향 연관관계로 설정
  • addComment 메서드를 Comment를 추가
@Getter
@Entity
@Table(name = "schedule")
public class Schedule extends BaseEntity{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String title;

    @Column(nullable = false)
    private String content;

    @ManyToOne
    @JoinColumn(name = "member_id")
    private Member member;

    @OneToMany(mappedBy = "schedule", fetch = FetchType.LAZY)
    private List<Comment> comments = new ArrayList<>();

    @Builder
    public Schedule(String title, String content) {
        this.title = title;
        this.content = content;
    }

    public Schedule() {
    }

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

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

    public void addComment(Comment comment){
        this.comments.add(comment);
    }
}

 

구현

  • page, size를 쿼리 파라미터로 받음
@GetMapping()
    public ResponseEntity<Page<SchedulePageResponseDto>> findAll(@RequestParam(value = "page", defaultValue = "0") int page,
                                                                 @RequestParam(value = "size", defaultValue = "10") int size){
        Page<SchedulePageResponseDto> scheduleResponseDto = scheduleService.findAll(page, size);
        return new ResponseEntity<>(scheduleResponseDto, HttpStatus.OK);
    }

 

  • repository에 Page타입으로 리턴받는 조회 메서드 추가
@Repository
public interface ScheduleRepository extends JpaRepository<Schedule, Long> {
    Page<Schedule> findAll(Pageable pageable);
}

 

  • PageRequest 를 통해서 page, size를 입력
  • Page<Schedule>로 받아온 객체를 map함수를 통해서 Page<SchedulePageResponseDto>로 변경
  • 회원명과 댓글개수는 schedule엔티티 내부에있는 각각의 엔티티에서 조회해 옴
@Transactional(readOnly = true)
    public Page<SchedulePageResponseDto> findAll(int page, int size) {
        Pageable pageable = PageRequest.of(page, size);
        Page<Schedule> schedulePage = scheduleRepository.findAll(pageable);
        return schedulePage.map(schedule -> {
            Member member = schedule.getMember();
            Long commentCount = (long) schedule.getComments().size();
            return SchedulePageResponseDto.builder()
                    .title(schedule.getTitle())
                    .content(schedule.getContent())
                    .author(member.getName())
                    .commentCount(commentCount)
                    .createdAt(schedule.getCreatedAt())
                    .modifiedAt(schedule.getModifiedAt())
                    .build();
        });
    }

 

  • Application에 @EnableSpringDataWebSupport를 추가하면 Page객체에서 핵심 내용만 조회할 수 있음
@EnableSpringDataWebSupport(pageSerializationMode = VIA_DTO) // Page 객체에서 필요한 부분만 조회할 수 있음
@SpringBootApplication
public class ScheduleDevelopApplication {

    public static void main(String[] args) {
        SpringApplication.run(ScheduleDevelopApplication.class, args);
    }

 

결과

요청 : http://localhost:8080/schedules?page=1&size=3