1. ✅요구사항
좋아요 기능에 Soft Delete를 적용하여 삭제될 시 deleted_at에 삭제된 시간을 기록한다.
2. ✅코드
2.1. 🔹좋아요 기능
2.1.1. Like
<code />
@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "likes")
@SQLDelete(sql = "UPDATE likes SET deleted_at = now() WHERE id = ?")
public class Like extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@ManyToOne
@JoinColumn(name = "post_id")
private Post post;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
public static Like toEntity(Post post, User user) {
return Like.builder()
.post(post)
.user(user)
.build();
}
}
Soft Deleted를 적용하기 위해서 @SQLDelete 애너테이션을 사용했다. 좋아요가 삭제가 될 시 Deleted_at컬럼에 시간이 들어가게 된다.
@Where은 좋아요 취소 로직을 구현하기 위해서는 삭제된 데이터의 조회도 필요했기 때문에 사용하지 않았다.
Soft Deleted에 관한 내용은 JPA 포스팅에 올려놓았다.
https://alcoholble.tistory.com/7
[JPA] Soft Delete 적용하기
Soft Delete가 왜 필요한가? SNS 프로젝트를 진행하면서 한가지 상황을 가정해보았다. 누군가가 법적으로 침해되는 게시물이나 댓글을 작성하여 신고를 하려고한다. 하지만 그 회원이 탈퇴를 하면
alcoholble.tistory.com
2.1.2. LikeService
좋아요 취소 후 다시 좋아요를 눌러도 계속 좋아요 취소만 뜬다.
이유는 아래와 같이 이전의 기능구현은 hard Delete로 했기 때문에 like의 존재 유무로 좋아요와 취소를 구현했기 때문이다.
하지만, Soft Delete로 변경한 지금 db에 데이터가 남아있기 때문에 다시 좋아요를 눌러도 남아있는 데이터로 계속 취소밖에 뜨지 않은 상황이였다.
<code />
public String controlLike(Long postId, String userName) {
//유저와 게시물의 존재 유무 확인
ValidateUserPostDto validateUserPost = validateService.validateUserPost(userName, postId);
//like db에 user과 post가 같은게 존재하는지 확인
Optional<Like> like = likeRepository.findByUserAndPost(validateUserPost.getUser(), validateUserPost.getPost());
//좋아요와 취소 로직
if(like.isPresent()){
//알람에서 type, 작성한 유저, 해당 포스트가 같을경우
Optional<Alarm> alarm = alarmRepository.cancelAlarm(AlarmType.LIKE.getAlarmType(), validateUserPost.getUser().getId(), validateUserPost.getPost().getId());
if(alarm.isPresent()){
alarmRepository.delete(alarm.get());
}
likeRepository.delete(like.get());
return "좋아요를 취소했습니다.";
}else{
likeRepository.save(Like.toEntity(validateUserPost.getPost(), validateUserPost.getUser()));
//알람 저장
Alarm alarm = Alarm.toEntity(AlarmType.LIKE, validateUserPost.getUser(), validateUserPost.getPost());
alarmRepository.save(alarm);
return "좋아요를 눌렀습니다.";
}
}
위의 문제를 해결하기 위해서는 좋아요를 누를 때 deleted_at을 null로 다시 변경해주는 로직이 필요하다고 생각했다.
Soft Delete로 좋아요와 좋아요 취소를 구현하기 위해서 4가지의 경우의 수로 나누었다.
- like db에 데이터가 아에 없는경우 - 처음 좋아요를 누를경우
- like db에 데이터가 있는경우 - 좋아요나 좋아요 취소를 진행한 적이 있는 경우
- deletedAt이 null일 경우 - 좋아요 취소 누른 경우
- deletedAt에 데이터가 있을 경우 - 좋아요 다시 누른 경우
이렇게 분리해서 코드로 구현했다.
<code />
public String controlLike(Long postId, String userName) {
//유저와 게시물의 존재 유무 확인
ValidateUserPostDto validateUserPost = validateService.validateUserPost(userName, postId);
//like db에 user과 post가 같은게 존재하는지 확인
Optional<Like> like = likeRepository.findByUserAndPost(validateUserPost.getUser(), validateUserPost.getPost());
//좋아요를 처음 누를 경우
if (!like.isPresent()) {
likeRepository.save(Like.toEntity(validateUserPost.getPost(), validateUserPost.getUser()));
Alarm alarm = Alarm.toEntity(AlarmType.LIKE, validateUserPost.getUser(), validateUserPost.getPost());
alarmRepository.save(alarm);
return "좋아요를 눌렀습니다.";
//이미 좋아요 및 취소를 누른 적 있는 경우
}else{
//deletedAt 기록 확인
LocalDateTime likeDeleteAt = like.get().getDeletedAt();
//좋아요 취소
if (likeDeleteAt == null) {
Optional<Alarm> alarm = alarmRepository.cancelAlarm(AlarmType.LIKE.getAlarmType(), validateUserPost.getUser().getId(), validateUserPost.getPost().getId());
if (alarm.isPresent()) {
alarmRepository.delete(alarm.get());
}
likeRepository.delete(like.get());
return "좋아요를 취소했습니다.";
//다시 좋아요 누르기
} else {
likeRepository.reSave(like.get().getId());
//알람 저장
Alarm alarm = Alarm.toEntity(AlarmType.LIKE, validateUserPost.getUser(), validateUserPost.getPost());
alarmRepository.save(alarm);
return "좋아요를 눌렀습니다.";
}
}
}
2.1.3. LikeRepository
<java />
@Modifying
@Query("update Like l set l.deletedAt = null where l.id = :likeId ")
void reSave(@Param("likeId") Integer likeId);
다시 좋아요를 누르게 되면 해당 deleted_at을 다시 null로 변환하는 쿼리를 작성해주었다.
또한, update쿼리를 날려주기 위해서는 @Modifying를 붙어주었다.
@Modifying이란?

