Spring Data JPA + JpaSpecificationExecutor + EntityGraph

Ker*_*rby 17 java spring jpa spring-data spring-data-jpa

(使用Spring数据JPA)我有两个实体ParentChild他们之间的一对多/多对一双向关系.我@NamedEntityGraph像这样添加一个父实体:

@Entity
@NamedEntityGraph(name = "Parent.Offspring", attributeNodes = @NamedAttributeNodes("children"))
public class Parent{
//blah blah blah

@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
Set<Child> children;

//blah blah blah
}
Run Code Online (Sandbox Code Playgroud)

请注意,Parent的子项的获取类型是LAZY.这是故意的.当我查询个别父母时,我并不总是急于加载孩子.通常我可以使用我的命名实体图来按需加载孩子,可以这么说.但.....

有一种特殊的情况,我想查询一个或多个父母,并急切地加载他们的孩子.除此之外,我还需要能够以编程方式构建此查询.Spring Data提供了JpaSpecificationExecutor,它允许我们构建动态查询,但我无法弄清楚如何将它与实体图一起用于在这种特定情况下急切加载子项.这甚至可能吗?有没有其他方法可以使用规范急切加载到许多实体?

Joe*_*oep 15

解决方案是创建实现这些功能的自定义存储库接口:

@NoRepositoryBean
public interface CustomRepository<T, ID extends Serializable> extends JpaRepository<T, ID>, JpaSpecificationExecutor<T> {

    List<T> findAll(Specification<T> spec, EntityGraphType entityGraphType, String entityGraphName);
    Page<T> findAll(Specification<T> spec, Pageable pageable, EntityGraphType entityGraphType, String entityGraphName);
    List<T> findAll(Specification<T> spec, Sort sort, EntityGraphType entityGraphType, String entityGraphName);
    T findOne(Specification<T> spec, EntityGraphType entityGraphType, String entityGraphName);

}
Run Code Online (Sandbox Code Playgroud)

还要创建一个实现:

@NoRepositoryBean
public class CustomRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements CustomRepository<T, ID> {

    private EntityManager em;

    public CustomRepositoryImpl(Class<T> domainClass, EntityManager em) {
        super(domainClass, em);
        this.em = em;
    }

    @Override
    public List<T> findAll(Specification<T> spec, EntityGraph.EntityGraphType entityGraphType, String entityGraphName) {
        TypedQuery<T> query = getQuery(spec, (Sort) null);
        query.setHint(entityGraphType.getKey(), em.getEntityGraph(entityGraphName));
        return query.getResultList();
    }

    @Override
    public Page<T> findAll(Specification<T> spec, Pageable pageable, EntityGraph.EntityGraphType entityGraphType, String entityGraphName) {
        TypedQuery<T> query = getQuery(spec, pageable.getSort());
        query.setHint(entityGraphType.getKey(), em.getEntityGraph(entityGraphName));
        return readPage(query, pageable, spec);
    }

    @Override
    public List<T> findAll(Specification<T> spec, Sort sort, EntityGraph.EntityGraphType entityGraphType, String entityGraphName) {
        TypedQuery<T> query = getQuery(spec, sort);
        query.setHint(entityGraphType.getKey(), em.getEntityGraph(entityGraphName));
        return query.getResultList();
    }

    @Override
    public T findOne(Specification<T> spec, EntityGraph.EntityGraphType entityGraphType, String entityGraphName) {
        TypedQuery<T> query = getQuery(spec, (Sort) null);
        query.setHint(entityGraphType.getKey(), em.getEntityGraph(entityGraphName));
        return query.getSingleResult();
    }
}
Run Code Online (Sandbox Code Playgroud)

并创建一个工厂:

public class CustomRepositoryFactoryBean<R extends JpaRepository<T, I>, T, I extends Serializable> extends JpaRepositoryFactoryBean<R, T, I> {

    protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
        return new CustomRepositoryFactory(entityManager);
    }

    private static class CustomRepositoryFactory<T, I extends Serializable> extends JpaRepositoryFactory {

        private EntityManager entityManager;

        public CustomRepositoryFactory(EntityManager entityManager) {
            super(entityManager);
            this.entityManager = entityManager;
        }

        protected Object getTargetRepository(RepositoryMetadata metadata) {
            return new CustomRepositoryImpl<T, I>((Class<T>) metadata.getDomainType(), entityManager);
        }

        protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
            // The RepositoryMetadata can be safely ignored, it is used by the JpaRepositoryFactory
            //to check for QueryDslJpaRepository's which is out of scope.
            return CustomRepository.class;
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

并将默认存储库工厂bean更改为新bean,例如在spring boot中将此添加到配置中:

@EnableJpaRepositories(
    basePackages = {"your.package"},
    repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class
)
Run Code Online (Sandbox Code Playgroud)

有关自定义存储库的更多信息:http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.custom-behaviour-for-all-repositories


vte*_*raz 9

我设法通过覆盖findAll方法并添加注释来实现这一点@EntityGraph

public interface BookRepository extends JpaSpecificationExecutor<Book> {
   @Override
   @EntityGraph(attributePaths = {"book.author"})
   List<Book> findAll(Specification<Book> spec);
}
Run Code Online (Sandbox Code Playgroud)

  • 非常优雅的解决方案。谢谢 :-) (2认同)

小智 8

Joepie的反应还可以

但是您不需要创建repositoryFactoryBeanClass,设置repositoryBaseClass

@EnableJpaRepositories(
    basePackages = {"your.package"},
    repositoryBaseClass = CustomRepositoryImpl.class)
Run Code Online (Sandbox Code Playgroud)


Tom*_*nos 6

Project Spring Data JPA EntityGraph实现了其他答案中提到的一些方法。

例如,它具有这些额外的存储库接口:

  • EntityGraphJpaRepository 这相当于标准 JpaRepository
  • EntityGraphJpaSpecificationExecutor 这相当于标准 JpaSpecificationExecutor

查看参考文档以获取一些示例。