이전에 작성한 Soft delete 적용한 코드에서는 deleted_at 컬럼이 null이면 삭제되지 않은 것으로 간주하고 조회하도록 @Where 절을 이용하여 처리하고 있었습니다. 그러나 이로 인해 삭제된 포스트 목록을 조회하는 경우 문제가 발생했습니다. 아래와 같이 코드를 작성하였으나 삭제된 포스트 목록을 조회하려고 해도 @where로 인한 조건과 where절이 중첩되어 빈 결과만 반환되었습니다.
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Where(clause = "deleted_at IS NULL")
@SQLDelete(sql = "UPDATE post SET deleted_at = CURRENT_TIMESTAMP WHERE post_id = ?")
public class Post extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "post_id")
private Integer id;
private String title;
private String body;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
@OneToMany(mappedBy = "comment", cascade = CascadeType.ALL)
private List<Comment> comments = new ArrayList<>();
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL)
private List<Like> likes = new ArrayList<>();
//.. 중략
}
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/posts")
public class PostRestController {
private final PostService postService;
//..중략
@ApiOperation(value = "삭제된 피드 목록", notes = "삭제 된 포스트 목록")
@GetMapping("/deleted")
public Response<Page<PostDto>> getDeletedPost(@PageableDefault(size = 20)
@SortDefault(sort = "deleted_at", direction = Sort.Direction.DESC) Pageable pageable, Authentication authentication) {
String userName = authentication.getName();
Page<PostDto> deletedPosts = postService.getDeletedPost(pageable, userName);
return Response.success(deletedPosts);
}
//..중략
}
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional
public class PostService {
private final UserRepository userRepository;
private final PostRepository postRepository;
//..중략
@Transactional(readOnly = true)
public Page<PostDto> getDeletedPost(Pageable pageable, String userName) {
User user = userRepository.findByUserName(userName)
.orElseThrow(() -> new SnsAppException(USERNAME_NOT_FOUND, USERNAME_NOT_FOUND.getMessage()));
Page<Post> deletedPosts = postRepository.findAllByDeletedAtNotNull(pageable);
return PostDto.toDtoList(deletedPosts);
}
//..중략
}
public interface PostRepository extends JpaRepository<Post, Integer> {
//..중략
// deleteAt이 null이 아닌것 조회(삭제 된것 조회)
Page<Post> findAllByDeletedAtNotNull(Pageable pageable);
}
where절에 deleted_at is null과 deleted_at is not null이 중첩되어 있어 빈 결과를 반환한다.
첫 번째 해결 방법은 @Query 어노테이션을 활용하여 네이티브 쿼리를 작성하는 것입니다.
이로인해 @Where 설정을 회피할 수 있습니다.
public interface PostRepository extends JpaRepository<Post, Integer> {
//..중략
// 수정 전
Page<Post> findAllByDeletedAtNotNull(Pageable pageable);
// 수정 후 ( nativeQuery 적용 )
@Query(value = "SELECT * FROM post WHERE deleted_at IS NOT NULL", nativeQuery = true)
Page<Post> getDeletedPosts(Pageable pageable);
}
아래와 같이 @Where 조건이 적용되지 않아 정상적으로 작동한다.
두 번째 해결 방법은 @Where대신 Hibernate의 @FilterDef와 @Filter를 활용하여 삭제된 포스트를 필터링하는 것입니다.
@FilterDef는 Hibernate에서 사용자 정의 필터를 정의하는 어노테이션입니다. name 속성은 필터의 이름을 지정하고, parameters 속성은 필터에 사용될 파라미터를 정의합니다. 위의 코드에서는 deletedPostFilter라는 이름의 필터를 정의하고, isDeleted라는 이름의 파라미터를 사용하겠다고 선언하였습니다.
@Filter는 @FilterDef에서 정의한 필터를 실제로 엔티티에 적용하는 어노테이션입니다. name 속성은 적용할 필터의 이름을 지정하고, condition 속성은 필터의 조건을 지정합니다. 조건은 SQL 조건식으로, 필터가 적용될 때 해당 조건에 따라 엔티티가 필터링됩니다. 위의 코드에서는 deletedPostFilter라는 이름의 필터를 사용하고, deleted_at 컬럼이 null이 아닌 경우를 필터링 조건으로 정의하였습니다.
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@SQLDelete(sql = "UPDATE post SET deleted_at = CURRENT_TIMESTAMP WHERE post_id = ?")
@FilterDef(
name = "deletedPostFilter",
parameters = @ParamDef(name = "isDeleted", type = "boolean")
)
@Filter(
name = "deletedPostFilter",
condition = "(deleted_at IS NOT NULL) = :isDeleted"
)
public class Post extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "post_id")
private Integer id;
private String title;
private String body;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
@OneToMany(mappedBy = "comment", cascade = CascadeType.ALL)
private List<Comment> comments = new ArrayList<>();
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL)
private List<Like> likes = new ArrayList<>();
// ..중략
}
FilterManager 클래스는 필터를 활성화하고 비활성화하기 위한 메서드를 제공하는 클래스입니다.
@Component
@RequiredArgsConstructor
public class FilterManager {
private final EntityManager entityManager;
/**
* 지정된 필터를 활성화하고 파라미터를 설정합니다.
*
* @param filterName 활성화할 필터의 이름
* @param paramName 설정할 파라미터의 이름
* @param paramValue 설정할 파라미터의 값
*/
public void enableFilter(String filterName, String paramName, Object paramValue) {
Session session = entityManager.unwrap(Session.class);
Filter filter = session.enableFilter(filterName);
filter.setParameter(paramName, paramValue);
}
/**
* 지정된 필터를 비활성화합니다.
*
* @param filterName 비활성화할 필터의 이름
*/
public void disableFilter(String filterName) {
Session session = entityManager.unwrap(Session.class);
session.disableFilter(filterName);
}
}
위에서 생성한 필터 관리 클래스를 서비스 클래스에서 활용하여 필터링을 적용합니다.
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional
public class PostService {
private final UserRepository userRepository;
private final PostRepository postRepository;
private final LikeRepository likeRepository;
private final CommentRepository commentRepository;
private final FilterManager filterManager;
//.. 중략
/**
* 모든 게시물을 조회합니다. (삭제되지 않은 것만)
*
* @param pageable 페이지 정보
* @return 게시물 DTO 페이지
*/
@Transactional(readOnly = true)
public Page<PostDto> getAllPost(Pageable pageable) {
// DELETED_POST_FILTER를 활성화하여 삭제된 게시물만 조회하도록 설정합니다.
filterManager.enableFilter(FilterConstants.DELETED_POST_FILTER, FilterConstants.DELETED_POST_AT_PARAM, false);
// 페이지 정보를 이용하여 게시물을 조회합니다.
Page<Post> posts = postRepository.getPosts(pageable);
// DELETED_POST_FILTER를 비활성화하여 원래 상태로 복구합니다.
filterManager.disableFilter(FilterConstants.DELETED_POST_FILTER);
// DTO로 변환
return PostDto.toDtoList(posts);
}
/**
* 삭제된 게시물을 조회합니다.
*
* @param pageable 페이지 정보
* @param userName 사용자 이름
* @return 삭제된 게시물 DTO 페이지
*/
@Transactional(readOnly = true)
public Page<PostDto> getAllDeletedPost(Pageable pageable, String userName) {
// 사용자 정보 가져오기 및 해당 사용자의 삭제된 게시물 조회
User user = userRepository.findByUserName(userName)
.orElseThrow(() -> new SnsAppException(USERNAME_NOT_FOUND, USERNAME_NOT_FOUND.getMessage()));
// DELETED_POST_FILTER를 활성화하여 삭제된 게시물만 조회하도록 설정합니다.
filterManager.enableFilter(FilterConstants.DELETED_POST_FILTER, FilterConstants.DELETED_POST_AT_PARAM, true);
// 페이지 정보를 이용하여 게시물을 조회합니다.
Page<Post> posts = postRepository.getPosts(pageable);
// DELETED_POST_FILTER를 비활성화하여 원래 상태로 복구합니다.
filterManager.disableFilter(FilterConstants.DELETED_POST_FILTER);
// DTO로 변환
return PostDto.toDtoList(posts);
}
//.. 중략
}
아래와 같이 정상적으로 작동한다.