[Java] Enum
- [Effective Java] 6장 열거 타입과 애너테이션
- [Java] Enum to Json / Enum to Object
- https://woowabros.github.io/tools/2017/07/10/java-enum-uses.html
enum의 장점은?
- https://medium.com/@nikitashahu/enum-vs-hashmaps-for-storing-constants-204ef4a1a8d7
- code navigator의 도움을 받을 수도 있고,
- 없는 enum value를 잘못 참조했을 경우 compile time에 잡아낼 수도 있다는 장점도 있음.
enum의 단점은?
- code를 찾아내기 위해 복합키를 사용해야 하는 경우
- vs DB) 코드테이블을 사용한다면 PK를 복합키로 지정하여 constraint를 이용한 무결성을 보장할 수 있다. (개발자가 DB에 새로운 코드를 추가하는 순간 알게된다.)
- 그러나 enum에 무언가를 추가하는건, enum 이름 기반 unique만 검사하기 때문에... enum 필드에 복합키를 넣어야 한다. 그래서 이에 대한 체크를 넣더라도, 어쨌든 runtime에 가서야 enum에 값이 잘못 추가되었다는 것을 알게된다.
- vs Map도 마찬가지다. Map은 key 자리에 복합키를 넣어줄 수 있고 이 key가 중복되면 덮어쓰거나 한다. 그러나 enum은 중복된 필드값 그대로 가지고 있기 때문에, 신경써주지 않으면 1개 값이 튀어나올 줄 알았는데 2개가 튀어나온다거나. 하는 에러가 발생할 수 있다.
code 1 : 현금, 2 : 카드 라는 정보를 관리하는 방법
- DB에 code table을 만들어서 관리
- 어드민 메뉴로 새로운 코드를 추가하는 것이 용이함. (삭제/수정은 constraint를 잘 써야 무결성 유지가 가능함)
- 단점은 코드에서 code, name을 String으로 받아와 관리해야 한다. 로직은 또 따로 두어야 한다.
- 매번 DB에서 읽어오는게 부담일 수 있다. 고작 이거 구분값 읽어오자고 DB를? 이라는 생각이 들 수도.
- DB에 있는 코드 표를 enum으로 아예 옮겨버리는 방법 (배민 enum 활용기)
- 코드 상에서 code와 name을 enum 타입으로 묶어서 들고다닐 수 있다. + 로직까지.
- 매번 DB에서 읽어오는건 부담스러우나, enum으로 변환하는건 아주 마음이 가볍다.
- 기타
- DBMS function을 이용해 코드 변환 (매번 조인보다 낫다)
- redis에 코드 테이블 올려두고 사용하는 방법
- 객체 내부에 Holder를 두어 같은 내용으로 두 번 resolve 하지 않도록 캐시처럼 사용하는 방법
- 하지만 이건 DB가 업데이트 되면 캐시도 업데이트 해주어야 한다는 문제점이 있어 좋지 않다.
enum을 쓰든, DB code table 을 참조하든 둘 중 하나만 하는 것이 좋은가?
- 관리 포인트가 늘어나기 때문에 둘 중 하나만 쓰는게 좋아 보일 수 있다. 수정 사항이 발생했을 때 DB, enum 둘 다 반영해야 하므로.
- 하지만 운영하다 보면 아래와 같은 상황이 발생하게 되는데...
- INSERT INTO SELECT 구문을 사용하고 싶을 때. (bulk insert)
- DB code table이 없다면 code 변환을 위해 어떻게든 app단으로 한 번 불러와서 enum을 사용해야 한다. 곤란함..
- mybatis에서 java static method 사용 할 수는 있지만 이는 mybatis 의존적인 쿼리이므로 피하는 것이 좋아보인다.
- 데이터 양에 따라 BULK INSERT는 애초에 피하는게 나은 경우도 있다.
- DB에서 해당 code table을 여기저기서 조인하여 사용해야 하는 경우
- DB code table이 없다면 바로 query에서 처리 못하고 매번 app단으로 불러와야 함. 매우 불편
- 해당 code table을 빈번하게 조회해야 하는 경우 (enum이 없다면 매번 code 변환을 위해 query 수행해야 함)
- INSERT INTO SELECT 구문을 사용하고 싶을 때. (bulk insert)
- 운영 하다 보면 DB table을 사용하는게 더 편할 때도 있고, enum을 사용하는게 더 편할 때도 있다.
- 억지로 한 쪽에서만 관리하기 보다는 상황에 따라 DB와 enum 둘 다 만들어 선택지를 주는 것이 적절해보인다.
enum VS properties
- enum은 소스코드 내 상수 코드와 관련 로직을 모으기 위해 사용하고, properties는 환경(알파 베타 프로파일)에 따라 달라지는 url, config 값 같은 메타 정보를 지정하는데 사용하기 때문에 전혀 목적이 다르다.
- enum은 소스코드이기 때문에, 변경하면 다시 컴파일해야 하는 반면 `` .yml, .properties``같이 외부로 빼면 컴파일 없이 jar 패키징만 해도 적용 가능하다...는 점을 properties 쓰는 이유로 대는 사람도 있는데, 사실 둘은 목적이 다르기 때문에 타당해보이지 않는다.
- 목적이 다르기 때문에 단순 비교는 의미 없을지도 모르지만, 굳이 비교한다면 yml은 아래와 같은 단점이 있다.
- 자주 변경된다면 DB에 두는 것이 낫고, syntax 힌트나 하이라이트, 링크 등을 생각하면 enum이 yml 보다 낫다.
- yml로 관리한다해도, 이를 읽어와서 필드로 가지고 있는 객체를 만들어야 되니까 클래스는 어차피 만들어야 한다.
- 즉, yml로 관리하는게 적절한 케이스는 하는 케이스는 간단한 설정값 등등이고, 복잡한 계층 구조나 연결 구조가 들어간다면 enum으로 빼는 것이 낫다.
enum은 extends는 불가하지만, implements는 가능하다.
- 공통 interface를 만들 때는, getter의 선언부가 인터페이스에 들어가줘야 해당 인터페이스 타입으로 접근했을 때 .getCode() 같은 메서드를 호출할 수 있다. (클래스 구현부에서는 @Getter를 써도 된다.)
예제
@Getter
@RequiredArgsConstructor
public enum PaymentCode {
CASH(1, "현금"),
CARD(2, "카드");
private final int code;
private final String koName;
public static PaymentCode fromKoName(String text){
for (PaymentCode code : values()){
if (code.getKoName().equals(text)) {
return code;
}
}
throw new IllegalArgumentException();
}
}
/**
* 위 코드와는 관련 없지만 함수형으로 쓰면 이런 식이 된다.
*/
public static PaycoResponseCode from(int _code) {
return Stream.of(values())
.filter(responseCode -> responseCode.getCode() == _code)
.findAny()
.orElseThrow(NoSuchElementException::new);
}
- 위 방식은 매번 호출할 때 마다 for나 stream을 돌게 되는데, Map으로 key-value를 미리 저장해 두고 거기서 꺼내는 식으로 해도 좋다. (선호하는 방식)
- enum.values()가 매번 호출되는 것을 막기 위해 enum factory를 사용하거나, static 변수에 이를 담아두는 경우가 있는데, 굳이 그럴 필요 까지는 없다.
/* 기본 제공 */
log.info("{}", Payment.PaymentCode.valueOf("CARD")); // CARD 아래와 같다. 이름 문자열 받아서 enum찾을 때 말고는 쓸일 없는 듯.
log.info("{}", Payment.PaymentCode.CARD); // CARD 위와 같다.
log.info("{}", Payment.PaymentCode.CARD.name()); // CARD
/* @Getter */
log.info("{}", Payment.PaymentCode.CARD.getKoName()); // 카드
log.info("{}", Payment.PaymentCode.CARD.getCode()); // 2
/* 직접 정의. */
log.info("{}", Payment.PaymentCode.fromKoName("현금")); // CASH
(중요) enum의 초기화 시점은, 애플리케이션 시작 시점이 아니라 enum의 최초 호출 시점이다.
- 기본적으로 JVM에서 compile-time 상수가 아닌 모든 static field는 해당 class 최초 접근 시에 비로소 초기화 된다. Application 실행 시점에 초기화 되는 것이 아니다. (JVM 관련)
- 그래서 of 연산을 위한 Map을 static 변수에 담아 둘 때, duplicate key 같은 문제는 enum이 최초로 호출되는 시점에서야 발견된다.
- enum static에서 Duplicate Key 같은 에러가 발생할 수 있다면, 앱이 실행되고 나중에 최초 접근할 때가 되어서야 문제가 있음을 알 수 있기 때문에, TC를 추가해서 미리 CI 레벨에서 검증이 되도록 하는게 좋다.
'Java Stack > Java' 카테고리의 다른 글
[Effective Java] 2장 객체 생성과 파괴 (0) | 2019.12.02 |
---|---|
[Java] LocalDateTime : 날짜 시간 처리 관련 (0) | 2019.07.10 |
[Java] Jackson ObjectMapper Serialization (0) | 2019.05.15 |
JVM 관련 (0) | 2017.05.05 |
[Java] Stream API 노트 (0) | 2017.03.09 |