(Using Spring Data JPA) I have two entities Parent
& Child
with a OneToMany/ManyToOne bi-directional relationship between them. I add a @NamedEntityGraph
to the parent entity like so:
@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
}
Notice that the fetch type for the Parent's children is LAZY. This is on purpose. I don't always want to eager load the children when I'm querying an individual parent. Normally I could use my named entity graph to eager load the children on-demand, so to speak. But.....
There is a specific situation where I'd like to query for one or more parents AND eager load their children. In addition to this I need to be able to build this query programmatically. Spring Data provides the JpaSpecificationExecutor which allows one to build dynamic queries, but I can't figure out how to use it in conjunction with entity graphs for eager loading children in this specific case. Is this even possible? Is there some other way to eager load 'toMany entities using specifications?
The solution is to create a custom repository interface that implements these features:
@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);
}
Also create an implementation:
@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();
}
}
And create a factory:
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;
}
}
}
And change the default repository factory bean to the new bean, e.g. in spring boot add this to the configuration:
@EnableJpaRepositories(
basePackages = {"your.package"},
repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class
)
For more info about custom repositories: http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.custom-behaviour-for-all-repositories
I managed to implement this with overriding of findAll
method and adding to it annotation @EntityGraph
:
public interface BookRepository extends JpaSpecificationExecutor<Book> {
@Override
@EntityGraph(attributePaths = {"book.author"})
List<Book> findAll(Specification<Book> spec);
}
The Joepie response is O.K.
but you don't need to create repositoryFactoryBeanClass, set up repositoryBaseClass
@EnableJpaRepositories(
basePackages = {"your.package"},
repositoryBaseClass = CustomRepositoryImpl.class)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With