해석: 쿼리를 수정하는 것을 나타내는 것. 이 애너테이션은 쿼리 메서드에만 사용이 됩니다.
2.1.4. @Modifying
추가적으로, 현재 상황에는 단건만 update를 하기 때문에 적용되지 않지만
벌크 연산으로 진행할 경우에는 @Query를 사용할 때 @Modifying에 있는 clearAutomatically, flushAutomatically 파라미터 설정을 통해서 영속성 컨텍스트 관리에 주의해야한다.
깊게 확인해보면 실제 데이터베이스 결과는 정상적으로 업데이트 되겠지만, 같은 영속성 컨텍스트에 저장 되어 있는 값은 변경이 안된다.
벌크연산이란?
다건의 UPDATE, DELETE 연산을 하나의 쿼리로 변경하는 것
이유는 벌크연산에서 @Query로 정의된 JPQL은 기존 JPA처럼 영속성 컨텍스트를 거쳐 동작하는 것이 아닌 바로DataBase에 질의를 하게 된다.
@Query로 정의된 JPQL은 직접 DB에 변경 쿼리를 날리게 되고, 이미 같은 ID로 영속성 컨텍스트에 저장되어있는 데이터는 무시하게 된다.
즉, 변경 이후에도 요청을 할 경우 영속성 컨텍스트에 있는 변경되지 않는 값이 나오게 된다.
해당 이슈는 clearAutomatically, flushAutomatically를 설정함으로써 해결 가능하다.
2.2. 🔹좋아요 조회
2.2.1. LikeService
이전의 포스트와는 크게 달라진 것이 없다.
<java />
public Integer countLike(Long postId) {
//유저와 게시물의 존재 유무 확인
Post post = validateService.validatePost(postId);
//해당 포스트의 like 개수 확인
Integer likeCnt = likeRepository.countBypost(post);
return likeCnt;
}
2.2.2. LikeRepository
좋아요 취소를 Soft Delete로 구현하였으니 마찬가지로 좋아요 조회도 db에서 가져오는 쿼리를 변경하였다.
이전에는 CountByPost를 통해서 모든 post의 좋아요 개수를 가져왔다면, 지금은 deleted가 null인 post만 가져오도록 했다.
좋아요 기능에는 @Where이 붙지 않았으니 직접적으로 where deletedAt is null 이라는 쿼리를 추가하였다.
<java />
@Query("select count(l.id) from Like l where l.post = :post and l.deletedAt is null ")
Integer countBypost(@Param("post") Post post);
soft delete를 적용하면서 좋아요 취소가 있는 좋아요 기능을 리펙토링하였다. 하지만 결과적으로 좋아요 취소 로직이 너무 복잡하고 가독성이 좋지 않아 다시 작은 메서드로 쪼개서 리펙토링 할 예정이다.
리펙토링 후에 다시 블로그에 포스팅하겠다!
3. Reference
https://joojimin.tistory.com/71
Spring Data JPA @Modifying 알아보기
벌크 연산 관련 공부를 하다가 @Modifying에 대해 자세히 알아볼 필요가 생겨 공부한 내용입니다 https://docs.spring.io/spring-data/data-jpa/docs/current/api/org/springframework/data/jpa/repository/Modifying.html Modifying (Spri
joojimin.tistory.com
https://devhyogeon.tistory.com/4
Spring Data JPA @Modifying (1) - clearAutomatically
이 글을 작성하게 된 계기는 Spring Data JPA의 @Modifying에 있는 flushAutomatically에 대해 의문점이 생겼고, 그에 대한 학습 테스트를 해보면서 였습니다. 하지만 @Modifying의 Attribute가 clearAutomatically, flushAu
devhyogeon.tistory.com
'프로젝트 > SNS만들기' 카테고리의 다른 글
[SNS Project] 마이피드 테스트 (0) | 2023.01.06 |
---|---|
[SNS Project] 마이피드 기능 구현 (0) | 2023.01.06 |
[SNS Project] Post, User 예외처리 분리하기 (0) | 2023.01.04 |
[SNS Project] 좋아요 테스트 (0) | 2023.01.04 |
[SNS Project] 좋아요 조회 (0) | 2023.01.04 |