쿼리를 통해서 가져온 결과를 엔티티 객체로 만들 때 일부 칼럼을 변환하는 방법을 알아본다. db데이터와 객체가 형식이 다른 경우 어떻게 매핑하는지 살펴본다. @Converter로 쿼리의 형식을 바꿔줄 수 있다.
다른 시스템을 연동하거나, 레거시 데이터는 원하지 않는 형태로 데이터를 저장해야 하거나 저장된 경우가 있다. 여기서는 int값으로 상태를 표현하고 있는 과거의 보편화된 개발 방식에 대해 커스터마이징하도록 한다.
int형 코드를 의미가 있는 객체로 변환하여 어플리케이션에서 사용하는 것은 ORM 규칙에 살짝 어긋나있다.
public class Book {
..
@Convert(converter = BookStatusConverter.class)
private BookStatus status; //판매상태
...
}
새로운 리턴 타입을 정한다. 책의 판매상태를 나타내기 위해 BookStatus의 리턴타입을 만든다. @Convert로 해당칼럼의 사용목적을 정의하고 converter는 우리가 내용을 정의 할 BookStatusConverter.class로 정한다.
@Data
public class BookStatus {
private int code;
private String description;
public BookStatus(int code) {
this.code = code;
this.description = parseDescription(code);
}
private String parseDescription(int code) {
switch(code) {
case 100:
return "판매종료";
case 200:
return "판매중";
case 300:
return "판매보류";
default:
return "미지원";
}
}
public boolean isDisplayed() {
return code == 200;
}
}
BookStatus 클래스는 dto의 종류이고 code와 description을 가진다. code는 실제 DB에 들어갈 책 판매상태 int값이고, 그 값이 무엇인지는 description이 설명한다. 생성자에서 description을 parseDescription 메서드로 정한다. 생성자의 매개변수인 code는 DB에 저장되어 있는 값으로 쿼리에서 조회해온 해당 값에 따라 description이 정해진다.
@Converter
public class BookStatusConverter implements AttributeConverter<BookStatus, Integer> {
@Override
public Integer convertToDatabaseColumn(BookStatus attribute) {
return attribute.getCode();
}
@Override
public BookStatus convertToEntityAttribute(Integer dbData) {
//return new BookStatus(dbData); null에 대한 대비를 해야한다.
return dbData != null ? new BookStatus(dbData) : null;
}
}
@Convert를 적용할 규칙을 @Converter를 가진 클래스에 정의한다. AttributeConverter를 구현하는 클래스이며,
<적용할 리턴타입, 칼럼에 적용할 리턴타입>을 정한다.
convertToDatabaseColumn은 엔티티값에서 DB 테이블로 매핑될 때, 실제로 저장되는 값으로 변환한다. 생성한 BookStatus 클래스의 code값이므로 attribute.getCode()를 리턴한다.
convertToEneityAttribute는 DB 테이블 쿼리가 엔티티로 매핑될 때, 정보를 개발자가 이해하기 쉽게 변환한다. Integer dbData가 쿼리에서 조회한 값을 만든 BookStatus로 바꾼다. AttributeConverter<BookStatus,Integer>로 구현했기 때문에 리턴형이 BookStatus이다. 실제로는 DB 저장소의 int값만 조회했지만, 마치 DB 저장소에서 객체를 조회한 효과를 가진다. 만약에, null이면 그대로 null을 리턴하고, null이 아니면 BookStatus 객체를 생성해서 가져온다. DB 저장소에서 조회해 온 값들이 null일 가능성이 있다면, 예기 못한 예외로 오류가 발생할 수 있으므로 주의한다.
@Test
public void convertTest() {
Book book = new Book();
book.setStatus(new BookStatus(100));
bookRepository.save(book);
bookRepository.findAll().forEach(System.out::println);
}
INSERT INTO BOOK(STATUS) VALUES('100');
JPA로 Book 엔티티에 new BookStatus(100); 를 넣는것과 DB 저장소에 '100' 을 넣는 것은 같은 의미이다. convertToDatabaseColumn가 작동하기 때문이다. 반대로 findAll()로 조회하면 아래 빨간박스의 객체를 얻는다. 다시한번 해당 객체는 DB 저장소에 있는 것이 아니라 convertToEntityAttribute가 작동해서 단순한 int값이 마치 객체를 조회한 것처럼 변한다. 개발자는 이제 100의 의미를 쉽게 알게 되었다. 판매종료는 전혀 서비스 로직에 영향을 주지 않는다.
'학습 > DB' 카테고리의 다른 글
JPA에서 Dirty Check 방지하기 (0) | 2021.10.02 |
---|---|
영속성 컨텍스트로 발생하는 이슈 (0) | 2021.10.01 |
@Embedded, @Embedabble (0) | 2021.09.30 |
Native Query (0) | 2021.09.30 |
@Transactional (0) | 2021.09.26 |