Spring Data JPA: Implementing Custom Repository Behavior with Specifications
I would like to create a Spring Data JPA repository with custom behavior, and implement that custom behavior using Specifications. I have gone through the Spring Data JPA documentation for implementing custom behavior in a single repository to set this up, except there is no example of using a Spring Data Specification from within a custom repository. How would one do this, if even possible?
I do not see a way to inject something into the custom implementation that takes a specification. I thought I would be tricky and inject the CRUD repository portion of the repository into the custom portion, but that results in a circular instantiation dependency.
I am not using QueryDSL. Thanks.
I guess the primary source for inspiration could be how SimpleJpaRepository
handles specifications. The key spots to have a look at are:
SimpleJpaRepository.getQuery(…)
- it's basically creating a CriteriaQuery
and bootstraps a select using a JPA Root
. Whether the latter applies to your use case is already up to you. I think the former will apply definitely. SimpleJpaRepository.applySpecificationToCriteria(…)
- it basically uses the artifacts produced in getQuery(…)
(ie the Root
and the CriteriaQuery
) and applies the given Specification
to exactly these artifacts. this is not using Specification, so not sure if it's relevant to you, but one way that I was able to inject custom behavior is as follows,
Basic structure: as follows
i. create a generic interface for the set of entity classes which are modeled after a generic parent entity. Note, this is optional. In my case I had a need for this hierarchy, but it's not necessary
public interface GenericRepository<T> {
// add any common methods to your entity hierarchy objects,
// so that you don't have to repeat them in each of the children entities
// since you will be extending from this interface
}
ii. Extend a specific repository from generic (step 1) and JPARepository as
public interface MySpecificEntityRepository extends GenericRepository<MySpecificEntity>, JpaRepository<MySpecificEntity, Long> {
// add all methods based on column names, entity graphs or JPQL that you would like to
// have here in addition to what's offered by JpaRepository
}
iii. Use the above repository in your service implementation class
Now, the Service class may look like this,
public interface GenericService<T extends GenericEntity, ID extends Serializable> {
// add specific methods you want to extend to user
}
The generic implementation class can be as follows,
public abstract class GenericServiceImpl<T extends GenericEntity, J extends JpaRepository<T, Long> & GenericRepository<T>> implements GenericService<T, Long> {
// constructor takes in specific repository
public GenericServiceImpl(J genericRepository) {
// save this to local var
}
// using the above repository, specific methods are programmed
}
specific implementation class can be
public class MySpecificEntityServiceImpl extends GenericServiceImpl<MySpecificEntity, MySpecificEntityRepository> implements MySpecificEntityService {
// the specific repository is autowired
@Autowired
public MySpecificEntityServiceImpl(MySpecificEntityRepository genericRepository) {
super(genericRepository);
this.genericRepository = (MySpecificEntityRepository) genericRepository;
}
}