오늘은 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.0
은
Hibernate 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의
SessionFactory
는
EntityManagerFactory
를 상속 받고 있다.
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 는 어렵다.