[프로젝트] Enum과 DB 매핑하기

공부하는 감자·2024년 1월 22일
0

F-Lab 프로젝트

목록 보기
4/11

들어가는 말

프로젝트에서 회원 상태나 성별을 Enum으로 관리하고 있다. Enum으로 관리한 값을 DB에 어떻게 매핑하여 저장할 것인가에 대해서 찾아보고 적용한 내용이다.

사전 준비

성별 Enum

먼저, 성별 쪽 Enum은 다음과 같다.

public enum MemberGender {
    MALE("M"),
    FEMALE("F");

    private final String gender;

    MemberGender(String gender) {
        this.gender = gender;
    }

    public String getGender() {
        return gender;
    }
}
  • 성별 코드는 “F” 와 “M”으로 결정했다.
  • 원래는 Char를 써서 한 글자만 넣게 강제하려고 했는데, DB와 매핑할 때 varchar(1)을 쓰기로 결정했으므로, String 으로 변경했다.

Enum valueOf()

먼저, Enum의 값, 즉 GenderEnum에선 gender 의 값으로 GenderEnum을 구하는 메서드가 필요하다.

수동으로 추가하려고 하다 보니, Java에서 지원해주는 메서드가 있어서 그걸 사용하기로 했다.

Java의 Enum.valueOf()

  • 해당하는 값을 가진 Enum을 반환
  • 만약 인수가 null이거나, 일치하는 Enum이 없으면 Exception 발생

출처: https://docs.oracle.com/javase/8/docs/api/java/lang/Enum.html

Member Entity

그리고 Entity는 간략화하여, 다음과 같다.

@Entity
@Table(name = "user")
public class MemberEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private MemberGender gender;
		// ...
}

Enum Mapping 방법

처음에는 String gender 로 선언 후, Enum의 값을 반환하는 메서드를 추가하여 필요할 때마다 호출해 넣어줄 예정이었다.

그런데, Enum gender 로 선언하고 Enum과 매핑시켜주는 방법이 있다고 해서 알아봤다.

Enum을 DB에 매핑하는 방법은 총 2가지가 있다.

  • @Enumerated
  • @Converter

@Enumerated

첫 번째 방법은 @Enumerated 어노테이션을 사용하는 것으로, 이는 JPA 2.1 이전에 사용되었다고 한다.

// 사용법
@Enumerated(EnumType)
Enum타입 변수명;

위의 사용법을 보면 알겠지만, @Enumerated 은 괄호를 열고 어떤 값이 저장될 지 EnumType 을 설정할 수 있다.

EnumType 에는 총 2가지 타입이 있다.

  • EnumType.ORDINAL (default)
  • EnumType.STRING

EnumType.ORDINAL

괄호 안의 내용을 적지 않았을 경우, 이 타입이 default 값으로 설정된다고 한다.

  • Enum에 선언된 변수의 순서를 저장한다. (0부터)
    • 예를 들어, MemberGender 의 경우엔 MALE이 맨 처음 선언되었으므로 0, FEMALE이 그 다음이므로 1이 매핑된다.
  • 단, 순서가 변경되면 DB에 저장된 값이랑 달라져서 데이터를 잘못 읽게되는 문제가 있다.
    • MALE과 FEMALE의 순서를 바꿔서, MALE이 1이 되어버릴 경우 DB의 데이터를 읽어올 때 1이 여성이 아닌 남성이 되어버린다.
@Entity
@Table(name = "user")
public class MemberEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Enumerated(EnumType.ORDINAL)
    private MemberGender gender;
		// ...
}

EnumType.STRING

  • Enum의 name이 저장된다.
    • 예를 들어, MemberGender 의 경우엔 “MALE”과 “FEMALE”이라는 이름이 그대로 저장된다.
  • 기존 이름을 변경한다면 데이터를 읽어올 때 문제가 생긴다.
    • MALE을 MAN으로 변경한다면, DB에 저장된 MALE은 읽어오면서 Enum에 해당하는 이름이 없으므로 오류가 발생할 수 있다.
  • 이름을 저장하게 되므로, 필요 이상의 많은 공간이 할당될 수 있다.
