(Spring Data JPAを使用)2つのエンティティ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であることに注意してください。これは意図的なものです。個々の親にクエリを実行するときに、常に子を熱心にロードしたいとは限りません。通常、名前付きエンティティグラフを使用して、いわばオンデマンドで子を熱心にロードできます。だが.....
1人以上の親に問い合わせて、子供に熱心に負荷をかけたいという特定の状況があります。これに加えて、このクエリをプログラムで作成できる必要があります。 Spring Dataは JpaSpecificationExecutor を提供します。これにより、動的クエリを作成できますが、この特定のケースで子を積極的に読み込むためにエンティティグラフと組み合わせて使用する方法がわかりません。これも可能ですか?仕様を使用して多くのエンティティに '熱心にロードする他の方法はありますか?
解決策は、次の機能を実装するカスタムリポジトリインターフェイスを作成することです。
@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
ジョーピーの応答はOKです。
ただし、repositoryFactoryBeanClassを作成し、repositoryBaseClassを設定する必要はありません。
@EnableJpaRepositories(
basePackages = {"your.package"},
repositoryBaseClass = CustomRepositoryImpl.class)
プロジェクト Spring Data JPA EntityGraph は、他の回答で言及されているアプローチのいくつかを実装しています。
たとえば、次の追加のリポジトリインターフェイスがあります。
EntityGraphJpaRepository
これは標準のJpaRepository
と同等ですEntityGraphJpaSpecificationExecutor
これは標準のJpaSpecificationExecutor
と同等ですいくつかの例については、 リファレンスドキュメント を確認してください。
findAll
メソッドをオーバーライドし、それにアノテーション@EntityGraph
を追加することで、これを実装することができました。
public interface BookRepository extends JpaSpecificationExecutor<Book> {
@Override
@EntityGraph(attributePaths = {"book.author"})
List<Book> findAll(Specification<Book> spec);
}
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);
}
_