如何在弹簧数据jpa中使用投影和规格?

时间:2017-01-17 08:37:18

标签: hibernate spring-boot spring-data-jpa jpql

我无法一起使用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(规范规范)抛出以下异常 -

  

引起:java.util.NoSuchElementException:null at   java.util.ArrayList $ Itr.next(ArrayList.java:854)〜[na:1.8.0_102] at at   java.util.Collections中的$ UnmodifiableCollection $ 1.next(Collections.java:1042)   〜[na:1.8.0_102] at   org.springframework.data.jpa.repository.query.CriteriaQueryParameterBinder.bind(CriteriaQueryParameterBinder.java:63)   〜[spring-data-jpa-1.10.6.RELEASE.jar:na] at   org.springframework.data.jpa.repository.query.ParameterBinder.bind(ParameterBinder.java:100)   〜[spring-data-jpa-1.10.6.RELEASE.jar:na] at   org.springframework.data.jpa.repository.query.ParameterBinder.bindAndPrepare(ParameterBinder.java:160)   〜[spring-data-jpa-1.10.6.RELEASE.jar:na] at   org.springframework.data.jpa.repository.query.ParameterBinder.bindAndPrepare(ParameterBinder.java:151)   〜[spring-data-jpa-1.10.6.RELEASE.jar:na] at   org.springframework.data.jpa.repository.query.PartTreeJpaQuery $ QueryPreparer.invokeBinding(PartTreeJpaQuery.java:218)   〜[spring-data-jpa-1.10.6.RELEASE.jar:na] at   org.springframework.data.jpa.repository.query.PartTreeJpaQuery $ QueryPreparer.createQuery(PartTreeJpaQuery.java:142)   〜[spring-data-jpa-1.10.6.RELEASE.jar:na] at   org.springframework.data.jpa.repository.query.PartTreeJpaQuery.doCreateQuery(PartTreeJpaQuery.java:78)   〜[spring-data-jpa-1.10.6.RELEASE.jar:na] at   org.springframework.data.jpa.repository.query.AbstractJpaQuery.createQuery(AbstractJpaQuery.java:190)   〜[spring-data-jpa-1.10.6.RELEASE.jar:na] at   org.springframework.data.jpa.repository.query.JpaQueryExecution $ CollectionExecution.doExecute(JpaQueryExecution.java:118)   〜[spring-data-jpa-1.10.6.RELEASE.jar:na] at   org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:82)   〜[spring-data-jpa-1.10.6.RELEASE.jar:na] at   org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:116)   〜[spring-data-jpa-1.10.6.RELEASE.jar:na] at   org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:106)   〜[spring-data-jpa-1.10.6.RELEASE.jar:na] at   org.springframework.data.repository.core.support.RepositoryFactorySupport $ QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:482)   〜[spring-data-commons-1.12.6.RELEASE.jar:na] at   org.springframework.data.repository.core.support.RepositoryFactorySupport $ QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:460)   〜[spring-data-commons-1.12.6.RELEASE.jar:na] at   org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)   〜[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE] at   org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:61)   〜[spring-data-commons-1.12.6.RELEASE.jar:na] at   org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)   〜[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE] at   org.springframework.transaction.interceptor.TransactionInterceptor $ 1.proceedWithInvocation(TransactionInterceptor.java:99)   〜[spring-tx-4.3.5.RELEASE.jar:4.3.5.RELEASE] at   org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)   〜[spring-tx-4.3.5.RELEASE.jar:4.3.5.RELEASE] at   org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)   〜[spring-tx-4.3.5.RELEASE.jar:4.3.5.RELEASE] at   org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)   〜[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE] at   org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)   〜[spring-tx-4.3.5.RELEASE.jar:4.3.5.RELEASE] at   org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)   〜[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE] at   org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor $ CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133)   〜[spring-data-jpa-1.10.6.RELEASE.jar:na] at   org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)   〜[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE] at   org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)   〜[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE] at   org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)   〜[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE] at   org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)   〜[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE] at   com.sun.proxy。$ Proxy82.findAllProjectedBy(未知来源)〜[na:na] at   com.mmp.data.jpa.DataJpaApplication.run(DataJpaApplication.java:42)   [classes /:na] at   org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:800)   [spring-boot-1.4.3.RELEASE.jar:1.4.3.RELEASE] ... 11个常见帧   省略

如何实现这一目标?有什么想法吗?

8 个答案:

答案 0 :(得分:9)

尚不支持混合投影和规格的功能。有bug跟踪此内容。

答案 1 :(得分:6)

我发现了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);

答案 2 :(得分:1)

没有解决方案,除非您实现自己的存储库。

答案 3 :(得分:0)

如果您使用规范,则无法在CountryRepository中使用。

CountryRepository cRepository;

cRepository.findAll(Specification<Country> specification);

答案 4 :(得分:0)

@esdee:现在,我创建了一个自定义存储库实现,在其中创建了一个动态查询,您甚至可以创建一个本地查询并将其映射到DTO,而无需使用投影。

为此,您可以查看以下文档:

https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.custom-implementations

这里已经是一个例子:

Spring Data JPA Custom Repository

请记住,如果要使其可分页,则必须在自定义存储库中还创建一种方法,以对要提取到customFindAll(parameters)中的行进行计数。 缺点是我重写了本机查询中的规范。但是也许自定义Implationation也可以与Specification一起使用,请告诉我是否有帮助。

关于, C

答案 5 :(得分:0)

所以这个问题在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
}



答案 6 :(得分:0)

根据您的需求的复杂程度,您可能最终必须实现自定义存储库: 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 :(得分:-1)

解决此问题的另一种方法是使用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());
}

希望这会有所帮助!