Simple custom methods for Spring Data JPA repositories
TLDR: see the last code block.
If you've ever researched how to create custom methods in a Hibernate repository, you might have come across solutions using an extra interface to extend your JPA repository. This allows for custom implementation to be automatically merged into the JPA repository instance. Sample code for this solution may look something like this:
public interface YourEntityRepositoryExtension {
YourEntity findBySomeDifficultCondition();
}
@Repository
public class YourEntityRepositoryExtensionImpl
implements YourEntityRepositoryExtension {
@PersistenceContext
private EntityManager entityManager;
@Override
YourEntity findBySomeDifficultCondition() {
CriteriaBuilder criteriaBuilder = entityManager.unwrap(Session.class).getCriteriaBuilder();
// -- omitted --
}
}
@Repository
public interface YourEntityRepository
extends JpaRepository<YourEntity, Long>,
YourEntityRepositoryExtension {
YourEntity findByName(String name);
}
That's a lot of code just to be able to use CriteriaBuilder in a repository. Moreover, the methods are now spread over 2 interfaces instead of just 1.
It can be better
Spring Data JPA comes with two useful interfaces: Specification<T> and JpaSpecificationExecutor<T>.
public interface Specification<T> extends Serializable {
Predicate toPredicate(Root<T> root,
CriteriaQuery<?> query,
CriteriaBuilder criteriaBuilder);
}
By implementing Specification
With that information, we can get rid of YourEntityRepositoryExtension and its implementation, and come up with something like this:
@Repository
public interface SomeDifficultCondition
implements Specification<YourEntity> {
@Override
Predicate toPredicate(Root<YourEntity> root,
CriteriaQuery<?> query,
CriteriaBuilder criteriaBuilder) {
// -- omitted --
}
}
@Repository
public interface YourEntityRepository
extends JpaRepository<YourEntity, Long>,
JpaSpecificationExecutor<YourEntity> {
// Inherited from JpaSpecificationExecutor<YourEntity>
// List<YourEntity> findAll(Specification<YourEntity> specification);
YourEntity findByName(String name);
}
Great! Now we can pass an instance of SomeDifficultCondition to the repository, and it will provide us with a ready CriteriaBuilder. This solution is already pretty neat by itself, but can we remove even more code?
Turns out we can. We can do this:
@Repository
public interface YourEntityRepository
extends JpaRepository<YourEntity, Long>,
JpaSpecificationExecutor<YourEntity> {
default List<YourEntity> findBySomeDifficultCondition() {
return findAll(Specification.where((root, query, criteriaBuilder) -> {
// -- omitted --
});
}
YourEntity findByName(String name);
}
Now we're down to one file, with all repository methods contained within it. With the help of lambda expressions, it even looks good.