Todo 프로젝트에서 Soft Delete를 적용해 논리적으로 데이터가 삭제된 것처럼 구현하였다.
실제 데이터가 삭제된 것은 아니기 때문에 불필요한 데이터가 계속 쌓이게 된다.
불필요한 데이터를 계속 저장해 둘 필요는 없기 때문에 주기적으로 삭제 처리된 데이터를 지우려고 한다.
이런 경우 Spring Scheduler를 사용하면 정해진 시간마다 삭제 작업을 처리할 수 있다.
다음과 같이 @EnableScheduling 어노테이션을 추가한다.
import org.springframework.scheduling.annotation.EnableScheduling
@EnableScheduling // 스케줄링 활성화
@SpringBootApplication
class TodolistApplication
fun main(args: Array<String>) {
runApplication<TodolistApplication>(*args)
}
Scheduler 작업을 모아둘 클래스를 생성해 다음과 같이 정상 동작하는지 테스트할 코드를 작성한다.
import org.springframework.scheduling.annotation.Scheduled
@Component
class ScheduledDeletionTasks {
private val logger = LoggerFactory.getLogger("ScheduledDeletionTasks")
@Scheduled(fixedRate = 5000) // 메서드 호출 간격
fun reportCurrentTime() {
logger.info("The time is now {}", LocalDateTime.now())
}
}
@Scheduled 어노테이션 속성
- fixedRate: 이전 작업 실행 여부와 관계없이 지정된 밀리초(ms)마다 실행
- 작업의 실행이 독립적일 때 유용
- 작업이 빨리 완료되지 않으면 메모리 부족 예외 발생
- cron: "초 분 시 일 월 요일"순으로 지정 → 초, 분: 0 ~ 59 / 시간: 0 ~ 23 / 일: 1 ~ 31 / 월: 1 ~ 12 / 요일: 0 ~ 7
- fixedDelay: 작업 실행 완료 시간과 다음 작업 실행 시간 사이에 밀리초의 지연을 두고 실행
- 항상 하나의 작업 인스턴스만 실행되도록 해야 할 때 유용
- initialDelay: 초기 지연 시간 설정(일회성 작업)
다음은 위 코드의 실행 결과로 5초마다 현재 날짜와 시간이 로그로 출력된다.
Scheduler가 정상적으로 실행되는 것을 확인했으니 실제 필요한 작업에 대한 코드를 작성한다.
@Component
class ScheduledDeletionTasks(
private val todoRepository: TodoRepository
) {
@StopWatch
@Scheduled(cron = "0 0/5 * * * *") // 5분마다 실행
fun reportCurrentTime() {
todoRepository.deleteByIsDeletedTrue()
}
}
서버를 실행시킨 후 scheduler가 수행되는지 확인하는데 where절에 is_deleted 컬럼에 대한 조건이 두 개나 들어있는 것을 볼 수 있었다.
이 이상한 쿼리가 발생한 이유는 Entity 클래스에 다음과 같이 어노테이션이 적용되어 있기 때문이다.
@Entity
@SQLRestriction("is_deleted = false") // org.hibernate.annotations.SQLRestriction
@SQLDelete(sql = "UPDATE todo SET is_deleted = true WHERE id = ?") // delete 쿼리 수행 시 update 처리
class Todo private constructor(
// ...
) : BaseEntity() {
// ...
}
@SQLRestriction 어노테이션에 설정한 is_deleted 컬럼의 값이 false인 조건과 삭제를 위해 데이터를 조회할 때 is_deleted 컬럼의 값이 true인 데이터를 찾으면서 같은 컬럼에 대해 true, false 조건이 모두 들어가게 된 것이다.
이 문제를 해결하기 위해서는 @SQLRestriction 어노테이션을 제거하고 각 조회 쿼리에 is_deleted 컬럼의 값이 false인 데이터만 필터링하는 조건을 설정해야 한다.
그 이유는 @SQLRestriction 어노테이션을 적용하면 해당 Entity를 조회하는 모든 쿼리에 where 절이 추가되기 때문이다.
JPQL, QueryDSL에도 전부 반영돼서 삭제 쿼리를 실행할 때도 where 절에 @SQLRestriction 어노테이션에 적용한 is_deleted = false가 추가되어 데이터를 삭제할 수 없게 된다.
이유를 알았으니 이제 코드를 수정한다.
Entity 클래스는 다음과 같이 @SQLRestriction 어노테이션을 제거한다.
@Entity
@SQLDelete(sql = "UPDATE todo SET is_deleted = true WHERE id = ?") // delete 쿼리 수행 시 update 처리
class Todo private constructor(
// ...
) : BaseEntity() {
// ...
}
전체 조회에 대한 QueryDSL은 다음처럼 where 조건에 isDeleted가 false인 값만 필터링 하도록 작성한다.
queryFactory.selectFrom(todo)
.where(todo.isDeleted.isFalse.and(where))
.offset(pageable.offset)
.limit(pageable.pageSize.toLong())
.fetch()
개별 조회는 쿼리 메서드 방식으로 작성했다.
interface TodoRepository : JpaRepository<Todo, Long>, CustomTodoRepository {
fun findByIdAndIsDeleted(id: Long, isDeleted: Boolean): Todo?
}
코드를 수정한 후 서버를 재시작해서 Scheduler가 실행되는 것을 보면 삭제 쿼리가 정상적으로 수행되고 있고 실제 DB에서도 데이터가 삭제된 것을 볼 수 있다.
삭제 테스트를 위해 설정했던 시간(5분)만 실제 적용할 시간으로 변경하면 된다.
'개발노트' 카테고리의 다른 글
Controller / Service / Repository의 역할 (0) | 2024.06.24 |
---|---|
[AWS] Elastic Compute Cloud (0) | 2024.06.24 |
[Spring Boot] Soft Delete 적용 (0) | 2024.06.20 |
TIL - Entity의 Setter 사용을 지양하기 (0) | 2024.06.19 |
[AWS] Identity and Access Management (0) | 2024.06.18 |