@Entity
@Table(name = "USERS")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User extends TimeStamped {
...
}
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class TimeStamped {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column
private LocalDateTime modifiedAt;
}
@SpringBootApplication
@EnableJpaAuditing
public class ShoppingmallprojectApplication {
public static void main(String[] args) {
SpringApplication.run(ShoppingmallprojectApplication.class, args);
}
}
많이들 사용하는 방법입니다.
그러나, 위와 같은 방식은 테스트 할 때 문제가 될 수 있습니다. 에러 메시지를 한번 볼까요?
... (매우 긴 에러메시지)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaMappingContext': JPA metamodel must not be empty
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1751)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:599)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:365)
... 99 more
Caused by: java.lang.IllegalArgumentException: JPA metamodel must not be empty
at org.springframework.util.Assert.notEmpty(Assert.java:479)
at org.springframework.data.jpa.mapping.JpaMetamodelMappingContext.<init>(JpaMetamodelMappingContext.java:58)
at org.springframework.data.jpa.repository.config.JpaMetamodelMappingContextFactoryBean.createInstance(JpaMetamodelMappingContextFactoryBean.java:68)
at org.springframework.data.jpa.repository.config.JpaMetamodelMappingContextFactoryBean.createInstance(JpaMetamodelMappingContextFactoryBean.java:44)
at org.springframework.beans.factory.config.AbstractFactoryBean.afterPropertiesSet(AbstractFactoryBean.java:142)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1797)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1747)
... 106 more
참 깁니다. 우리가 봐야 할 것은 어느부분인지? 라고 하면 바로
Caused by: java.lang.IllegalArgumentException: JPA metamodel must not be empty
이부분입니다. JPA의 메타모델은 empty 해선 안된다고 하네요?
자바가 제공하는 "어노테이션 프로세서" 기능을 사용하면 어노테이션을 분석해서 클래스를 생성 할 수 있습니다. 이를 이용해서 Entity의 Criteria 클래스를 생성해요.
예를 들어, User엔티티의 Criteria 클래스는 User_ 와 같이 생성되는데, 이것이 바로 메타 모델입니다. 이를 이용하면 온전히 코드를 이용해서 쿼리를 작성 가능합니다 ("String" 형태로 작성 시에 오타나, 잦은 변경이 있을 경우 매우 곤란하니까)
// Criteria 쿼리
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> query = cb.createQuery(Member.class);
// 루트 클래스(조회를 시작할 클래스)
Root<Member> m = query.from(Member.class);
// 쿼리 생성
CriteriaQuery<Member> cq = query.select(m).where(cb.equal("username"), "kim"));
List<Member> resultList = em.createQuery(cq).getResultList();
테스트를 돌릴 때는 기본적으로 Application 이 부트되야 합니다!!
그런데,
따라서 JPA 메타 모델이 온전히 만들어지지 않은 것입니다.
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@EnableJpaAuditing
public class TimeStamped {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column
private LocalDateTime modifiedAt;
}
끝!
하지만 이런 방법도 있긴 합니다.
@WebMvcTest(CartController.class)
@MockBean(JpaMetamodelMappingContext.class)
public class CartControllerTest {
메타모델 맵핑 컨텍스트를 의도적으로 주입해주는 방법이요. 하지만 모든 컨트롤러에 붙이긴 귀찮죠? 안붙이는걸로!