ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Quartz를 이용하여 softDelete한 내용을 실제로 delete 하기
    🌱 spring/🚛 spring batch 2022. 12. 28. 18:52

    이번에는 Quartz를 이용하여 이전 프로젝트에서 사용해봤던 softDelete를 한 데이터에 대해 특정 시간이 지났을 경우 실제로 데이터를 삭제(hard delete)하는 작업을 해보았다.

     

    softDelete를 하기 위해 예제로 user entity를 만들어 사용했다.

     

    User.java

    @Entity
    @Table(name = "user")
    @Where(clause = "is_deleted = false")
    @SQLDelete(sql = "UPDATE user SET is_deleted = true WHERE id = ?")
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    @AllArgsConstructor
    @Getter
    public class User extends BaseEntity {
    
      @Id
      @GeneratedValue
      private Long id;
    
      @Column(name = "name")
      private String name;
    
      @Column(name = "age")
      private Integer age;
    
      @Column(name = "is_deleted")
      private Boolean isDeleted;
    
      public User(String name, Integer age) {
        this.name = name;
        this.age = age;
        isDeleted = Boolean.FALSE;
      }
    }
    
    • 삭제를 할 경우 SQLDelete로 인해 실제 데이터가 삭제되지 않고 isDeleted 가 true로 update 된다.

     

    이렇게 isDeleted가 true가 된 데이터들을 실제로 삭제하는 작업을 하기 위해

    UserRepository interface에서 isDeleted 된 유저정보들을 가져오는 함수와 실제로 삭제하는 함수를 native query를 통해 생성해주었다.

     

    UserRepository

    public interface UserRepository extends JpaRepository<User, Long> {
    
      @Query(value = "SELECT * FROM user AS u WHERE u.is_deleted = true", nativeQuery = true)
      List<User> findDeleteUserList();
    
      @Modifying
      @Query(value = "DELETE FROM user AS u WHERE u.is_deleted = true AND u.id = :userId", nativeQuery = true)
      void hardDelete(@Param("userId") Long userId);
    }
    

    ⚠️ 주의

    • @Modifying 어노테이션을 작성해주어야 한다!!
    • native query를 통해 작성된 INSERT, UPDATE, DELETE 쿼리에서는 사용해주어야 한다.
      • SELECT 제외
    • 이 어노테이션을 작성하지 않아서 다음과 같은 에러가 계속 발생했었다.

     

    Job


    userService.java

    @Transactional
    public void deleteStep(LocalDateTime now){
    
        List<User> userByIsDeleted = userRepository.findDeleteUserList();
    
        List<User> deleteUsers = userByIsDeleted.stream()
            .filter(user -> gapDate(user.getUpdatedAt(), now))
            .collect(Collectors.toList());
    
        for (User deleteUser : deleteUsers) {
          userRepository.hardDelete(deleteUser.getId());
        }
    
      }
    
    private boolean gapDate(LocalDateTime updateDate, LocalDateTime now) {
      Duration between = Duration.between(updateDate, now);
      if(between.getSeconds() > 120) return true;
      return false;
    }
    • 예제를 위해서 해당 시간과 삭제된(updated된) 시간이 2분이상 차이가 날 경우 hardDelete하도록 하였다.

     

    Job 생성

    Controller

    @PostMapping("/jobs")
      public ResponseEntity<?> addScheduleJob(@RequestBody JobRequest jobRequest) throws SchedulerException {
    
        JobKey jobKey = new JobKey(jobRequest.getJobName(), jobRequest.getJobGroup());
    
        if(!schedulerServiceImpl.isJobExist(jobKey)) {
          schedulerServiceImpl.addJob(jobRequest, DbCronJob.class);
        } else {
          return new ResponseEntity<>(new ApiResponse(false, "Job already exists"), HttpStatus.CONFLICT);
        }
    
        return new ResponseEntity<>(new ApiResponse(true, "Job created Successfully"), HttpStatus.CREATED);
      }
    • 이미 있는 job이 아니라면 Job 생성

     

    DbCronJob

    public class DbCronJob  extends QuartzJobBean {
    
      private static Logger log = LoggerFactory.getLogger(DbCronJob.class);
    
      @Autowired
      private UserService userService;
    
      @Override
      protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        JobKey jobKey = context.getJobDetail().getKey();
        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
    
        LocalDateTime now = LocalDateTime.now();
    
        // 현재 시간을 기반으로 삭제된지 지정한 시간이 지난 데이터 삭제하기
        userService.deleteStep(now);
      }
    }

     

    SchedulerService

    public boolean addJob(JobRequest jobRequest, Class<? extends Job> jobClass) {
    
        JobKey jobKey = null;
        JobDetail jobDetail;
        Trigger trigger;
    
        try {
          if(jobClass.equals(CronJob.class) || jobClass.equals(DbCronJob.class)) {
            trigger = JobUtils.createCronTrigger(jobRequest);
          }
          else trigger = JobUtils.createTrigger(jobRequest);
          jobDetail = JobUtils.createJobDetail(jobRequest, jobClass);
          jobKey = JobKey.jobKey(jobRequest.getJobName(), jobRequest.getJobGroup());
          Scheduler scheduler = schedulerFactoryBean.getScheduler();
    
          Date dt = scheduler.scheduleJob(jobDetail, trigger);
          log.debug("Job with jobKey : {} scheduled successfully at date : {}", jobDetail.getKey(), dt);
          return true;
        } catch (SchedulerException e) {
          log.error("error occurred while scheduling with jobKey : {}", jobKey, e);
        }
        return false;
      }

     

    JobUtils

    public static CronTrigger createCronTrigger(JobRequest jobRequest) {
        CronTrigger trigger = newTrigger()
            .withIdentity(jobRequest.getJobName(), jobRequest.getJobGroup())
            .startNow()
            .withSchedule(CronScheduleBuilder.cronSchedule("0 0/1 18 * * ?"))
            .build();
    
        return trigger;
      }
    • Job의 실행을 trigger 한다.
    • cron trigger를 생성해 schedule이 언제 실행되어야 하는지 설정해주고 주었다.
    • 매일 18시에 1분 간격(19시까지)으로 실행하도록 했다.

     

    테스트


    유저 생성

     

    유저 삭제(soft delete)

    - is_deleted = true 가 되었다.

     

    Quartz를 통한 hard delete

     

    Job 추가 후 1분 후

    • 아직 삭제 쿼리가 발생하지 않았다.

     

    2분후

    • DELETE 쿼리가 발생

    • 데이터가 삭제되었다.

     

    참고자료

    https://joojimin.tistory.com/71

    https://astrid-dm.tistory.com/495

    '🌱 spring > 🚛 spring batch' 카테고리의 다른 글

    Quartz2  (0) 2022.12.22
    Quartz (1)  (0) 2022.12.22
    Spring Batch - ItemProcessor  (0) 2022.12.16
    Spring Batch - ItemWriter  (0) 2022.12.16
    SpringBatch - ItemReader  (0) 2022.12.16

    댓글

Designed by Tistory.