JPA는 ORM이기 때문에 DB Data를 JAVA 객체로 바꿔 주는데 DB Data와 형식이 다르면 어떻게 할까?🤔🤔 바로 @Converter를 사용하는 것이다.
쿼리를 통해 가져온 데이터를 객체로 맵핑을 할때 커스터마이징하는 방법을 알아보자.
@Enumerated(value = EnumType.STRING)
private Gender gender;
위 코드는 DB의 Enum과 자바의 Enum Type 형식이 다르기 때문에 @Enumerated(value = EnumType.STRING)를 넣어 줬다. 실제로 OrdinalEnumValueConverter 클래스에 보면 Integer 형식의 값을 String으로 변환하고, NamedEnumValueConverter에는 String 값을 받아서 Enum의 Name으로 맵핑해서 변환해주는 기능이 있다. 이 기능은 Javax의 BasicValueConverter 에서 Converter기능을 제공해준다. 그렇다면 우리도 가능할 것이다.
처음부터 Enum이나 임베디드 타입클래스를 활용해서 DB를 설계하면 필요없지 않아?
라고 말 할수 있겠지만, 과거에 설계한 레거시Data이거나 혹은 다른 시스템의 정보를 연동하는 경우는 내가 원하지 않는 형태의 어떠한 Data가 존재할수있다. 특히나 Int값을 통해 어떠한 상태를 들어내는 Data 저장법은 과거에 보편화 되어있었다고 한다. 그렇기 때문에 Converter를 사용할줄 알아야한다.
자 이제 실습해보자.
public class Book extends BaseEntity {
...
...
private int status; // 판매상태
public boolean isDisplayed(){
return status == 200;
}
...
...
}
data.sql
insert into book(`id`,`name`,`publisher_id`, `deleted`, `status`) values (2,'Spring Security 작은격차 클래스', 1,false, 100);
insert into book(`id`,`name`,`publisher_id`, `deleted`, `status`) values (3,'SpringBoot 하나인 클래스', 1,true, 200);
insert into book(`id`,`name`,`publisher_id`, `deleted`, `status`) values (1,'JPA 작은격차 클래스', 1,false, 100);
Book(super=BaseEntity(....), id=1, name=JPA 작은격차 클래스, ....status=100)
Book(super=BaseEntity(....), id=2, name=Spring Security 작은격차 클래스, ....status=100)
@Data
public class BookStatus {
private int code;
private String description;
public boolean isDisplayed(){
return code == 200;
}
}
private BookStatus status; // 판매상태
@Data
public class BookStatus {
private int code;
private String description;
public BookStatus(int code) {
this.code = code;
this.description = parseDescription(code);
}
public boolean isDisplayed() {
return code == 200;
}
private String parseDescription(int code) {
switch (code) {
case 100:
return "판매종료";
case 200:
return "판매중";
case 300:
return "판매보류";
default:
return "미지원";
}
}
}
@Converter //JPA에서 사용하는 Converter이다 라는 표시
public class BookStatusConverter implements AttributeConverter<BookStatus,Integer> {
@Override
public Integer convertToDatabaseColumn(BookStatus attribute) {
// 여기는 BookStatus의 객체를 받아서 DataBase에 저장할때 어떻게 할꺼냐?
// Code를 가져와서 넣어줄꺼다.
return attribute.getCode();
}
@Override
public BookStatus convertToEntityAttribute(Integer dbData) {
// DB에서 Integer값을 받았을때는 어떻게 변환할꺼냐??
// BookStatus를 만들어 줄 꺼다.
// 그리고 nullPointException이 일어나면 절때 안된다. DB에 접근하는 Data이기 때문에 민감하다.
return dbData != null ? new BookStatus(dbData) : null;
}
@Convert(converter = BookStatusConverter.class) //어떤 컨버터 class 쓸지 지정
private BookStatus status; // 판매상태
// 내가 저장한 마지막 책 정보 하나만 가져와 보자.
@Query(value = "select * from book order by id desc limit 1", nativeQuery = true)
Map<String, Object> findRawRecord();
@Test
void converterTest(){
bookRepository.findAll().forEach(System.out::println);
// 새로운 책 생성하나 해보자.
Book book = new Book();
book.setName("또다른 IT전문서적");
book.setStatus(new BookStatus(200));
bookRepository.save(book);
// 실제 DB에 있는 값이 200으로 잘 변환되어있는지 보자.
System.out.println(bookRepository.findRawRecord().values());
}
[4, 2022-02-16 17:18:01.127, 2022-02-16 17:18:01.127, null, null, false, 또다른 IT전문서적, 200, null]