Подтвердить что ты не робот

Spring Данные JPA + JpaSpecificationExecutor + EntityGraph

(Используя 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 Данные предоставляют JpaSpecificationExecutor, который позволяет строить динамические запросы, но я не могу понять, как использовать его в сочетании с графами сущностей для нетерпеливой загрузки детей в этом конкретном случае. Возможно ли это? Есть ли какой-либо другой способ загружать "toMany" с использованием спецификаций?

4b9b3361

Ответ 1

Решение состоит в том, чтобы создать пользовательский интерфейс хранилища, который реализует эти функции:

@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 which is out of scope.
            return CustomRepository.class;
        }
    }

}

И измените заводской компонент репозитория по умолчанию на новый компонент, например, при весенней загрузке добавьте это в конфигурацию:

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

Для получения дополнительной информации о пользовательских репозиториях: http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.custom-behaviour-for-all-repositories

Ответ 2

Ответ Joepie - O.K.

но вам не нужно создавать repositoryFactoryBeanClass, настроить repositoryBaseClass

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

Ответ 3

Проект Spring Data JPA EntityGraph реализует некоторые из подходов, упомянутых в других ответах.

Он имеет, например, эти дополнительные интерфейсы репозитория:

  • EntityGraphJpaRepository который эквивалентен стандартному JpaRepository
  • EntityGraphJpaSpecificationExecutor который эквивалентен стандартному JpaSpecificationExecutor

Проверьте справочную документацию для некоторых примеров.

Ответ 4

Чтобы дополнить ответы Joep и pbo, я должен сказать, что в новых версиях 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);
 }

Ответ 5

Мне удалось осуществить это с переопределением findAll методы и добавлением к нему аннотациям @EntityGraph:

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