Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.whereyouad.WhereYouAd.domains.notification.domain.service.scheduler;

import com.whereyouad.WhereYouAd.domains.notification.persistence.repository.NotificationDeliveryRepository;
import com.whereyouad.WhereYouAd.domains.notification.persistence.repository.NotificationRepository;
import com.whereyouad.WhereYouAd.domains.notification.persistence.repository.UserNotificationRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Slf4j
@Component
@RequiredArgsConstructor
public class NotificationCleanupExecutor {

private final NotificationRepository notificationRepository;
private final UserNotificationRepository userNotificationRepository;
private final NotificationDeliveryRepository notificationDeliveryRepository;

@Transactional
public void deleteNotificationChunk(List<Long> notificationIds) {
int userNotificationCount = userNotificationRepository.deleteByNotificationIdIn(notificationIds);
int deliveryCount = notificationDeliveryRepository.deleteByNotificationIdIn(notificationIds);
notificationRepository.deleteAllByIdInBatch(notificationIds);

log.debug("만료 알림 청크 삭제 - 알림(Notification): {}건, 회원 알림 내역(UserNotification): {}건, 발송 이력(NotificationDelivery): {}건",
notificationIds.size(), userNotificationCount, deliveryCount);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.whereyouad.WhereYouAd.domains.notification.domain.service.scheduler;

import com.whereyouad.WhereYouAd.domains.notification.persistence.repository.NotificationRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.PageRequest;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;

@Slf4j
@Component
@RequiredArgsConstructor
public class NotificationCleanupScheduler {

private static final int RETENTION_DAYS = 30;
private static final int CHUNK_SIZE = 1000;

private final NotificationRepository notificationRepository;
private final NotificationCleanupExecutor notificationCleanupExecutor;

@Scheduled(cron = "0 0 0 * * *", zone = "Asia/Seoul")
public void deleteOldNotifications() {
// 스케줄러 실행 시점으로 부터 만료 기준일 (30일 과거) 설정
LocalDateTime threshold = LocalDate.now(ZoneId.of("Asia/Seoul")).minusDays(RETENTION_DAYS).atStartOfDay();
log.info("오래된 알림 내역 삭제 스케줄러 실행 - threshold: {}", threshold);

int totalDeleted = 0;
while (true) {
// 삭제 대상인 알림 내역 (30일 이상 지난 내역) Id 를 List 조회
List<Long> notificationIds =
notificationRepository.findIdsByCreatedAtBefore(threshold, PageRequest.of(0, CHUNK_SIZE));

// 삭제 대상인 알림 내역이 없으면 종료
if (notificationIds.isEmpty()) {
break;
}

// 청크 단위 알림 내역 삭제
notificationCleanupExecutor.deleteNotificationChunk(notificationIds);
totalDeleted += notificationIds.size();
}

log.info("오래된 알림 내역 삭제 완료 - 삭제 알림 갯수: {}", totalDeleted);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@
import com.whereyouad.WhereYouAd.domains.organization.persistence.entity.Organization;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;

@Entity
@Table(name = "notification")
@Table(name = "notification", indexes = {
// 오래된 알림 삭제 스케줄러의 청크 조회용 인덱스
@Index(name = "idx_notification_created_at_id", columnList = "created_at, notification_id")
})
@EntityListeners(AuditingEntityListener.class)
@Getter
@Builder
Expand Down Expand Up @@ -42,6 +47,7 @@ public class Notification {
// 연관 관계
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "org_id", nullable = false)
@OnDelete(action = OnDeleteAction.CASCADE)
Comment thread
ojy0903 marked this conversation as resolved.
private Organization organization;

@Enumerated(EnumType.STRING)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public class NotificationDelivery extends BaseEntity {

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "notification_id", nullable = false)
@OnDelete(action = OnDeleteAction.CASCADE)
private Notification notification;

@ManyToOne(fetch = FetchType.LAZY)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import org.springframework.util.StringUtils;

@Entity
Expand All @@ -22,6 +24,7 @@ public class OrgNotificationSetting extends BaseEntity {
@OneToOne(fetch = FetchType.LAZY)
@MapsId // PK == FK(식별 관계)
@JoinColumn(name = "org_id")
@OnDelete(action = OnDeleteAction.CASCADE)
private Organization organization;

@Column(name = "slack_webhook_url", length = 512)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;

import java.time.LocalDateTime;

Expand All @@ -30,10 +32,12 @@ public class UserNotification {
// 연관관계
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "notification_id", nullable = false)
@OnDelete(action = OnDeleteAction.CASCADE)
private Notification notification;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
@OnDelete(action = OnDeleteAction.CASCADE)
private User user;

// 알림 읽음 처리
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,17 @@

import com.whereyouad.WhereYouAd.domains.notification.persistence.entity.NotificationDelivery;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;


public interface NotificationDeliveryRepository extends JpaRepository<NotificationDelivery, Long> {

// 만료 알림 정리용 - 발송 이력 벌크 삭제
@Modifying
@Query("DELETE FROM NotificationDelivery nd WHERE nd.notification.id IN :notificationIds")
int deleteByNotificationIdIn(@Param("notificationIds") List<Long> notificationIds);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
package com.whereyouad.WhereYouAd.domains.notification.persistence.repository;

import com.whereyouad.WhereYouAd.domains.notification.persistence.entity.Notification;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.time.LocalDateTime;
import java.util.List;

public interface NotificationRepository extends JpaRepository<Notification, Long> {

// 보관 기간이 지난 알림 id 조회 (청크 단위로 끊어서 조회)
@Query("SELECT n.id FROM Notification n WHERE n.createdAt < :threshold ORDER BY n.createdAt, n.id")
List<Long> findIdsByCreatedAtBefore(@Param("threshold") LocalDateTime threshold, Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.springframework.data.repository.query.Param;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

public interface UserNotificationRepository extends JpaRepository<UserNotification, Long> {
Expand Down Expand Up @@ -56,4 +57,8 @@ int markAllAsRead(@Param("userId") Long userId,
@Param("orgId") Long orgId,
@Param("now") LocalDateTime now);

// 만료 알림 정리용 - 회원별 알림 내역 벌크 삭제
@Modifying
@Query("DELETE FROM UserNotification un WHERE un.notification.id IN :notificationIds")
int deleteByNotificationIdIn(@Param("notificationIds") List<Long> notificationIds);
}
Loading