如何在 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 实体?

如何在 Spring Data JPA 中使用带有分页的投影接口?

从控制台应用程序使用带有休眠功能的spring-data-jpa时如何延迟加载收集

如何在 Spring data jpa 中正确使用 findBySomeOtherId 而不是 findById?