web-dev-qa-db-ja.com

Spring Data JPA + JpaSpecificationExecutor + EntityGraph

(Spring Data JPAを使用)2つのエンティティParentChildがあり、それらの間に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であることに注意してください。これは意図的なものです。個々の親にクエリを実行するときに、常に子を熱心にロードしたいとは限りません。通常、名前付きエンティティグラフを使用して、いわばオンデマンドで子を熱心にロードできます。だが.....

1人以上の親に問い合わせて、子供に熱心に負荷をかけたいという特定の状況があります。これに加えて、このクエリをプログラムで作成できる必要があります。 Spring Dataは JpaSpecificationExecutor を提供します。これにより、動的クエリを作成できますが、この特定のケースで子を積極的に読み込むためにエンティティグラフと組み合わせて使用​​する方法がわかりません。これも可能ですか?仕様を使用して多くのエンティティに '熱心にロードする他の方法はありますか?

17
Kerby

解決策は、次の機能を実装するカスタムリポジトリインターフェイスを作成することです。

@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

15
Joep

ジョーピーの応答はOKです。

ただし、repositoryFactoryBeanClassを作成し、repositoryBaseClassを設定する必要はありません。

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

プロジェクト Spring Data JPA EntityGraph は、他の回答で言及されているアプローチのいくつかを実装しています。

たとえば、次の追加のリポジトリインターフェイスがあります。

  • EntityGraphJpaRepositoryこれは標準のJpaRepositoryと同等です
  • EntityGraphJpaSpecificationExecutorこれは標準のJpaSpecificationExecutorと同等です

いくつかの例については、 リファレンスドキュメント を確認してください。

3
Tomas Pinos

findAllメソッドをオーバーライドし、それにアノテーション@EntityGraphを追加することで、これを実装することができました。

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

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);
 }
_
1
Ortomala Lokni