spring-data-jpa 如何使用预测和规格与 spring data jpa ?

8hhllhi2  于 2022-11-10  发布在  Spring
关注(0)|答案(8)|浏览(266)

我不能同时使用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(规范说明)抛出以下异常-

Caused by: java.util.NoSuchElementException: null at java.util.ArrayList$Itr.next(ArrayList.java:854) ~[na:1.8.0_102] 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(Unknown Source) ~[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 common frames omitted

如何才能做到这一点?有什么想法吗?

xqk2d5yq

xqk2d5yq1#

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

w46czmvw

w46czmvw2#

我找到了这个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);

数据库级预测更新

下面有一条过时的评论。
这种解决方案的缺点在于,它选择了DB中的所有内容,而不仅仅是将其“Map”到投影。
此库的最新版本支持指向提交的数据库级投影链接。

最新Sping Boot 版本的更新

this pull request未合并之前,为了支持最新的Sping Boot 版本,可以将固定库源代码从此处直接复制粘贴到项目https://github.com/v-ladynev/specification-with-projection-embeded

hjzp0vay

hjzp0vay3#

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

wpx232ag4#

@esdee:现在,我创建了一个自定义存储库实现,其中我创建了一个动态查询,您甚至可以在其中创建一个本地查询,并将其Map到DTO,而无需使用投影。
为此,您可以查看此文档:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.custom-implementations
下面是一个已经存在的例子:
Spring Data JPA Custom Repository
请记住,如果您想使其可分页,您还必须在自定义存储库中创建一个方法来计算行数,您希望在customFindAll(参数)中提取这些行。缺点是我重写了原生查询中的规范。但也许自定义实现也可以与规范一起工作,如果有帮助,请告诉我。
此致,C

7uhlpewt

7uhlpewt5#

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

piok6c0g

piok6c0g6#

根据需求的复杂程度,最终可能需要实现一个自定义存储库: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允许的任何方式实现查询。

3b6akqbq

3b6akqbq7#

如果使用Specification,则不能在CountryRepository中使用。

CountryRepository cRepository;

cRepository.findAll(Specification<Country> specification);
uurv41yg

uurv41yg8#

另一个解决这个问题的方法是使用ProxyProjectionFactory,你可以让你的存储库获取实际的实体,然后沿着这条线(可能在你的服务层),把结果集Map到投影类型。

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());
}

希望这对你有帮助!

相关问题