My application and end-to-end tests works just fine, they find proper items by tag in a db. I use in controller layer annotation-based specification-arg-resolver to easily handle requests with many parameters and create for me specifications. The problem lies in a @DataJpaTest class where I manually create Specification. Tests that uses joins fail on this:
Caused by: java.lang.IllegalArgumentException: Unable to locate Attribute with the the given name [pt] on this ManagedType [com.tbar.makeupstoreapplication.model.Product]
at org.hibernate.metamodel.model.domain.internal.AbstractManagedType.checkNotNull(AbstractManagedType.java:147)
at org.hibernate.metamodel.model.domain.internal.AbstractManagedType.getAttribute(AbstractManagedType.java:118)
at org.hibernate.metamodel.model.domain.internal.AbstractManagedType.getAttribute(AbstractManagedType.java:43)
at org.hibernate.query.criteria.internal.path.AbstractFromImpl.locateAttributeInternal(AbstractFromImpl.java:111)
at org.hibernate.query.criteria.internal.path.AbstractPathImpl.locateAttribute(AbstractPathImpl.java:204)
at org.hibernate.query.criteria.internal.path.AbstractPathImpl.get(AbstractPathImpl.java:177)
at net.kaczmarzyk.spring.data.jpa.domain.PathSpecification.path(PathSpecification.java:50)
at net.kaczmarzyk.spring.data.jpa.domain.In.toPredicate(In.java:59)
at net.kaczmarzyk.spring.data.jpa.domain.EmptyResultOnTypeMismatch.toPredicate(EmptyResultOnTypeMismatch.java:55)
at net.kaczmarzyk.spring.data.jpa.domain.Conjunction.toPredicate(Conjunction.java:80)
at net.kaczmarzyk.spring.data.jpa.domain.Conjunction.toPredicate(Conjunction.java:80)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.applySpecificationToCriteria(SimpleJpaRepository.java:762)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.getQuery(SimpleJpaRepository.java:693)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.getQuery(SimpleJpaRepository.java:651)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll(SimpleJpaRepository.java:443)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:371)
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:204)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:657)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:621)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:605)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:366)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
The failing test look like this:
@Test
void shouldFindProductWithTag() {
Product expectedProduct = createProductWithTags(1L, "ProperTag");
productRepository.save(expectedProduct);
Specification<Product> specification = createSpecWithTag("ProperTag");
Pageable pageable = PageRequest.of(0, 12);
Page<Product> actualPageOfProducts = productRepository.findAll(specification, pageable);
assertEquals(expectedProduct, actualPageOfProducts.getContent().get(0));
}
private Conjunction<Product> createSpecWithTag(String... tagName) {
return new Conjunction<>(createJoin("productTags", "pt"),
new Conjunction<>(createIn("pt.name", tagName)));
}
private Join<Product> createJoin(String pathToJoinOn, String alias) {
return new Join<>(
new WebRequestQueryContext(nativeWebRequestMock), pathToJoinOn, alias, JoinType.INNER, true);
}
private EmptyResultOnTypeMismatch<Product> createIn(String path, String[] expectedValue) {
return new EmptyResultOnTypeMismatch<>(
new In<>(
new WebRequestQueryContext(nativeWebRequestMock), path, expectedValue,
Converter.withTypeMismatchBehaviour(OnTypeMismatch.EMPTY_RESULT)));
}
I copied the structure of the createSpecWithTags private method from the debugging process of real, working application where Specification's toString method looks like this:
Conjunction [innerSpecs=[Join [pathToJoinOn=productTags, alias=pt, joinType=INNER, queryContext=WebRequestQueryContext [contextMap={}], distinctQuery=true], Conjunction [innerSpecs=[EmptyResultOnTypeMismatch [wrappedSpec=net.kaczmarzyk.spring.data.jpa.domain.In@90beeb55]]]]]
Model
@Entity
@Table
@Data
@NoArgsConstructor
public class Product {
@Id
private Long id;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name = "PRODUCT_ID")
private Set<ProductTag> productTags;
}
@Entity
@Table
@Data
@NoArgsConstructor
@RequiredArgsConstructor
public class ProductTag {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@EqualsAndHashCode.Exclude
private int id;
@NonNull
private String name;
}
Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
Page<Product> findAll(Specification<Product> specification, Pageable pageable);
}
Controller method
@GetMapping
public String shopPage(Model model,
@Join(path = "productTags", alias = "pt")
@And({
@Spec(path = "pt.name", params = "product_tags", spec = In.class)
}) Specification<Product> productSpecification, Pageable pageable) throws ProductsNotFoundException {
Page<Product> pageOfProducts = makeupService.findProducts(productSpecification, pageable);
addAttributesToShopModel(model, pageOfProducts);
return ViewNames.SHOP;
}
Aucun commentaire:
Enregistrer un commentaire