@Entity
@Table(name = "user")
public class MemberEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Enumerated(EnumType.STRING)
    private MemberGender gender;
		// ...
}

@Converter

@Enumerated 는 Enum의 순서나 이름이 변경되면 데이터를 잘못 읽거나 아예 읽지 못하는 문제가 발생한다.

따라서, 이러한 문제를 해결하기 위해 JPA 2.1 이후에 나온 것이 @Converter 이다.

AttributeConverter 인터페이스 구현

@Converter 는 DB의 값과 Entity의 값을 매핑시켜주는 Converter 클래스를 구현하는 방식을 말한다.

Converter 클래스에는 총 2가지 메서드가 필요하다.

  • DB의 값을 Java에서 사용할 타입으로 변환하는 메서드
  • Java에서 사용할 타입을 DB의 값으로 변환하는 메서드

Converter 클래스는 AttributeConverter 라는 인터페이스를 구현하여 만든다.

해당 인터페이스에는 2개의 메서드가 있다.

  • convertToDatabaseColumn : Entity의 값을 DB에 저장할 값으로 변환하는 메서드
  • convertToEntityAttribute : DB에 저장된 값을 Entity의 값으로 변환하는 메서드
  • 이때, AttributeConverter<MemberGender, String> 처럼 제네릭으로 타입을 선언한다.
    • 첫 번째 인자가 Java에서 사용할 값 (여기서는 Enum)
    • 두 번째 인자가 DB에 저장될(저장된) 값이다.
@Converter
public class GenderAttributeConverter implements AttributeConverter<MemberGender, String> {

    @Override
    public String convertToDatabaseColumn(MemberGender attribute) {
				// Enum의 gender 값 반환
        return attribute.getGender();
    }

    @Override
    public MemberGender convertToEntityAttribute(String dbData) {
				// valueOf() 메서드로, 해당 gender와 일치하는 MemberGender를 반환
        return MemberGender.valueOf(dbData);
    }
}

💡 @Converter는 필수인가?

예제를 찾아봤을 때, 해당 어노테이션을 적지 않은 코드도 있었다. 쓰거나 쓰지 않거나 똑같이 동작하는 것처럼 보여서 왜 사용하는 것인지 찾아봤다.

공식 문서에 따르면, 해당 클래스가 Converter 임을 명시해주기 위해 기입하는 것으로 보인다.

물론, autoApply 값을 설정해서 자동으로 매핑시켜주는 기능도 있다고 하는데, 이건 그리 사용할 거 같지 않다.

문서: https://docs.oracle.com/javaee/7/api/javax/persistence/Converter.html

Entity에 적용

이렇게 만든 Converter는 다음과 같이 적용시키면 된다.

@Entity
@Table(name = "user")
public class MemberEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Convert(converter = GenderAttributeConverter.class)
    private MemberGender gender;
		// ...
}

맺는 말

Enum 뿐만이 아니라 패스워드처럼 암호화되어 저장되어야 하는 경우도 Converter로 분리해서 사용할 수 있다.

정리하고 보니 별 내용이 없지만, 막연히 JPA가 Enum을 자동 매핑시켜주는 기능을 제공하더라 하고 기억하는 것보단 이렇게 조금이라도 찾아서 적용해보는 게 실력에 도움이 될 거 같다.

Reference

참고 사이트

[Spring] DB 코드값과 Enum의 매핑 방법 @Enumerated vs @Convert

JPA and Enums via @Enumerated

[Spring] Enum 타입을 DB에 저장하기

JPA | AttributeConverter 을 사용한 엔티티 구성

[JPA] JPA AttributeConverter를 사용하여 자동으로 변환된 값을 바인딩하기

profile
책을 읽거나 강의를 들으며 공부한 내용을 정리합니다. 가끔 개발하는데 있었던 이슈도 올립니다.

0개의 댓글

관련 채용 정보