Dia's code

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, we can create entity filters using CriteriaBuilder without injecting our own EntityManager. Then, we extend the JPA repository interface from JpaSpecificationExecutor, which contains methods to retrieve entities using Specification instances.

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.

#CriteriaBuilder #Hibernate #JPA #JpaSpecificationExecutor #Specification #Spring Data JPA