TIL - day46

정상화·2023년 4월 26일
0

TIL

목록 보기
35/46
post-thumbnail

Spring

그램그램 프로젝트 리포지토리 링크

LikeablePerson의 수정 삭제에 30분의 쿨타임 두기

미션의 새로운 요구사항으로 수정과 삭제에 쿨타임을 두게 되었다.

처음에는 괜히 어렵게 세션을 이용해서 구현해야하나 싶었는데 LikeablePerson 엔티티의 modifyDate을 와 LocalDateTime.now() 만으로 구현 가능했다.

LikeablePersonValidator

@Component
@RequiredArgsConstructor
public class LikeablePersonValidator {
    //...

    public RsData<LikeablePerson> checkCoolTime(String message, RsData<LikeablePerson> rsData) {
        LikeablePerson likeablePerson = (LikeablePerson) rsData.getAttribute("likeablePerson");

        LocalDateTime now = LocalDateTime.now();
        LocalDateTime modifyDate = likeablePerson.getModifyDate();
        long minutesBetween = ChronoUnit.MINUTES.between(modifyDate, now);

        if (minutesBetween < 30) {
            return RsData.of("F-5", message);
        }
        return rsData;
    }
}

LikeablePersonService

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class LikeablePersonService {
    private final LikeablePersonRepository likeablePersonRepository;
    private final LikeablePersonValidator validator;

    private final ApplicationEventPublisher publisher;
    @Transactional
    public RsData<LikeablePerson> like(Member member, String username, int attractiveTypeCode) {
        return RsData.produce(LikeablePerson.class)
                .then(rsData -> validator.checkOwnInstagramId(member, rsData))
                .then(rsData -> validator.checkSelfLike(member, username, rsData))
                .then(rsData -> validator.checkAlreadyLike(member, username, attractiveTypeCode, rsData))
                .then(rsData -> validator.checkMaximumLike(member, rsData))
                .then(rsData -> successfulLike(member, username, attractiveTypeCode, rsData))
                .catchEx(rsData -> validator.checkCoolTime("호감사유는 30분마다 수정가능합니다.", rsData))
                .catchEx(rsData -> changeReason(attractiveTypeCode, rsData));
    }
    
    //...
    
    @Transactional
    public RsData cancel(LikeablePerson likeablePerson) {
        return RsData.produce(LikeablePerson.class)
                .then(rsData -> {
                    rsData.setAttribute("likeablePerson", likeablePerson);
                    return rsData;
                })
                .then(rsData -> validator.checkCoolTime("호감갱신 후 30분 뒤에 삭제가능합니다.",rsData ))
                .then(this::successfulCancel);
    }
}

구현은 간단했으나 문제는 테스트였다.

기존의 호감사유변경, 호감삭제 관련 테스트들이 실패하기 시작했다.
현재시간에 의존적인 테스트는 직접 modifyDate를 수정하는 것이 편하다는 의견을 듣고 그렇게 해봤다

근데 안된다.

private void turnBackModifyDate(LikeablePerson likeablePerson){
	TestUtil.setPrivateField(likeablePerson, "modifyDate", LocalDateTime.now().minusMinutes(30));
    em.persist(likeablePerson);
    em.flush();
}

이런식으로 엔티티의 필드를 수정하고 영속화한 후 flush를 했으나 자꾸 modifyDate이 원상복귀가 되는 문제가 있었다.

EntityManager도 써보고 리포지토리로도 해봐도 안돼서 테스트 중에만 현재시스템시간을 바꿔볼까 했는데 그건 그거대로 안됐다.

@Builder
@NoArgsConstructor
@AllArgsConstructor
@EntityListeners(AuditingEntityListener.class)
@ToString
@Entity
@Getter
public class LikeablePerson {
    @Id
    @GeneratedValue(strategy = IDENTITY)
    private Long id;
    @CreatedDate
    private LocalDateTime createDate;
    @LastModifiedDate
    private LocalDateTime modifyDate;
    
    //...
}

@LastModifedDate가 없으면 날짜 자동갱신이 안되고 빼고 쓰자니 테스트를 위해 엔티티를 수정하는 바보짓이라는 생각이 들어서 생짜sql을 날리기로 했다.

public interface LikeablePersonRepository extends JpaRepository<LikeablePerson, Long> {
    //...

    @Modifying
    @Query(value = """
            UPDATE likeable_person
            SET modify_date = CURRENT_TIMESTAMP() - INTERVAL '40' MINUTE
            WHERE id = ?
            """
            , nativeQuery = true
    )
    void changeModifyDate(@Param("id") Long id);
}

테스트에서 H2인메모리db를 사용해서 h2문법으로 생짜쿼리를 만들었다.
jpql로 짜고 dialect설정을 해봤는데 이러면 역시 작동을 안한다. 하이버네이트 문제인건지...

public class TestUtil {
	public static void changeModifyDateForce(Long id, LikeablePersonRepository repository, EntityManager em) {
        repository.changeModifyDate(id); // 생짜쿼리 실행
        em.refresh(repository.findById(id).get()); // 영속성 컨텍스트 동기화
    }
}

생짜쿼리를 날렸기때문에 영속성컨텍스트와 DB가 dirty한 상황이다.
영속성 컨텍스트의 엔티티를 db에 맞추기 위해 refresh()를 호출

이후 모든 테스트는 통과

profile
백엔드 희망

0개의 댓글