DB Data와 형식이 다르면 어떻게 할까?🤔🤔
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; } ... ... }
Test를위해 data.sql에 들어갈 쿼리도 수정해준다.
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);
- 200이면 isDisplayed 가 Ture이기 때문에 화면에 출력되고있다는 뜻이고, 100이면 품절된 상태 즉 화면에 안나오는 상태 일 것이다. TEST GO GO
Book(super=BaseEntity(....), id=1, name=JPA 작은격차 클래스, ....status=100)
Book(super=BaseEntity(....), id=2, name=Spring Security 작은격차 클래스, ....status=100)
- status=100 라고 나오는데 이건 일종의 코드 형식이다. 이 코드 형식을 바꿔보자.
- dto 생성하고 Book 클래스도 변경해주자.
@Data
public class BookStatus {
private int code;
private String description;
public boolean isDisplayed(){
return code == 200;
}
}
private BookStatus status; // 판매상태
- 하지만 BookStatus Type은 Entity에 사용하지 못한다고 인텔리J가 알려준다. 이제 coverter패키지를 하나 만들고 BookStatusConverter를 만들자. 아 만들기 전에 BookStatus생성자를 만들어주고 description에 대한 초기화값도 설정해 주자.
@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;
}
- 마지막으로 Book Entity에서 어떤 컨버터를 쓸지 표시해줘야한다.
@Convert(converter = BookStatusConverter.class) //어떤 컨버터 class 쓸지 지정
private BookStatus status; // 판매상태
- 그리고 TEST할때 DB에는 어떻게 들어가있는지 보자.
// 내가 저장한 마지막 책 정보 하나만 가져와 보자.
@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]
- JPA는 자동으로 영속성을 관리해주기 때문에 편리하지만, 개발자가 생각지도 못하는 예외적인 동작을 한다. 컨버터는 convertToDatabaseColumn, convertToEntityAttribute 두개다 무조건 다 구현해야한다. JPA에서 Transaction이 시작할때 끝날때 비교하게되는데 오작동을 일으켜서 DB에 빈값 ,즉 Null로 표시할수도 있기 때문이다.
'Dev > JPA' 카테고리의 다른 글
JPA Embedded, Embeddable, 속성의 재정의 (0) | 2022.03.02 |
---|---|
JPA Native Query (0) | 2022.03.02 |
JPA @Query (0) | 2022.03.02 |
JPA OrphanRemoval(+@Where) (0) | 2022.03.02 |
JPA에서 Transaction 활용하기 (0) | 2022.03.02 |