Spring Data JPA + JPA规范执行程序+实体图

gmxoilav  于 2022-11-14  发布在  Spring
关注(0)|答案(7)|浏览(179)

(使用Spring Data JPA)我有两个实体ParentChild,它们之间存在OneToMany/ManyToOne双向关系。

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

请注意,Parent的子对象的获取类型是LAZY。这是故意的。当我查询单个父对象时,我并不总是想立即加载子对象。通常,可以说,我可以使用命名实体图按需立即加载子对象。但是......
有一个特定的情况,我想查询一个或多个父对象,并急切地加载它们的子对象。除此之外,我需要能够以编程方式构建这个查询。Spring Data提供了JpaSpecificationExecutor,它允许构建动态查询,但是我不知道如何将它与实体图结合使用,以便在这个特定的情况下快速加载子对象。是否有其他方法可以使用规范将'toMany实体'快速加载?

omtl5h9j

omtl5h9j1#

解决方案是创建一个实现以下功能的自定义存储库接口:

@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

ajsxfq5m

ajsxfq5m2#

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

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

dwthyt8l3#

Joepie的回答是OK。
但您不需要创建存储库FactoryBeanClass,也不需要设置存储库BaseClass

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

lsmepo6l4#

项目Spring Data JPA EntityGraph实现了其他答案中提到的一些方法。
例如,它具有以下附加存储库接口:

  • EntityGraphJpaRepository,相当于标准JpaRepository
  • EntityGraphJpaSpecificationExecutor,相当于标准的JpaSpecificationExecutor

请查看reference documentation以了解一些示例。

sbdsn5lh

sbdsn5lh5#

为了补充Joeppbo的答案,我不得不说,对于新版本的Spring Data JPA,您必须修改CustomRepositoryImpl的构造函数。
该类需要具有特定于存储的资料档案库工厂实施正在使用的超类的构造函数。如果资料档案库基类具有多个构造函数,请覆盖采用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);
 }
ibps3vxo

ibps3vxo6#

顺便说一句,仓库bean的工厂不是必需的。你只需要一个简单的Bean吗?
你可以在这里看到我的完整例子:https://github.com/netstart/POCs/tree/master/jpa/jpaspecification-whith-entity-graph
第一个

ymdaylpp

ymdaylpp7#

看一看Composite Specification API,它可以满足您的需求。This类实现了Specification接口,并且可以像example中那样进行组合。如果您要获取姓“Doe”的父母及其在1.1.1990之前出生的孩子,则需要以下规范:

public final class ParentSpecifications {

    private ParentSpecifications() {
    }

    public static <S extends Path<Parent>> CompositeSpecification<Parent, S> surname(String surname) {
        return CompositeSpecification.<Parent, S, TypeSafePredicateBuilder<Path<Parent>>>of(
                (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("surname"), surname)
        );
    }

    public static <S extends From<?, Parent>> CompositeSpecification<Parent, S> fetchChildren(CompositeSpecification<?, ? super Join<?, Child>> childSpecification) {
        return CompositeSpecification.<Parent, S, TypeSafePredicateBuilder<From<?, Parent>>>of(
                (root, query, criteriaBuilder) -> {
                    query.distinct(true);
                    return childSpecification.asBuilder().toPredicate((Join<Parent, Child>) root.<Parent, Child>fetch("children", JoinType.LEFT), query, criteriaBuilder);
                });
    }
}

public final class ChildSpecifications {

    private ChildSpecifications() {
    }
    
    public static <S extends Path<Child>> CompositeSpecification<Child, S> dateOfBirth(CompositeSpecification<?, ? super Path<LocalDate>> dateOfBirthSpecification) {
        return CompositeSpecification.<Child, S, TypeSafePredicateBuilder<Path<Child>>>of(
                (root, query, criteriaBuilder) -> dateOfBirthSpecification.asBuilder().toPredicate(root.get("dateOfBirth"), query, criteriaBuilder)
        );
    }
}

和查询:

var parents = parentRepository.findAll(surname("Doe").and(fetchChildren(dateOfBirth(lessThan(LocalDate.of(1990,1,1))))));

相关问题