如何在 spring data jpa 中使用预测和规范?
Posted
技术标签:
【中文标题】如何在 spring data jpa 中使用预测和规范?【英文标题】:How to use projections and specifications with spring data jpa? 【发布时间】:2017-06-01 05:40:02 【问题描述】:我无法同时使用 Spring Data JPA 预测和规范。我有以下设置:
实体:
@Entity
public class Country
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "NAME", nullable = false)
private String name;
@Column(name = "CODE", nullable = false)
private String code;
---getters & setters---
投影界面:
public interface CountryProjection
String getName();
国家规格:
public class CountrySpecification
public static Specification<Country> predicateName(final String name)
return new Specification<Country>()
@Override
public Predicate toPredicate(Root<Country> eventRoot, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder)
return criteriaBuilder.equal(eventRoot.get(Country_.name), name);
;
存储库:
public interface CountryRepository extends JpaRepository<Country, Long>, JpaSpecificationExecutor<Country>
List<CountryProjection> findByName(String name); // works fine
List<CountryProjection> findAllProjectedBy(); // works fine
List<CountryProjection> findAllProjectedBy(Specification<Country> specification); //throws Exception as shown below
前两个方法 findByName 和 findAllProjectedBy 工作正常。 而第三种方法 findAllProjectedBy(Specification specification) 抛出以下异常 -
原因:java.util.NoSuchElementException: null at java.util.ArrayList$Itr.next(ArrayList.java:854) ~[na:1.8.0_102] 在 java.util.Collections$UnmodifiableCollection$1.next(Collections.java:1042) ~[na:1.8.0_102] 在 org.springframework.data.jpa.repository.query.CriteriaQueryParameterBinder.bind(CriteriaQueryParameterBinder.java:63) ~[spring-data-jpa-1.10.6.RELEASE.jar:na] 在 org.springframework.data.jpa.repository.query.ParameterBinder.bind(ParameterBinder.java:100) ~[spring-data-jpa-1.10.6.RELEASE.jar:na] 在 org.springframework.data.jpa.repository.query.ParameterBinder.bindAndPrepare(ParameterBinder.java:160) ~[spring-data-jpa-1.10.6.RELEASE.jar:na] 在 org.springframework.data.jpa.repository.query.ParameterBinder.bindAndPrepare(ParameterBinder.java:151) ~[spring-data-jpa-1.10.6.RELEASE.jar:na] 在 org.springframework.data.jpa.repository.query.PartTreeJpaQuery$QueryPreparer.invokeBinding(PartTreeJpaQuery.java:218) ~[spring-data-jpa-1.10.6.RELEASE.jar:na] 在 org.springframework.data.jpa.repository.query.PartTreeJpaQuery$QueryPreparer.createQuery(PartTreeJpaQuery.java:142) ~[spring-data-jpa-1.10.6.RELEASE.jar:na] 在 org.springframework.data.jpa.repository.query.PartTreeJpaQuery.doCreateQuery(PartTreeJpaQuery.java:78) ~[spring-data-jpa-1.10.6.RELEASE.jar:na] 在 org.springframework.data.jpa.repository.query.AbstractJpaQuery.createQuery(AbstractJpaQuery.java:190) ~[spring-data-jpa-1.10.6.RELEASE.jar:na] 在 org.springframework.data.jpa.repository.query.JpaQueryExecution$CollectionExecution.doExecute(JpaQueryExecution.java:118) ~[spring-data-jpa-1.10.6.RELEASE.jar:na] 在 org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:82) ~[spring-data-jpa-1.10.6.RELEASE.jar:na] 在 org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:116) ~[spring-data-jpa-1.10.6.RELEASE.jar:na] 在 org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:106) ~[spring-data-jpa-1.10.6.RELEASE.jar:na] 在 org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:482) ~[spring-data-commons-1.12.6.RELEASE.jar:na] 在 org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:460) ~[spring-data-commons-1.12.6.RELEASE.jar:na] 在 org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE] 在 org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:61) ~[spring-data-commons-1.12.6.RELEASE.jar:na] 在 org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE] 在 org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99) ~[spring-tx-4.3.5.RELEASE.jar:4.3.5.RELEASE] 在 org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282) ~[spring-tx-4.3.5.RELEASE.jar:4.3.5.RELEASE] 在 org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) ~[spring-tx-4.3.5.RELEASE.jar:4.3.5.RELEASE] 在 org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE] 在 org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136) ~[spring-tx-4.3.5.RELEASE.jar:4.3.5.RELEASE] 在 org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE] 在 org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133) ~[spring-data-jpa-1.10.6.RELEASE.jar:na] 在 org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE] 在 org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE] 在 org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE] 在 org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE] 在 com.sun.proxy.$Proxy82.findAllProjectedBy(Unknown Source) ~[na:na] at com.mmp.data.jpa.DataJpaApplication.run(DataJpaApplication.java:42) [类/:na]在 org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:800) [spring-boot-1.4.3.RELEASE.jar:1.4.3.RELEASE] ... 11个常用框架 省略
如何做到这一点?有什么想法吗?
【问题讨论】:
【参考方案1】:尚不支持混合投影和规格的功能。有一个bug 跟踪这个。
【讨论】:
目前有什么解决这个问题的建议吗?【参考方案2】:我找到了这个https://github.com/pramoth/specification-with-projection,它似乎可以满足您的需求。我已经将它包含在我自己的项目中,到目前为止没有问题。非常感谢 Pramoth。
基本上你扩展 JpaSpecificationExecutorWithProjection 而不是 JpaSpecificationExecutor。
public interface DocumentRepository extends JpaRepository< Country,Long>,JpaSpecificationExecutorWithProjection<Country,Long>
你会得到带有预测和规范的 findall() 方法
<R> Page<R> findAll(Specification<T> spec, Class<R> projectionClass, Pageable pageable);
【讨论】:
这个解决方案是不幸的,因为它选择了数据库中的所有内容,而不仅仅是“映射”到投影。 这个解决方案违背了使用 Projections 的目的。 它选择了所有的列并开始加载 尽管如此,我认为这在 REST API 中会运行得更快,因为您必须在 JSON 中发送更少的数据。我说的对吗? @JakubSłowikowski 否。最好不要在 REST API 范围内使用实体类。【参考方案3】:@esdee:目前,我创建了一个自定义存储库实现,在其中创建了一个动态查询,您甚至可以在其中创建本机查询并将其映射到 DTO,而无需使用投影。
为此,您可以查看此文档:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.custom-implementations
这里已经是一个例子:
Spring Data JPA Custom Repository
请记住,如果您想使其可分页,在您的自定义存储库中,您还必须创建一个计算行数的方法,您希望在 customFindAll(parameters) 中提取该方法。 缺点是我在本机查询中重写了规范。但也许自定义实施也可以与规范一起使用,如果有帮助,请告诉我。
问候, C
【讨论】:
【参考方案4】:除非您实现自己的存储库,否则没有解决方案。
【讨论】:
尝试提供有关您的解决方案的详细信息。添加您的代码,突出显示关键字 你可以去看看thoughts-on-java.org/dto-projections【参考方案5】:所以这个问题在 spring data github 中仍然存在。 正如@Naso 所说,您可以将另一个依赖项带入您的项目(https://github.com/pramoth/specification-with-projection) 或者没有什么能阻止您创建两个指向同一个表的实体类。例如
@Entity
@Table("country")
public class Country
String code;
String name;
@Entity
@Table("country")
public class CountryName
String name;
public interface CountryRepository extends JpaRepository<CountryName, Long>, JpaSpecificationExecutor<Country>
List<CountryName> findAllProjectedBy(Specification<Country> specification); //throws Exception as shown below
【讨论】:
+1 指出使用单独的轻量级实体作为投影方式的(通常被遗忘的)可能性【参考方案6】:根据您的需求变得多么复杂,您最终可能不得不实现一个自定义存储库: https://dzone.com/articles/accessing-the-entitymanager-from-spring-data-jpa
总结上面的文章,你需要为自定义方法实现一个接口(接口的名称必须以Custom
结尾):
public interface ParkrunCourseRepositoryCustom
void refresh(ParkrunCourse parkrunCourse);
然后你需要创建一个实现接口的类(类名必须以Impl
结尾):
import javax.persistence.PersistenceContext;
import javax.persistence.EntityManager;
import com.glenware.springboot.form.ParkrunCourse;
import org.springframework.transaction.annotation.Transactional;
public class ParkrunCourseRepositoryImpl implements ParkrunCourseRepositoryCustom
@PersistenceContext
private EntityManager em;
@Override
@Transactional
public void refresh(ParkrunCourse parkrunCourse)
em.refresh(parkrunCourse);
最后,您必须实现实际存储库的接口:
public interface ParkrunCourseRepository extends CrudRepository, ParkrunCourseRepositoryCustom
这将使您可以完全访问EntityManager
,从而允许您以 JPA 允许的任何方式实现查询。
【讨论】:
【参考方案7】:如果使用规范,则不能在CountryRepository
中使用。
CountryRepository cRepository;
cRepository.findAll(Specification<Country> specification);
【讨论】:
【参考方案8】:解决此问题的另一种方法是使用ProxyProjectionFactory
。您将让您的存储库获取实际实体,然后沿线(可能在您的服务层中)将结果集映射到投影类型。见下文;
public interface CountryRepository extends JpaRepository<Country, Long>, JpaSpecificationExecutor<Country>
然后在你的服务中,你这样做;
List<CountryProjection> findAllProjectedBy(Specification<Country> countrySpecification)
List<Country> countries = this.countryRepository.findAll(countrySpecification);
ProxyProjectionFactory pf= new SpelAwareProxyProjectionFactory();
return countries.stream().map(c->pf.createProjection(CountryProjection.class, c)).collect(Collectors.toList());
希望这会有所帮助!
【讨论】:
这将提取所有实体,Projection 的目的是避免这种情况,以减少提取不必要字段的时间。如果需要优化,我不会使用此解决方案 @CosminConstantinescu,你能分享一个更好的建议吗?以上是关于如何在 spring data jpa 中使用预测和规范?的主要内容,如果未能解决你的问题,请参考以下文章
处理 JPA 规范和 spring-data-jpa 时如何使用声明 Stream 作为返回类型
如何在 Spring Data 中漂亮地更新 JPA 实体?
如何在 Spring Data JPA 中使用带有分页的投影接口?