Spring Data JPA + JpaSpecificationExecutor + EntityGraph

时间:2014-10-10 02:43:42

标签: java spring jpa spring-data spring-data-jpa

(使用Spring Data JPA)我有两个实体Parent& Child,它们之间具有OneToMany / ManyToOne双向关系。我向父实体添加@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
}

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

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

6 个答案:

答案 0 :(得分: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);

}

同时创建一个实现:

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

创建工厂:

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

}

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

@EnableJpaRepositories(
    basePackages = {"your.package"},
    repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class
)

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

答案 1 :(得分:8)

Joepie的回应是O.K。

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

@EnableJpaRepositories(
    basePackages = {"your.package"},
    repositoryBaseClass = CustomRepositoryImpl.class)

答案 2 :(得分:1)

为了补充Joeppbo的答案,我不得不说,对于新版本的Spring Data JPA,您必须修改CustomRepositoryImpl的构造函数。现在documentation说:

  

该类需要具有特定于商店的存储库工厂实现所使用的超类的构造函数。如果存储库基类具有多个构造函数,则覆盖采用EntityInformation和特定于商店的基础结构对象(例如,EntityManager或模板类)的构造函数。

我使用以下构造函数:

public CustomRepositoryImpl(JpaEntityInformation<T,?> entityInformation, EntityManager em) {
    super(entityInformation, em);
    this.domainClass = entityInformation.getJavaType();
    this.em = em;
}

我还添加了一个私有字段来存储域类:

private final Class<T> domainClass;

这允许我摆脱已弃用的方法readPage(javax.persistence.TypedQuery<T> query, Pageable pageable, @Nullable Specification<T> spec)并改为使用:

@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, domainClass, pageable, spec);
 }

答案 3 :(得分:1)

项目Spring Data JPA EntityGraph实现了其他答案中提到的某些方法。

例如具有以下其他存储库接口:

  • EntityGraphJpaRepository等同于标准JpaRepository
  • EntityGraphJpaSpecificationExecutor等同于标准JpaSpecificationExecutor

请查看reference documentation中的示例。

答案 4 :(得分:0)

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

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

答案 5 :(得分:0)

顺便说一下,不需要存储库 bean 的工厂。你只需要一个简单的 Bean。

您可以在此处查看我的完整示例:https://github.com/netstart/POCs/tree/master/jpa/jpaspecification-whith-entity-graph


import com.specgraph.entitygraph.model.Characteristic;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.repository.NoRepositoryBean;

import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;

@NoRepositoryBean
public class CharacteristicsJpaSpecificationRepository extends SimpleJpaRepository<Characteristic, Long> {

   private final EntityManager em;

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

   public Page<Characteristic> findByTypeUsingSpecification(String type, Pageable pageable) {
       Specification<Characteristic> spec =
           (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("type"), type);

       return findAll(spec, pageable, EntityGraphType.FETCH, "Characteristic.item");
   }

   public Page<Characteristic> findAll(Specification<Characteristic> spec,
                                       Pageable pageable,
                                       EntityGraph.EntityGraphType entityGraphType,
                                       String entityGraphName) {

       TypedQuery<Characteristic> query = getQuery(spec, pageable.getSort());
       query.setHint(entityGraphType.getKey(), em.getEntityGraph(entityGraphName));
       return readPage(query, Characteristic.class, pageable, spec);
   }

}

import com.specgraph.entitygraph.model.Characteristic;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Configuration
public class MyRepositoryConfiguration {

    @PersistenceContext // this will inject em in your class
    private EntityManager em;

    @Bean
    public CharacteristicsJpaSpecificationRepository getCharacteristicsJpaSpecificationRepository() {
        return new CharacteristicsJpaSpecificationRepository(Characteristic.class, em);
    }

}

import com.specgraph.entitygraph.model.Characteristic;
import com.specgraph.entitygraph.repository.specentitygraph.CharacteristicsJpaSpecificationRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.test.context.jdbc.Sql;

import static org.assertj.core.api.Assertions.assertThat;

@DataJpaTest
@Sql(scripts = "/entitygraph-data.sql")
public class CharacteristicsJpaSpecificationTest2 {

    @Autowired
    private CharacteristicsJpaSpecificationRepository characteristicsJpaSpecificationRepository;

    @Test
    public void find() {
        int pageNumber = 1;
        int pageSize = 10;
        PageRequest pageable = PageRequest.of(pageNumber, pageSize);

        Page<Characteristic> page =
            characteristicsJpaSpecificationRepository.findByTypeUsingSpecification("Rigid", pageable);

        assertThat(page.getTotalElements()).isEqualTo(1);
    }

}