[Spring] Converter 사용

WOOK JONG KIM·2022년 11월 23일
0

패캠_java&Spring

목록 보기
65/103
post-thumbnail

JPA ORM은 일종에 interface
-> 데이터베이스의 레코드를 자바에 객체화 시켜주는 역할

자바의 객체화와 DB 데이터의 형식이 다른 경우
-> @Converter를 통해 데이터를 가져오는 즉시 정보를 변경하여 마치 Entity에서는 원래 이런 값이 있던 것 처럼 핸들링 가능

JPA에서 Enum 데이터를 가져오는 경우 -> Converter를 사용
-> OrdinalEnumValueConverter 구현체를 통해 JPA는 Enum, DB엔 Integer로 표현

Enum, 임베디드 데이터를 못쓰는 경우 발생 → 레거시 시스템, 다른 시스템과 연동, Integer 코드로 존재하는 경우

ORM과 같이 Convert를 하기 위해 필요한 class가 AttributeConverter

Converter 사용 예시

  1. Convert할 대상인 BookStatus.java를 생성
@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 "미지원";
        }
    }
}
  1. DB-Entity에 Covnert역할을 하는 BookStatusConverter.java를 추가

AttributeConverter interface 구현이 필요

AttributeConverter<BookStatus, Integer>는 Convert할 Entity와 DB타입을 표시

  • convertToDatabaseColumn는 Entity → 데이터베이스로 변환할 메소드
  • convertToEntityAttibute는 데이터베이스 → Entity로 변환할 메소드

@Converter 어노테이션 추가, Convert할 클래스임을 표시

@Converter
public class BookStatusConverter implements AttributeConverter<BookStatus, Integer> {
    @Override
    public Integer convertToDatabaseColumn(BookStatus attribute) {
        return attribute.getCode();
    }

    @Override
    public BookStatus convertToEntityAttribute(Integer dbData) {
        return dbData != null ? new BookStatus(dbData) : null;
    }
}
  1. Book Entity에 Convert할 속성인 BookStatus를 추가

@Convert(converter = BookStatusConverter.class)는 어떤 class를 통해 Convert 할지 지정

public class Book extends BaseEntity{
		...
		@Convert(converter = BookStatusConverter.class)
		private BookStatus status; // 판매상태 표시
}
  1. 조회 결과

data.sql 에 column 추가

insert into book(`id`, `name`, `publisher_id`, `deleted`, `status`) values(1, 'JPA 초격자 패키지', 1, false, 100);
insert into book(`id`, `name`, `publisher_id`, `deleted`, `status`) values(2, 'Spring', 1, false, 200);
insert into book(`id`, `name`, `publisher_id`, `deleted`, `status`) values(3, 'Spring Security', 1, true, 100);

DB변환 된 status (Integer) 확인을 위해 native쿼리사용, JPA에선 BookStatus객체 반환

@Query(value = "select * from book order by id desc limit 1", nativeQuery = true)
Map<String, Object> findRowRecord();
@Test
void convertTest(){
    bookRepository.findAll().forEach(System.out::println);

    Book book = new Book();
    book.setName("또 다른 전문서적");
    book.setStatus(new BookStatus(200));

    bookRepository.save(book);

    System.out.println(bookRepository.findRowRecord().values());
}

//결과
//bookRepository.findAll().forEach(System.out::println);
Book(super=BaseEntity(createdAt=2021-08-14T12:08:05.490965, updatedAt=2021-08-14T12:08:05.490965), id=1, name=JPA 초격자 패키지, category=null, deleted=false, status=BookStatus(code=100, description=판매종료))
Book(super=BaseEntity(createdAt=2021-08-14T12:08:05.497410, updatedAt=2021-08-14T12:08:05.497410), id=2, name=Spring, category=null, deleted=false, status=BookStatus(code=200, description=판매중))

