#294 (리뷰/메모): - MemoService.upsert: 동시성 INSERT 시 DuplicateKeyException 폴백 → UPDATE - ReviewService.toggleFavorite: 동시성 INSERT 시 DuplicateKeyException ignored (토글 ON) - ReviewController: rating(0~5) Bean validation 헬퍼, body.rating null/비숫자 → 400 - ReviewMapper.xml getAvgRating: NVL로 0건 시에도 0.0 보장 #295 (채널): - ChannelController.create: typed DataIntegrityViolationException으로 유니크 충돌 감지 (제약명 문자열 매칭 폐기) - ChannelController.create: channel_id/channel_name null/빈값 → 400 - ChannelService.deactivate: "UC..." 형식 검증으로 명시적 분기 (이전 폴백 방식의 의도 모호함 해결) - ChannelMapper.xml findByChannelId: description/tags/sort_order까지 SELECT #290 (식당 CRUD): - RestaurantController: @PreDestroy로 virtual thread executor shutdown - RestaurantController: 캐시 역직렬화 실패를 silent ignore → log.warn + cache.del 자동 evict - RestaurantController: setTablingUrl/setCatchtableUrl URL 스킴 화이트리스트 검증 - CacheService: 단일 키 del() 메서드 추가 후속 분리: - #333 (#290 DTO 화이트리스트 + DDG 대체) - #334 (#295 cache.flush 세분화 + scan 비동기) - #335 (#294 테스트) Refs: #290 #294 #295
53 lines
1.9 KiB
Java
53 lines
1.9 KiB
Java
package com.tasteby.service;
|
|
|
|
import com.tasteby.domain.Memo;
|
|
import com.tasteby.mapper.MemoMapper;
|
|
import com.tasteby.util.IdGenerator;
|
|
import org.springframework.dao.DuplicateKeyException;
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
import java.time.LocalDate;
|
|
import java.util.List;
|
|
|
|
@Service
|
|
public class MemoService {
|
|
|
|
private final MemoMapper mapper;
|
|
|
|
public MemoService(MemoMapper mapper) {
|
|
this.mapper = mapper;
|
|
}
|
|
|
|
public Memo findByUserAndRestaurant(String userId, String restaurantId) {
|
|
return mapper.findByUserAndRestaurant(userId, restaurantId);
|
|
}
|
|
|
|
@Transactional
|
|
public Memo upsert(String userId, String restaurantId, Double rating, String memoText, LocalDate visitedAt) {
|
|
String visitedStr = visitedAt != null ? visitedAt.toString() : null;
|
|
// #294 — 동시성 가드: 사전 SELECT → 분기 INSERT/UPDATE 패턴은 두 트랜잭션이 동시에 미존재
|
|
// 판정 후 둘 다 INSERT → UNIQUE 충돌(500). INSERT 우선 시도 후 DuplicateKeyException 시 UPDATE.
|
|
Memo existing = mapper.findByUserAndRestaurant(userId, restaurantId);
|
|
if (existing != null) {
|
|
mapper.updateMemo(userId, restaurantId, rating, memoText, visitedStr);
|
|
} else {
|
|
try {
|
|
mapper.insertMemo(IdGenerator.newId(), userId, restaurantId, rating, memoText, visitedStr);
|
|
} catch (DuplicateKeyException e) {
|
|
// 동시 INSERT 충돌 → UPDATE로 폴백
|
|
mapper.updateMemo(userId, restaurantId, rating, memoText, visitedStr);
|
|
}
|
|
}
|
|
return mapper.findByUserAndRestaurant(userId, restaurantId);
|
|
}
|
|
|
|
public boolean delete(String userId, String restaurantId) {
|
|
return mapper.deleteMemo(userId, restaurantId) > 0;
|
|
}
|
|
|
|
public List<Memo> findByUser(String userId) {
|
|
return mapper.findByUser(userId);
|
|
}
|
|
}
|