(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을 이용해 코드 변환 (매번 조인보다 낫다)
- 로컬 캐시 (
@Cacheable
) 사용하는 방법 - redis에 코드 테이블 올려두고 사용하는 방법
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 둘 다 만들어 선택지를 주는 것이 적절해보인다.
[!info] 무조건 enum이 낫다, DB code table이 낫다 할 수 없는 문제다. 상황에 따라 결정.
e.g. 비즈니스에서 코드에 따른 분기가 필요하다면 보통 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를 써도 된다.)
예제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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();
}
}
1
2
3
4
5
6
7
8
9
/**
* 위 코드와는 관련 없지만 함수형으로 쓰면 이런 식이 된다.
*/
public static PaycoResponseCode from(int _code) {
return Stream.of(values())
.filter(responseCode -> responseCode.getCode() == _code)
.findAny()
.orElseThrow(NoSuchElementException::new);
}
- 위 방식은 매번 호출할 때 마다 for나 stream을 돌게 되는데, Map으로 key-value를 미리 저장해 두고 거기서 꺼내는 식으로 해도 좋다. (선호하는 방식)
- effectivejava/chapter6/item34/Operation.java#L31-L33
- enum.values()가 매번 호출되는 것을 막기 위해 enum factory를 사용하거나, static 변수에 이를 담아두는 경우가 있는데, 굳이 그럴 필요 까지는 없다.
- is-there-a-performance-hit-when-using-enum-values-vs-string-arrays
1
2
3
4
5
6
7
8
9
/* 기본 제공 */
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 레벨에서 검증이 되도록 하는게 좋다.
This post is licensed under CC BY 4.0 by the author.