//System.out.println(bookRepository.findRowRecord().values());
[4, 2021-08-14 12:08:06.090323, 2021-08-14 12:08:06.090323, null, false, 또 다른 전문서적, 200, null]

Converter 사용 시 나타날 수 있는 문제점

레거시 시스템 에서 경우 convertToEntityAttribute만 구현하여 조회만 하는 로직을 만든 경우

조회만하는 경우에도 convertToDatabaseColumn를 모두 구현해야 문제가 발생하지 않음
영속성 컨텍스트가 구현되지 않은 메소드를 통해 변경감지로 인식 (update실행)

조회만 하는 용도로써 convertToDatabaseColumn를 구현하지 않음

@Override
public Integer convertToDatabaseColumn(BookStatus attribute) {
    return null;
}

조회하는 용도의 쿼리를 생성(@Transactional 사용 주목)

	@Transactional
    public List<Book> getAll(){
        List<Book> books = bookRepository.findAll();

        books.forEach(System.out::println);

        return books;
    }

BookServiceTest.java
-> bookService, bookRepository를 이용해 2번의 데이터 조회

@Test
void converterErrorTest() {
    bookService.getAll();

    bookRepository.findAll().forEach(System.out::println);
}

영속성 컨텍스트가 해당 엔티티 값 중 변경된 내용이 있는지 없는지 체크를 하고 만약 변경된 내용이 있다면 그 데이터를 DB에 다시 영속화 하게 됨
-> update 구문 발생
-> converter를 덜 구현이 되어 조회했을 때와 다시 DB에 확인하는 값이 서로 다르게 되어 영속성 컨텍스트 입장에서는 값이 변경된것 처럼 감지하였음

Book(super=BaseEntity(createdAt=2022-11-23T17:01:04, updatedAt=2022-11-23T17:01:04), id=1, name=JPA 초격차 패키지, category=null, authorId=null, deleted=false, status=BookStatus(code=100, description=판매 종료))
Book(super=BaseEntity(createdAt=2022-11-23T17:01:04, updatedAt=2022-11-23T17:01:04), id=2, name=스프링 시큐리티 패키지, category=null, authorId=null, deleted=false, status=BookStatus(code=200, description=판매 중))

//bookRepository.findAll().forEach(System.out::println);
Hibernate: 
    update
        book 
    set
        updated_at=?,
        category=?,
        deleted=?,
        name=?,
        publisher_id=?,
        status=? 
    where
        id=?
Hibernate: 
    update
        book 
    set
        updated_at=?,
        category=?,
        deleted=?,
        name=?,
        publisher_id=?,
        status=? 
    where
        id=?

// status가 null로 프린트 됨
Book(super=BaseEntity(createdAt=2022-11-23T17:01:04, updatedAt=2022-11-23T17:01:05.063945), id=1, name=JPA 초격차 패키지, category=null, authorId=null, deleted=false, status=null)
Book(super=BaseEntity(createdAt=2022-11-23T17:01:04, updatedAt=2022-11-23T17:01:05.067392), id=2, name=스프링 시큐리티 패키지, category=null, authorId=null, deleted=false, status=null)

Convert에 autoApply속성

  • @Converter(autoApply = true) 를 지정하면 객체 타입을 통해 자동 매핑
    IntegerConvert, StringConvert 등 많은 곳에서 사용하는 경우 문제 발생할 확률이 높음

  • 용도가 명확한 클레스(BookStatus)를 사용하는 경우에 사용하는 것을 권장

  • 개발자가 만든 타입에만 적용하자, 예를 들어 String에 autoapply를 하면 다양한 자료형들이 만든 컨버터를 타게 될 것!

@Converter(autoApply = true)
public class BookStatusConverter implements AttributeConverter<BookStatus, Integer> {
		...
}
//    @Convert(converter = BookStatusConverter.class)
private BookStatus status; // 판매상태
profile
Journey for Backend Developer

0개의 댓글