2021-05-15-TIL
2021-05-15-TIL
예외처리 관련
SimpleDateFormat::parse에서 발생할 수 있는 예외인 ParseException에 대해서 처리하는데 고민되는 부분이 있었다.
- 계속 throws 하는것이 바람직한 해결방법인가?
- 중간에 stream이 있는데, stream으로 처리하는 코드상에서 예외 처리는 어떻게 하는가?
- 그냥 logger로 예외 정보만 출력하는 것이 문제가 되는가?
DateFormat.parse(String)은 문자열 앞부분을 해석할 수 없으면 ParseException을 던지고, 이 예외는 checked 예외다(DateFormat Javadoc). 질문을 하나씩 정리하면 다음과 같다.
계속 throws로 올리는 것은 호출자가 그 예외에서 회복할 수 있을 때만 의미가 있다. 날짜 형식이 틀린 입력은 대개 클라이언트 요청이 잘못된 경우라서, 컨트롤러 가까이에서 400 응답으로 바꾸는 것이 자연스럽다. 그러려면 파싱하는 곳에서 잡아 도메인 예외(unchecked)로 감싸고, 원인 예외를 cause로 넘겨 스택 트레이스를 보존한다.
스트림 안에서는 더 직접적인 제약이 있다. map이 받는 Function.apply는 checked 예외를 선언하지 않으므로 람다 안에서 ParseException을 그대로 던질 수 없다. 람다 안에서 잡아서 unchecked로 바꾸거나, 파싱을 별도 메서드로 빼서 그 안에서 변환한다.
logger로 출력만 하고 넘어가는 것은 문제가 된다. 예외를 삼키면 메서드는 null이나 잘못된 값을 들고 계속 진행하고, 진짜 문제는 나중에 엉뚱한 곳에서 NullPointerException으로 나타난다. 로그를 남기더라도 처리 흐름은 끊어야 한다.
애초에 java.time을 쓰면 이 고민의 상당 부분이 사라진다. LocalDate.parse가 던지는 DateTimeParseException은 RuntimeException의 하위 클래스라서 unchecked다(DateTimeParseException Javadoc). 또 SimpleDateFormat은 동기화되지 않아서 여러 스레드가 인스턴스 하나를 공유하면 안 되지만, LocalDate는 불변이고 스레드 안전하다.
Aggregate Root 관련
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Store {
@Id
private Long id;
private String name;
private String owner;
private String description;
private Integer level;
private String address;
private String phone;
@MappedCollection(idColumn = "STORE_ID", keyColumn = "ID")
private Map<Long, BusinessTime> businessTimes = new HashMap<>();
}
이 경우에 외부에서 RequestDto로 List<BusinessTime>을 받아왔다면 어떻게 AUTO_INCREMENTED인 LONG id를 만들어내는가?
Map의 키는 DB가 만들어 주는 값이 아니라 애플리케이션이 정하는 값이다. Spring Data JDBC 매핑 문서는 Map<단순 타입, 엔티티>를 키가 있는 일대다 관계로 보고, 자식 테이블에 부모 외래 키 컬럼과 맵 키를 담을 컬럼을 하나 더 기대한다. @MappedCollection의 keyColumn = "ID"는 그 맵 키를 ID 컬럼에 쓰라는 뜻이다. 그래서 BusinessTime의 ID가 AUTO_INCREMENT라면 맵 키 쓰기와 충돌한다.
선택지는 세 가지다.
List<BusinessTime>으로 바꾼다. 문서에 따르면List는Map<Integer, 엔티티>처럼 매핑되고, 키 컬럼에는 리스트 인덱스가 들어간다. 요청 DTO의 리스트를 그대로 옮기면 된다.- 순서가 의미 없으면
Set<BusinessTime>으로 바꾼다.Set에는 키 컬럼이 필요 없다. Map을 유지해야 한다면 요일처럼 업무상 의미 있는 값을 키로 삼고, 키 컬럼을 AUTO_INCREMENT id와 분리한다.
어느 쪽이든 Spring Data JDBC는 루트를 다시 저장할 때 자식을 모두 지우고 다시 넣으므로(엔티티 저장 문서), 자식 행의 id를 바깥에서 참조하는 설계는 피하는 편이 좋다.
댓글
아직 댓글이 없습니다