[ BoardApplication.java ]
@EnableScheduling //스케줄링 사용 시 설정
@SpringBootApplication
public class BoardApplication {
public static void main(String[] args) {
SpringApplication.run(BoardApplication.class, args);
}
}
[ PostRepository.interface ]
Repository에 메서드 추가
예약여부가 null인 appointment만 조회하는 메서드이다.
Page<Post> findByAppointment(String appointment, Pageable pageable);
[ Post.class ]
appointment : 예약여부 (예약하면 Y 아니면 null 값으로 세팅)
appointmentTime : 예약 시간
private String appointment;
private LocalDateTime appointmentTime;
public void updateAppointment(String appointment){
this.appointment = appointment;
}
[ PostService.java ]
예약 여부 필드가 추가 되었기 때문에, DB에서 게시글들을 조회할 때 예약되지 않은 글들만 가져와야 한다.
findByAppointment 메서드를 호출할 때 첫 번째 매개변수로 null 값을 넘기면서,
DB에 저장된 post들 중 예약되지 않은 것들 중 pageable에서 정의한 size 개수만큼 글들을 가져오게 된다.
이때, Page 클래스는 iterator문 사용이 불가하기 때문에 .map을 통해 조회한 Post 객체들을 PostListResDto로 바꿔준다.
public Page<PostListResDto> findByAppointment(Pageable pageable){
Page<Post> posts = postRepository.findByAppointment(null, pageable);
Page<PostListResDto> PostListResDtos
= posts.map(post -> new PostListResDto(post.getId(), post.getTitle(), post.getAuthor()==null?"익명유저":post.getAuthor().getEmail()));
return PostListResDtos;
}
예약 여부에 따라 게시글을 save하는 로직이 많이 바뀐다. (중요하다)
사용자가 작성한 게시글의 예약 여부가 Y이고, 예약 시간이 담겨있다면
예약 여부와 예약 시간을 각각 appointment, localDateTime 변수에 담아준다.
예약 관련 정보가 없다면 없는대로(null) post 객체를 생성한다.
public void save(PostCreateReqDto postCreateReqDto) throws IllegalArgumentException{
Author author = authorRepository.findByEmail(postCreateReqDto.getEmail()).orElse(null);
LocalDateTime localDateTime = null;
String appointment = null;
if (postCreateReqDto.getAppointment().equals("Y") && //YES인 경우에만 DB에 Y, NO이면 null 세팅
!postCreateReqDto.getAppointmentTime().isEmpty()){
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm");
localDateTime = LocalDateTime.parse(postCreateReqDto.getAppointmentTime() , dateTimeFormatter);
LocalDateTime now = LocalDateTime.now();
if(localDateTime.isBefore(now)){
throw new IllegalArgumentException("시간정보 잘못입력");
}
appointment = "Y";
}
Post post = Post.builder()
.title(postCreateReqDto.getTitle())
.contents(postCreateReqDto.getContents())
.author(author)
.appointment(appointment)
.appointmentTime(localDateTime)
.build();
// 더티체킹 테스트
// author.updateAuthor("dirty checking test", "1234");
// authorRepository.save(author);
postRepository.save(post);
}
[ PostScheduler.java ]
cron의 동작을 설정하는 클래스. @Component 어노테이션을 통해 스프링 빈으로 등록한다.
예약 시간에 맞춰 예약 업로드를 해야하는 post들을 조회한다.
이때 페이징처리는 하지 않기 때문에 Pageable.unpaged()라는 메서드를 이용한다.
가져온 Page<Post> posts에는 게시글들이 이런식으로 들어있기 때문에
posts.getContent() 메서드를 통해 각 게시글 하나하나에 접근할 수 있다.
예약된 게시글들 중, 예약 시간이 현재 시간보다 이전인 게시글을 매일 1분 0초마다 확인해 업로드 시켜준다.
updateAppointment() 메서드를 통해 예약 여부를 null(NO)로 설정만 하는데,
@Transactional 어노테이션을 붙여, 중간에 문제가 생기지 않는다면 자동으로 수정 사항이 dirty checking되어
appointment 값만 수정하고, 값이 변경된 객체를 따로 save() 해주지 않아도 변경사항이 반영되어 save 된다.
@Component
public class PostScheduler {
private final PostRepository postRepository;
@Autowired
public PostScheduler(PostRepository postRepository) {
this.postRepository = postRepository;
}
// 초 분 시간 일 월 요일 형태로 스케줄링 설정
// * : 매 초(분/시 등)을 의미, 0/특정숫자 : 특정숫자마다 반복
// 특정 숫자 : 특정 숫자의 초(분/시 등)을 의미
// 초 분 시 일 월 요일
// 0 0 * * * * : 매일 0분 0초에 스케줄링 시작
// 0 0/1 * * * * : 매일 1분마다 0초에 스케줄링 시작
// 0/1 * * * * * : 매초마다
// 0 0 11 * * * : 매일 11시에 스케줄링
@Scheduled(cron = "0 0/1 * * * *")
@Transactional //한 트랜잭션의 끝에 commit하는데, commit이 save()와 같은 개념이라 자동 더티체킹된다 ⭐
public void postSchedule(){
System.out.println("===스케줄러 시작===");
Page<Post> posts = postRepository.findByAppointment("Y", Pageable.unpaged());
for(Post post : posts.getContent()){
if(post.getAppointmentTime().isBefore(LocalDateTime.now())){
post.updateAppointment(null);
// postRepository.save(post);
}
}
System.out.println("===스케줄러 끝===");
}
}
[ post-create.html ]
<div class="form-group">
<label>예약여부</label>
<input type="radio" id="appointment" name="appointment" value="Y"> YES
<input type="radio" style="padding-left: 30px;" name="appointment" value="N" checked="checked"> NO
<label>예약시간</label>
<input type="datetime-local" id="appointmentTime" name="appointmentTime">
</div>
현재 게시글 목록 - 게시글 4개
게시글을 등록할 때, 1분 뒤로 예약 글쓰기 설정
게시글을 등록했을 때 시간 : 오후 4시 21분
게시글이 페이지에 업로드 되어야 하는 시간 : 오후 4시 22분
DB에 값 삽입
appointment_time 시간이 현재 시간보다 작아지는 경우, 스케줄러를 통해 appointment 값이 null로 설정되고
해당 게시글이 화면에 보여지게 된다.
1분 뒤 게시글 목록 - 게시글 5개
'Back-End 공부 > Spring' 카테고리의 다른 글
[스프링] dirtychecking(변경 감지)과 cascading(삽입, 삭제) (0) | 2024.01.26 |
---|---|
[스프링] 지연로딩, 즉시로딩, N+1문제 제대로 이해하기 (0) | 2024.01.26 |
[스프링] Paging 기능 구현 (0) | 2024.01.26 |
Spring에서 발생하는 대표적인 순환참조 문제 해결하기 (0) | 2024.01.24 |
생성자 객체 생성 방식의 단점을 극복한 builder 패턴 (1) | 2024.01.23 |