springjpaentitygraph

Spring Data JPA, parametrize @EntityGraph in CrudRepository interface


Is it possible with Spring Data JPA to do smth. like this:

public interface UserDao extends CrudRepository<User, Long> {

@EntityGraph(value = :graphName, type = EntityGraph.EntityGraphType.LOAD)
    @Query(value = "SELECT DISTINCT u FROM User u")
    List<User> findAllWithDetailsByGraphName(@Param(value="graphName") String graphName);

}

to be able to pass the graphName into method at run-time and invoke the load with a need set of collections? This construction is not working, producing compile time error. Any plays around it also failed...

So, I have multiple collections in User class, which I want to load on condition;

@Entity
@Table(name="user")
@NamedEntityGraphs({
        @NamedEntityGraph(name = "User.details", attributeNodes = {
                @NamedAttributeNode("phones"), @NamedAttributeNode("emails"), @NamedAttributeNode("pets")}),
        @NamedEntityGraph(name = "User.phones", attributeNodes =
                {@NamedAttributeNode("phones")}),
        @NamedEntityGraph(name = "User.emails", attributeNodes =
                {@NamedAttributeNode("emails")}),
        @NamedEntityGraph(name = "User.pets", attributeNodes =
                {@NamedAttributeNode("pets")})
})
public class User {
    @Id
    @GeneratedValue(strategy= GenerationType.AUTO, generator="native")
    @GenericGenerator(name = "native", strategy = "native")
    @Column(name="user_id")
    private Long userId;

    @Column(name="name")
    private String name;

// more fields omitted

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    private Set<Phone> phones;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    private Set<Email> emails;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    private Set<Pet> pets;
}

Now, I only must declare all the methods implicitly, like this:

@EntityGraph(value = "User.phones", type = EntityGraph.EntityGraphType.LOAD)
@Query(value = "SELECT DISTINCT u FROM User u")
List<User> findAllWithPhones();

Thnaks you for suggestions!


Solution

  • You can define a base JPA repository that accepts the entity graph as a parameter. This is particularly useful in combination with Specifications. Therefore, here follows an example based on Specifications. It's also possible to construct other queries, using different types of arguments.

    @NoRepositoryBean
    public interface MyBaseRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {
    
        T findOne(Specification<T> spec, EntityGraphType entityGraphType, String entityGraphName);
    
        List<T> findAll(Specification<T> spec, Sort sort, EntityGraphType entityGraphType, String entityGraphName);
    
    }
    

    Implement the base repository:

    @NoRepositoryBean
    public class MyBaseRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements MyBaseRepository<T, ID> {
    
        private EntityManager em;
    
        public MyBaseRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
            super(entityInformation, entityManager);
            this.em = entityManager;
        }
    
        public MyBaseRepositoryImpl(Class<T> domainClass, EntityManager em) {
            super(domainClass, em);
            this.em = em;
        }
    
        @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();
        }
    
        @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();
        }
    
    }
    

    Specify the custom base repository:

    <jpa:repositories base-package="my.domain" base-class="my.repository.MyBaseRepositoryImpl" />
    

    Extend from custom base repository:

    public interface UserRepository extends JpaRepository<User, Long>, MyBaseRepository<User, Long>, JpaSpecificationExecutor<User> {
    }
    

    Use the custom repository's method:

    Specification mySpecs = ...
    List<User> user = picklistRepository.findAll(mySpecs, EntityGraphType.LOAD, "User.phones");