화가 날 뻔한 상황
코드에는 에러가 없다. 근데 Delete가 실행이 안 된다. 왜 안 되지? 난 화가 날 뻔했다. 화가 나기 전에 해결이 되어서 다행이다.
코드는 다음과 같다.
public DictLikeResponseDto likeDict(UserDetailsImpl userDetails, Long dictId) {
// 로그인 체크
ValidChecker.loginCheck(userDetails);
User user = jwtAuthenticateProcessor.getUser(userDetails);
Dict dict = getSafeDict(dictId);
/*
1. 좋아요 중일 시 : 좋아요 취소
2. 좋아요 중이 아닐 시 : 좋아요
*/
boolean isLike = false;
if (isDictLike(dict, userDetails)) {
DictLike dictLike = getSafeDictLike(user, dict);
dictLikeRepository.deleteById(dictLike.getDictLikeId());
} else {
DictLike dictLike = DictLike.builder()
.dict(dict)
.user(user)
.build();
dictLikeRepository.save(dictLike);
isLike = true;
}
return DictLikeResponseDto.builder()
.result(isLike)
.build();
}
private boolean isDictLike(Dict dict, UserDetailsImpl userDetails) {
// 1. 로그인하지 않았으면 무조건 false.
// 2. dictLikeList 가 비어있으면 무조건 false.
// 3. 사용자의 dictLike 목록에 해당 dict 가 포함되어있지 않으면 false.
// 4. 포함되어있을시 true.
if (userDetails == null) {
return false;
}
for (DictLike dictLike : dict.getDictLikeList()) {
if (dictLike.getUser().getUsername().equals(userDetails.getUsername())) {
return true;
}
}
return false;
}
기능에 대한 설명을 하자면, 현재 좋아요 중이 아닐 시 좋아요 처리를 하고, 좋아요 중이면 취소 처리하는, SNS에서 자주 볼 수 있는 그 기능이다. 근데 이 기능을 시행하는 중, 좋아요 취소를 담당하는
dictLikeRepository.deleteById(dictLike.getDictLikeId());
코드가 실행이 되지 않는 상황이었다. 에러메세지 하나 없이 말이다. 그야말로 화가 나는 상황이라고 볼 수 있다.
여기서 생각 가지치기를 해봤는데, 일단 에러메세지가 뜨지 않았다는 것은 일단 문제 없이 쿼리를 실행은 했다는 거고, 그렇다면 그냥 이 행동이 DB에 반영이 되지 않은 것 뿐 아닐까? 라는 생각이 들었다. 그러면 왜 반영이 안 되지? 뭔가 문제가 있나? 혹시 관계?
이하부터는 내 추측에 따른 뇌피셜이니 이런 생각을 할 수도 있구나 하고 넘어가주길 바란다.
public class Dict extends Timestamped {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long dictId;
// ...
@OneToMany(mappedBy = "dict", cascade = CascadeType.ALL)
private final List<DictLike> dictLikeList = new ArrayList<>();
}
Dict 엔티티는 현재 OneToMany로 이 사전의 좋아요 리스트를 가지고 있다. 이 리스트는 호출되는 시점에 Lazy fetch를 시행하게 되고, fetch를 시행한 시점에서 이 리스트는 DB로부터 독립 된다. 실제 DB의 변경 상태를 리스트가 반영하지 못 하게 되는 것이다. 지금 이 상태에서 원래 DB의 값이 변경되면, 데이터의 무결성이 깨진다. Dict.dictLikeList는 신뢰할 수 없는 데이터가 되는 것이다. 내가 생각하기엔, JPA가 이러한 상황에서 데이터 무결성이 깨지지 않도록 어떤 장치를 해둔 것이 아닐까? 그렇기 때문에 DML 작업을 무시하는 것이 아닐까? 하는 생각이 들었다.
그래서 한 번 Dict.dictLikeList를 참조하지 않는 코드를 짜보기로 했다.
private boolean isDictLike(Dict dict, UserDetailsImpl userDetails) {
if (userDetails == null) {
return false;
}
/*
for (DictLike dictLike : dict.getDictLikeList()) {
if (dictLike.getUser().getUsername().equals(userDetails.getUsername())) {
return true;
}
}
*/
Optional<DictLike> dictLike = dictLikeRepository.findByUserAndDict(jwtAuthenticateProcessor.getUser(userDetails), dict);
return dictLike.isPresent();
}
결과는 잘 된다. 추측에 의해 풀어나간 문제였는데, 잘 풀렸다. 앞으로 이 점을 유의해서 문제를 풀어야겠다. 더 깊게 들어가보자면 영속성 컨텍스트... 그런 관계에 의한 복잡한 이해관계가 얽히게 되겠지만, 당장은 프로젝트을 시행하는 기간이기 때문에 이 부분에 대한 공부는 추후 추가로 진행하기로 한다.
22. 1. 2 추가)
생각해보니, Transactional 선언을 해주고 List의 값도 지워주도록 설정하는 코드를 구성한다면 문제가 없을 수도 있지 않을까? 라는 생각이 든다. 그냥 문득 해 본 생각이다.
'내가 배운 것들 > 문제 해결' 카테고리의 다른 글
[QueryDSL] 에러 발생 - query specified join fetching, but the owner of the fetched association was not present in the select list (0) | 2022.01.01 |
---|---|
[Ubuntu/SpringBoot] Ubuntu 8080 포트를 80 포트로 포트포워딩 하기 (0) | 2021.12.29 |
[SpringBoot/Redis/Ubuntu] 우분투 환경에 Redis 설치, SpringBoot에 세팅 (0) | 2021.12.27 |
[SpringBoot] DTO 형식으로 반환해줄 때, NULL값은 숨긴 채로 되돌려주기 (0) | 2021.12.23 |
[RegExp] 아이디, 닉네임, 비밀번호 정규식 (1) | 2021.12.23 |
댓글