ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring boot 2.0 과 Jpa2.2 그리고 Hibernate 5.2
    카테고리 없음 2023. 4. 23. 14:05
    오늘은 Spring boot2 기반으로 Jpa2.2 대해서 알아볼 예정이다. 실제 Spring boot 2.0은 기본적으로 하이버네이트 5.2를 지원한다. 그렇다고 해서 Spring boot 2.0 에 대해서 알아볼건 아니고 Spring boot 가 여러가지로 편해서 Spring boot 기반으로 작성할 예정이다. 만약 단독으로 Hibernate 를 사용한다면 여러 설정을 해야하므로 설정 관련은 따로 보는 것으 좋을 듯하다. 주로 JPA 2.2에 대해서 알아보도록 할 것이다. 시작해보자! 기본적으로 Spring boot 2.0Hibernate 5.2.16을 지원하고 있으며 (현재 이글을 쓰고 있을 때 최신버전은 Spring boot 2.0.1) Hibernate 5.2은 JPA2.1을 디펜더시 받고 있으며 2.1을 지원하고 있다. 그렇지만 Hibernate 5.2는 JPA2.2 도 일부 지원하고 있다. 그럼 지원하는 몇가지를 알아보도록 하자. 일단 spring boot 기준으로 설명하니 디펜더시도 Spring boot 기준일 것이다.

    Maven Install

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.hibernate.javax.persistence</groupId>
                <artifactId>hibernate-jpa-2.1-api</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>javax.persistence</groupId>
        <artifactId>javax.persistence-api</artifactId>
        <version>2.2</version>
    </dependency>
    
    Spring data jpa 에서 hibernate-jpa-2.1-api 를 제외하고 그대신 javax.persistence-api 2.2 를 작성하면 된다. 그럼 일단 준비는 완료다.

    JSR-310

    JPA2.2 에서는 JSR-310 스펙인 Date and Time API를 지원한다.
    @Converter(autoApply = true)
    public class LocalDateTimeConverter implements AttributeConverter<LocalDateTime, Date> {
    
        @Override
        public Date convertToDatabaseColumn(LocalDateTime date) {
            //blabla
        }
    
        @Override
        public LocalDateTime convertToEntityAttribute(Date date) {
            //blabla
        }
    }
    
    예전에는 LocalDateTime, LocalDate, LocalTime 등 JSR-310을 사용하려면 Converter를 만들어서 위와 같이 변환을 했어야 했다. 하지만 JPA2.2 부터는 그럴 필요 없다. 기본적으로 LocalDate, LocalTime, LocalDateTime, OffsetTime, OffsetDateTime 등을 지원하고 있으니 참고하면 되겠다.
    @Entity
    public class Person {
    
        @Id
        @GeneratedValue
        private Long id;
    
        private String name;
    
        private String email;
    
        private LocalDateTime date;
    
       //etc
    }
    
    그냥 위와 같이 작성해도 정상적으로 동작한다.

    @Repeatable

    여러 어노테이션에서 @Repeatable 어노테이션이 추가가 되었다.
    • AssociationOverride
    • AttributeOverride
    • NamedQuery
    • NamedStoredProcedureQuery
    • PersistenceUnit
    • PrimaryKeyJoinColumn
    • SequenceGenerator
    • SecondaryTable
    • SqlResultSetMapping
    • TableGenerator
    • JoinColumn
    • MapKeyJoinColumn
    • NamedEntityGraph
    • NamedNativeQuery
    • PersistenceContext
    필자가 찾아본 것으로는 대략 위와 같다. 얼추 찾아본거라 더 있을 수 있거나 잘못 작성한 내용도 있을 수 있으니 다시 확인하길 바란다. 기존에는 위의 어노테이션을 여러개 사용했을 경우 아래와 같이 사용했어야 했다.
    @Entity
    public class Person {
    
        //...
    
        @Embedded
        private Phone phone;
    
        @Embedded
        @AttributeOverrides({
                @AttributeOverride(name = "number1", column = @Column(name = "telNumber1")),
                @AttributeOverride(name = "number2", column = @Column(name = "telNumber2")),
                @AttributeOverride(name = "number3", column = @Column(name = "telNumber3"))
        })
        private Phone telNumber;
        //...
    }
    
    하지만 이제는 그럴 필요 없이 @AttributeOverride 어노테이션만 여러개 작성하면 된다. 좀 더 깔끔해졌다. 물론 위의 코드가 더 낫다고 생각할 수 도 있겠지만 필자는 아래가 더 깔끔한 것 같다.
    @Entity
    public class Person {
    
        //...
    
        @Embedded
        private Phone phone;
    
        @Embedded
        @AttributeOverride(name = "number1", column = @Column(name = "telNumber1"))
        @AttributeOverride(name = "number2", column = @Column(name = "telNumber2"))
        @AttributeOverride(name = "number3", column = @Column(name = "telNumber3"))
        private Phone telNumber;
    
        //...
    }
    
    @Repeatable어노테이션을 이해하려면 좀 더 내용을 찾아보기를 권장한다. 그렇게 어려운 내용은 아니니 말이다.

    resultStream

    EntityManager로 통해서 값을 가져올 경우에 Stream 으로 변환해서 가져올 수 있다. 실제 이 메서드는 TypedQuery 인터페이스의 default 메서드로 List 를 Stream 으로 변환하는 단순한 메서드 이다.
    default Stream<X> getResultStream() {
        return getResultList().stream();
    }
    
    딱히 어려운 부분은 없는 것 같다.
    @Test
    void entityManagerStream(@Autowired EntityManager entityManager) {
        entityManager.persist(new Person("wonwoo", "wonwoo@test.com", LocalDateTime.now(),
                new Phone("000", "111", "2222"), new Phone("333", "444", "5555")));
        List<PersonDto> persons = entityManager.createQuery("select p from Person p", Person.class)
                .getResultStream()
                .map(person -> new PersonDto(person.getName(), person.getEmail())).collect(Collectors.toList());
        assertThat(persons).hasSize(1);
        PersonDto personDto = persons.iterator().next();
        assertThat(personDto.getName()).isEqualTo("wonwoo");
        assertThat(personDto.getEmail()).isEqualTo("wonwoo@test.com");
    }
    
    위와 같이 map, filter등 변환할 것이나 stream 메서드를 사용하고 싶다면 getResultList() 메서드 말고 getResultStream() 메서드를 사용하면 된다.

    CDI Injection

    글쎄다. 일단 CDI Injection 스펙은 JPA2.2 에 존재하지만 아직 Hibernate에서는 지원하지 않는 것으로 보인다. 아니면 필자가 잘몰라서..
    @Converter(autoApply = true)
    public class TypeConverter implements AttributeConverter<Type, String> {
    
        @Inject
        private PersonService personService;
    
        @Override
        public String convertToDatabaseColumn(Type attribute) {
            return attribute.getType();
        }
    
        @Override
        public Type convertToEntityAttribute(String dbData) {
            return new Type(dbData);
        }
    }
    
    위와 같이 DI를 받을 수 있다는 내용 같다. 하지만 필자는 동작하지 않는다.ㅠㅠ 만약 되는 것이라면 피드백을..

    참고

    하다가 몇가지 발견한게 있는데 뭐 중요한 내용은 아니지만 그래도 혹시나 누군가 이런 상황이 온다면 잘 해결하길 바란다. (무책임) 필자는 그냥 테스트한번 해볼려다 당한거니.. Hibernate 5.2의 SessionFactoryEntityManagerFactory를 상속 받고 있다.
    public interface SessionFactory extends EntityManagerFactory, HibernateEntityManagerFactory, Referenceable, Serializable, java.io.Closeable {
       // ...
    }
    
    기존에는 다음과 같은 형태의 인터페이스 였다.
    public interface SessionFactory extends Referenceable, Serializable, java.io.Closeable {
       //...
    }
    
    Hibernate 의 Session 또한 EntityManager를 상속 받고 있다. 기존의 Session 인터페이스는 다음과 같다.
    public interface Session extends SharedSessionContract, java.io.Closeable {
       //..
    }
    
    하지만 현재 Hibernate 5.2에서는 다음과 같다.
    public interface Session extends SharedSessionContract, EntityManager, HibernateEntityManager, AutoCloseable {
       //...
    }
    
    그것도 모르고 필자가 Hibernate API 를 사용하고 싶어서 문서에 있는 그대로 설정하였으나 제대로 동작하지 않았다. 아마도 그이유는 Spring boot의 자동 설정 때문일 것 같다.
    @ConditionalOnMissingBean({ LocalContainerEntityManagerFactoryBean.class,
            EntityManagerFactory.class })
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            EntityManagerFactoryBuilder factoryBuilder) {
      // ...
    }
    
    딱히 필자가 중요한 내용은 아니라서 그냥 알고만 있는 중이다. 나중에 필요하다면 좀 더 자세히 살펴보려고 한다. 혹시나 그 이유와 해결책을 알고 있다면.. 댓글로! 위의 예제 코드들은 여기에 있으니 참고하면 되겠다. 이상으로 오늘은 Hibernate 5.2의 JPA2.2를 알아봤다. JPA 는 어렵다.

    댓글

Designed by Tistory.