이번 팀 프로젝트에서 DB설계, ERD 작성, GIT 초기 전략 등등.. 초기에 필요한 부분들을 담당해서 만들었습니다. 그러한 설계 과정에서 했던 고민과 어떻게 해결해나갔는지, 선택이유와 차이점 등에 관해서 글을 작성해보겠습니다.
우선 필수적으로 존재할 테이블은 아래와 같습니다
- User
- Task
- Log
- Comment
Task 테이블에는 priority("LOW", "MEDIUM", "HIGH"), status("TODO", "IN_PROGRESS", "DONE")이 존재합니다. 저 값들만이 존재하는 요구사항이 있습니다. 이런 경우에는 몇가지 방법이 있을 것 같습니다. 분리를 하는 것이 좋은 방법일까요?
정규화를 하기 위해 분리를 하다보면 Task, PRIORITY, STATUS와 같은 테이블을 만들고 PRIORITY 테이블 내에는 id, name이 존재하는 식으로 만들 수 있을 거 같습니다.
하지만 이렇게 설계를 하면 부가적인 내용 / 추가되지 않을 부분인데 오버 엔지니어링이 될 것 같고, 간단한 Task 조회를 하기 위해서는 몇번의 join, 만약 join하지 않는다면 LazyLoading에 의해 의도치 않은 시점에서 쿼리를 날리게 될 것입니다.
또한 기본적인 name값 외에 필요한 칼럼이나 부가적인 부분이 존재하지 않습니다. 이는 물론 개발자/설계자마다 관점이 다르다고 생각하지만 이런 경우에 저는 테이블을 분리하는 것이 의미가 없다고 생각합니다.
따라서 저는 테이블을 분리하여 정규화를 맞추기보다는 @Enumerated VS @Converter을 통해서 어떻게 오버엔지니어링 적인 부분을 줄이고 현 상황에 맞는 코드를 짤 수 있을지 고민했습니다.
그러기 위해서는 우선 @Enumerated와 @Converter의 차이를 명확히 알아보겠습니다.
@Enumerated
@Enumerated란 자바의 Enum 타입을 데이터 타입으로 지정해 DB에 저장할 수 있게 하는 어노테이션입니다.
이에는 두가지 속성이 존재합니다.
// String 형식을 그대로 DB에 저장
@Enumerated(EnumType.STRING)
// 순서(정수)를 DB에 저장
@Enumerated(EnumType.ORDINAL)
EnumType.STRING은 주석에 적어놓은 것처럼 ENUM의 문자열 형식이 그대로 저장되며 EnumType.ORDINAL은 순서가 정수타입으로 저장됩니다.
@Converter
@Converter는 영어 그대로 변환해주는 용도로 사용됩니다.
예를 들어 위 DB 구조에서 DB에는 BIGINT 형식으로 저장하고, Entity에서는 String으로 사용을 합니다.
그리고 저장 시 자동으로 변환하여 String -> Long 형식으로 저장을 할 수 있게 됩니다.
물론 @Converter는 이런 간단한 변환 뿐 아니라 따로 본인이 클래스를 만들고 메서드를 만드는 것이기 때문에 더 복잡한 로직을 타서 변환도 할 수 있을 겁니다.
사용법은 크게 어렵지 않습니다 AttributeConverter<T, Y>를 상속받은 클래스를 만들고
convertToDatabaseColumn(String value) 메서드 [Entity -> Database]
convertToEntityAttribute(Long value) 메서드를 구현해주기만 하면 사용할 수 있게 됩니다.
(메서드의 매개변수는 상황에 따라 다르게 만들면 됩니다.)
그런데 사실 지금 제가 사용하고 있는 부분은 Enum의 느낌이 강하기 때문에 Enum을 Long으로 변경하겠습니다.
public class StatusConverter implements AttributeConverter<ProcessStatus, Long> {
@Override
public Long convertToDatabaseColumn(ProcessStatus status) {
if (status == null) {
return null;
}
return status.getCode();
}
@Override
public ProcessStatus convertToEntityAttribute(Long code) {
if (code == null) {
return null;
}
return ProcessStatus.getProcessStatus(code);
}
}
컨버터 클래스입니다.
//@Enumerated(EnumType.STRING)
@Convert(converter = StatusConverter.class)
@Column(name = "status", nullable = false)
private ProcessStatus status;
기본에는 @Enumerated 속성을 이용하여 만들었기에 변경했습니다.
이런식으로 변환을 하면 DB 저장 시 자동으로 저장하여 문자열을 저장할 때보다 공간낭비를 줄일 수 있고, 만약 문자열의 이름이 변했을 때의 상황 또한 방지할 수 있습니다.
그런데 @Enumerated(EnumType.ORDINAL) 쓰면 안되나? 어짜피 값만 바꿔서 Enum의 있는 값을 저장할텐데? 라는 의문이 생기기 마련입니다.
하지만 이는 지양하는 방식입니다. 그 이유는 ORDINAL은 기본적으로 저장되어있는 순서만을 반환하여 저장하는 것이기 때문입니다. 그로 인해 추후 시스템 운영상에서 문제가 생길 수도 있고, 중간에 새로운 Enum 속성을 추가했을 때 완전히 꼬여버릴 수 있습니다.
저는 이번 프로젝트에서는 앞으로 변할 데이터가 아니며, 데이터의 저장공간을 고려할만한 프로젝트가 아닌 간단한 단기 프로젝트이기 때문에 @Enumerated를 사용했습니다.
하지만 상황에 따라 확장성과 유지보수적인 측면에서 사용한다면 @Converter 가 더 좋은 측면이 있을 것이라고 생각됩니다.