Converter(Entity ↔ db 값 변환)
// Book.java
public class Book{
@Convert(converter=BookStatusConverter.class)
private BookStatus status;
}
//BookStatus.java
@Data
public class BookStatus {
private long code;
private String description;
public boolean isDisplayed(){
return code == 200;
}
public BookStatus(long code){
this.code = code;
this.description = parseDescription(code);
}
public String parseDescription(long code){
switch((int) code){
case 100:
return "판매종료";
case 200:
return "판매중";
case 300:
return "판매보류";
default:
return "미지원";
}
}
}
// BookStatusConverter.java
@Converter // jpa에서 사용하는 converter (autoApply = true) 가 적용되면
// BookStatus가 선언된 필드는 다 적용해줌
public class BookStatusConverter implements AttributeConverter<BookStatus, Long> {
@Override
public Long convertToDatabaseColumn(BookStatus attribute) {
return attribute.getCode();
}
@Override
public BookStatus convertToEntityAttribute(Long dbData) {
return dbData != null? new BookStatus(dbData) : null;
}
}
- 위와 같이 @Converter와 @Convert를 통하여 데이터베이스에 저장된 특정 값을 객체로 매핑해서 활용할 수 있음
- 이 때, database column에 생성되는 field의 type은 AttributeConverter의 두번째 generic type을 따라서 생성됨
@Converter(autoApply=true)
- AttributeConverter의 첫번째 generic type에 선언된 클래스를 필드로 가지고 있는 Entity에 대해 자동으로 Converter를 적용해주는 옵션임
- BookStatus와 같이 사용자가 만든 객체면 autoApply 사용해도 괜찮지만, String, Integer와 같은 일반적인 클래스를 변환할 때는 autoApply 적용 안하는 것이 좋음
Converter 사용 시 주의할 점
- 만약에 legacy 데이터여서 읽기만 하고 쓰기 상황은 없다고 생각하고 Converter의 convertoDatabaseColumn() 메서드를 구현하지 않았을 때 생기는 문제
public class BookStatusConverter implements AttributeConverter<BookStatus, Long> {
@Override
public Long convertToDatabaseColumn(BookStatus attribute) {
// return attribute.getCode();
return null;
}
@Override
public BookStatus convertToEntityAttribute(Long dbData) {
return dbData != null? new BookStatus(dbData) : null;
}
}
@Transactional
public List<Book> getAll(){
List<Book> books = bookRepository.findAll();
books.forEach(System.out::println);
return books;
}
@Test
void converterErrorTest(){
bookService.getAll();
bookRepository.findAll().forEach(System.out::println);
}
- getAll() 메써드를 @Transactional을 적용함으로써, 끝날 때, dirty checking을 하게 됨
- Entity를 db 값을 변환했을 때 Transaction의 처음과 동일하지 않으면 update를 하게 되는데 converToDatabaseColumn() 이 null을 반환하기에 값이 달라졌음 → update 쿼리를 하게 됨
- 즉, 조회만 두번 했는데 dirty checking으로 인해 update 쿼리가 발생함으로 데이터가 null로 바뀌게 됨
- 여기서 알게 된것 ! ⇒ dirty checking 시, entity의 값을 db field값으로 변환한 뒤에 비교하고 update를 날림! convertToDatabaseColumn() 이 잘 구현되어 있으면 update 쿼리 안생김