Spring
미션의 새로운 요구사항으로 수정과 삭제에 쿨타임을 두게 되었다.
처음에는 괜히 어렵게 세션을 이용해서 구현해야하나 싶었는데 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()를 호출
이후 모든 테스트는 통과