[JPA] DB Set Type Column을 EnumSet 타입으로 Entity 선언하기

GilLog·2021년 12월 23일
1

JPA

목록 보기
8/8

#import

Mapping EnumSet to mysql Set using JPA 2.1[StackOverFlow]


기존 MySQL 특정 Table Column의 Type을,

아래와 같이 Enum Type으로 사용하고 있었다.

`CATEGORY` enum('CHINA','KOREA','JAPAN','ALONE','FRANCHISE', 'ETC') DEFAULT 'ETC'

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;

@ToString
@AllArgsConstructor
@Getter
public enum Category {
    CHINA("중식")
    , KOREA("한식")
    , JAPAN("일식")
    , ALONE("1인분")
    , FRANCHISE("프랜차이즈");
    
    private String name;
}

JPA Entity를 선언할 때도 아래와 같이 단순 @Enumerated Annotation을 사용하는 형태

Enum Type을 사용하고 있었다.

@Entity
public class RestaurantEntity {
	...
    
    @Enumerated(EnumType.STRING)
    @Column(name = "CATEGORY")
    private Category category;
    
}

프로젝트 도중에 해당 Column의 요구사항이 변경되어 기존 Enum to Set Type으로,

Column Type을 변경하게 되었다.
카테고리는 여러 값을 중복하지 않는 형태로 저장하기 위해 아래와 같은 형태

`CATEGORY` set('CHINA','KOREA','JAPAN','ALONE','FRANCHISE', 'ETC') DEFAULT 'ETC'

오늘은 이러한 상황에서 JPA Entity에서 단순 @Enumerated로 선언한 요소를,

EnumSet Collection으로 활용하기 위한 과정을 길록한다.


먼저 EnumSet 사용이 처음이라면 아래 간략 설명을 살펴보고 넘어가자.
안봐도 무방

EnumSet

EnumSetJava에서 Enum TypeSet Collection으로 사용하기 위한 특화 Collection이다.

EnumSet은 Set Interface를 구현하고, AbstractSet을 상속한다.

이러한 EnumSetBit Vector로 구현되어 있어 매우 효율적이다.

A specialized Set implementation for use with enum types.
All of the elements in an enum set must come from a single enum type
that is specified, explicitly or implicitly,
when the set is created.
Enum sets are represented internally as bit vectors.
This representation is extremely compact and efficient.
By Java 13 API - EnumSet

이러한 EnumSetThread-Safe 하지 않아 공유 자원으로 사용시에는

생성 외부에서 Synchronized하게 선언해주어야 한다.

EnumSet<GillogEnum> enumSet = Collections.synchronizedSet(EnumSet.noneOf(GillogEnum.class));

@Converter

EnumSet Type으로 JPA Entity에 선언하기 위해서 Converter를 사용해야 한다.

@ConverterEntity의 특정 Column에 대한 Data를 변환하여 사용해야 할 때 사용하는 Annotation이다.


해당 @Converter Annotation이 붙은 Classjavax.persistence에서 제공하는

AttributeConverter Interface를 구현해야 한다.

구현해야 할 Abstract Method 두 가지

convertToDatabaseColumn methodEntity에서 DB로 입력될 때,

Entity에서 선언한 Type X를,

DB에서 사용할 Type Y로 변환해주는 로직을 구현하면 되고,


convertToEntityAttribute methodDB에서 Entity로 변환 될 때,

DB에서 읽어올 Type Y를,

Entity안에 선언한 Type X로 변환해주는 로직을 구현하면 된다.

AttributeConverter 구현

먼저 @Converter를 사용할 AttributeConverter Interface를 구현하는 Class를 생성해보자.


import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.util.Arrays;
import java.util.EnumSet;

@Slf4j
// 해당 Class가 Converter Class임을 선언
@Converter
// Entity에서 사용할 X Type으로 EnumSet<Category>
// DB에서 읽어오거나 저장할 때 사용할 Y Type으로 String 선언
public class SetCategoryConverter implements AttributeConverter<EnumSet<Category>, String> {

    // DB에서 사용할 Type으로 Entity Type을 변환하는 로직 구현
    @Override
    public String convertToDatabaseColumn(EnumSet<Category> attribute) {
    	// DB에서 사용할 String Type 생성을 위해 StringBuilder 선언
        StringBuilder sb = new StringBuilder();
        // ["KOREA", "ALONE"] 과 같은 EnumSet Collection Data를
        // 각각 foreach를 돌며 "KOREA," 형식으로 StringBuilder에 append
        attribute.stream().forEach(e -> sb.append(e.name()+","));
        // 최종 결과 String으로 변환
        String result = sb.toString();
        // "KOREA,ALONE," 형식 일 경우 마지막 ',' 제거
        if(result.charAt(result.length() - 1) == ',') result = result.substring(0, result.length() - 1);
        return result;
    }

    // Entity에서 사용할 Type으로 DB Type을 변환하는 로직 구현
    @Override
    public EnumSet<Category> convertToEntityAttribute(String dbData) {
        // DB에서 읽어온 값이 null이거나 공백이거나 CATEGORY.KOREA(name="한식") 형태로 읽어올 경우 제외
        if(dbData == null || dbData == "" || dbData.contains(".")) return EnumSet.noneOf(Category.class);
        // 최초 빈 Collection 생성
        EnumSet<Category> attribute = EnumSet.noneOf(Category.class);
        // DB에서 읽어온 "KOREA,ALONE" 형태의 데이터 ','로 split
        String[] dbDataArray = StringUtils.trimAllWhitespace(dbData).toUpperCase().split(",");
        // 빈 Collection으로 생성한 EnumSet에 split한 data를 Category(Enum) .valueOf로 생성
        // 해당 구문에서 Enum에 선언되지 않은 값 존재 시 Exception 발생 가능
        Arrays.stream(dbDataArray).forEach(e -> attribute.add(Category.valueOf(e)));
        return attribute;
    }
}

@Converter Class를 자신의 Enum Type에 맞게 생성했다면,

최종적으로 Entity 선언 시에 아래와 같이 @Convert Annotation으로

해당 Class를 지정해주면 DB의 Set Column을 Java에서 EnumSet Type으로 변환하여 사용 가능하다.

@Entity
public class RestaurantEntity {

    ...
    
    @Convert(converter = SetCategoryConverter.class)
    @Column(name = "CATEGORY")
    private EnumSet<Category> category;
    
}
profile
🚀 기록보단 길록을 20.10 ~ 22.02 ⭐ Move To : https://gil-log.github.io/

0개의 댓글