새소식

개발

[JPA] @AttributeConverter

  • -

@AttributeConverter

@AttributeConverter를 쓰면 좋은 상황

@AttributeConverter 는 다음과 같은 경우에 유용하게 사용 될 수 있다.

  • JPA가 지원하지 않는 타입을 매핑할때
  • 두개 이상 속성을 갖는 밸류타입을 하나의 컬럼에 매핑할때

AttributeConverter는 자바 타입과 DB 타입간의 변환을 처리해준다.

AttributeConverter Interface & Method

AttributeConverter의 인터페이스는 아래와 같이 정의되어 있다.

package javax.persistence;

public interface AttributeConverter<X,Y> {

    public Y convertToDatabaseColumn (X attribute);
    public X convertToEntityAttribute (Y dbData);
  • convertToDatabaseColumn
    엔티티의 X타입 속성을 Y타입의 DB데이터로 변환한다.
  • convertToEntityAttribute
    Y타입의 DB데이터를 엔티티의 X타입으로 변환한다

사용예시

이를 처리하기위한 예시 엔티티 컬럼 하나와 컨버터를 구현해보자.

와우 관련 프로젝트를 만들고 있으니 와우의 직업을 기준으로 코드를 구현해본다.

public class CharacterEntity {
        ...
        @Column(length = 12, nullable = false)
        private String playableClass;
        ...
    }

먼저 Enum클래스를 생성해준다 작성해준다.

@AllArgsConstructor
@Getter
public enum PlayableClassType {

    WARRIOR("전사",1),
    PALADIN("성기사", 2),
    HUNTER("사냥꾼",3),
    ROGUE("도적",4),
    PRIEST("사제",5),
    DEATH_KNIGHT("죽음의 기사",6),
    SHAMAN("주술사",7),
    MAGE("마법사",8),
    WARLOCK("흑마법사", 9),
    MONK("수도사", 10),
    DRUID("드루이드",11),
    DEMON_HUNTER("악마사냥꾼", 12),
    EVOKER("기원사", 13);


    private final String playableClassName;

    private final int playableClassId;

    public static PlayableClassType getTypeByName(String playableClassName) {
        return Arrays.stream(PlayableClassType.values())
                .filter(targetType -> targetType.getPlayableClassName().equals(playableClassName))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("There is no described playble class."));
    }

    public static PlayableClassType getTypeById(int playableClassId) {
        return Arrays.stream(PlayableClassType.values())
                .filter(targetValue -> targetValue.getPlayableClassId() == playableClassId)
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("There is no described playble class."));
    }

}

그리고 오늘의 목표인 Converter를 구현해준다.

public class PlaybleClassConverter implements AttributeConverter<String, Integer> {

    /**
     * 전달된 playableClassName에 해당하는 PlayableClassType을 검색하고 이와 맵핑되는 playableClassId를 데이터베이스 컬럼으로 반환합니다.
     * 해당하는 PlayableClassType이 없을 경우 IllegalArgumentException이 발생합니다.
     *
     * @param playableClassName playable class type의 name
     * @return 데이터베이스에 맵핑되는 playableClassId
     * @throws IllegalArgumentException playableClassName이 PlayableClassType에 존재하지 않을 경우 발생
     */
    @Override
    public Integer convertToDatabaseColumn(String playableClassName) {
        return PlayableClassType.getTypeByName(playableClassName).getPlayableClassId();
    }

    ;

    /**
     * 전달된 playableClassId에 해당하는 플레이어블 클래스 이름을 반환합니다.
     * 해당하는 PlayableClassType이 없을 경우 IllegalArgumentException이 발생합니다.
     *
     * @param playableClassId 플레이어블 클래스 타입의 ID
     * @return 플레이어블 클래스 타입의 이름
     * @throws IllegalArgumentException playableClassId가 PlayableClassType에 존재하지 않을 경우 발생
     */
    @Override
    public String convertToEntityAttribute(Integer playableClassId) {
        return PlayableClassType.getTypeById(playableClassId).getPlayableClassName();
    }
}

이제 해당 컬럼에 Converter를 적용해주자

public class CharacterEntity {
...
    @Convert(converter = PlaybleClassConverter.class)
    @Column(length = 12, nullable = false)
    private String playableClass;
...
    }

사용예시 테스트

이제 컨버팅이 잘되는지 테스트하기위한 테스트코드를 작성한다.

@Transactional
@Test
void 엔티티_AttributeConverter_test(){

        // 엔티티 등록
        CharacterEntity characterEntity = CharacterEntity.builder()
                .accountEntity(accountRepository.findAllByEmail("donghyeondev@gmail.com"))
                .characterSeNumber(123456)
                .name("autocat")
                .level(60)
                .playableClass("전사") // "00"
                .specialization("분노")
                .race("타우렌")
                .gender("남자")
                .faction("호드")
                .equippedItemLevel(230)
                .averageItemLvel(231)
                .slug("아즈샤라")
                .lastCrawledAt(LocalDateTime.now())
                .expansionOption("나이트페이")
                .expansionOptionLevel(50)
                .build();
        characterRespository.save(characterEntity);

        // 컨버팅 된 코드값으로 로우 조회
        characterRespository.findAllByName(0);

        // 검증, 값으로 조회할땐 코드가 아닌 "전사"로 나와야함
        String playbleClass = characterEntity.getPlayableClass();
        assertEquals(playbleClass,"전사");
    }

실행 후 정상적으로 통과가 되는것을 보면서 마무리

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.