@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,"전사");
}
실행 후 정상적으로 통과가 되는것을 보면서 마무리
'개발' 카테고리의 다른 글
[Design Pattern] Strategy Pattern (0) | 2022.01.26 |
---|---|
[Mac] 환경변수 선언 (2) | 2021.08.23 |
Docker(2) - Container 다루기 (0) | 2021.08.04 |
Docker(1) - image 와 container 사용법 (0) | 2021.08.04 |
[Design Pattern] 객체지향의 5대 원칙 Solid원칙 (0) | 2021.07.24 |