How to add custom method to Spring Data JPA

Solution 1:

You need to create a separate interface for your custom methods:

public interface AccountRepository 
    extends JpaRepository<Account, Long>, AccountRepositoryCustom { ... }

public interface AccountRepositoryCustom {
    public void customMethod();
}

and provide an implementation class for that interface:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

    @Autowired
    @Lazy
    AccountRepository accountRepository;  /* Optional - if you need it */

    public void customMethod() { ... }
}

See also:

  • 4.6 Custom Implementations for Spring Data Repositories

  • Note that the naming scheme has changed between versions. See https://stackoverflow.com/a/52624752/66686 for details.

Solution 2:

In addition to axtavt's answer, don't forget you can inject Entity Manager in your custom implementation if you need it to build your queries:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

    @PersistenceContext
    private EntityManager em;

    public void customMethod() { 
        ...
        em.createQuery(yourCriteria);
        ...
    }
}

Solution 3:

The accepted answer works, but has three problems:

  • It uses an undocumented Spring Data feature when naming the custom implementation as AccountRepositoryImpl. The documentation clearly states that it has to be called AccountRepositoryCustomImpl, the custom interface name plus Impl
  • You cannot use constructor injection, only @Autowired, that are considered bad practice
  • You have a circular dependency inside of the custom implementation (that's why you cannot use constructor injection).

I found a way to make it perfect, though not without using another undocumented Spring Data feature:

public interface AccountRepository extends AccountRepositoryBasic,
                                           AccountRepositoryCustom 
{ 
}

public interface AccountRepositoryBasic extends JpaRepository<Account, Long>
{
    // standard Spring Data methods, like findByLogin
}

public interface AccountRepositoryCustom 
{
    public void customMethod();
}

public class AccountRepositoryCustomImpl implements AccountRepositoryCustom 
{
    private final AccountRepositoryBasic accountRepositoryBasic;

    // constructor-based injection
    public AccountRepositoryCustomImpl(
        AccountRepositoryBasic accountRepositoryBasic)
    {
        this.accountRepositoryBasic = accountRepositoryBasic;
    }

    public void customMethod() 
    {
        // we can call all basic Spring Data methods using
        // accountRepositoryBasic
    }
}

Solution 4:

There's a slightly modified solution that does not require additional interfaces.

As specificed in the documented functionality, the Impl suffix allows us to have such clean solution:

  • Define in you regular @Repository interface, say MyEntityRepository the custom methods (in addition to your Spring Data methods)
  • Create a class MyEntityRepositoryImpl (the Impl suffix is the magic) anywhere (doesn't even need to be in the same package) that implements the custom methods only and annotate such class with @Component** (@Repository will not work).
    • This class can even inject MyEntityRepository via @Autowired for use in the custom methods.

Example:

Entity class (for completeness):

package myapp.domain.myentity;
@Entity
public class MyEntity {
    @Id     private Long id;
    @Column private String comment;
}

Repository interface:

package myapp.domain.myentity;

@Repository
public interface MyEntityRepository extends JpaRepository<MyEntity, Long> {

    // EXAMPLE SPRING DATA METHOD
    List<MyEntity> findByCommentEndsWith(String x);

    List<MyEntity> doSomeHql(Long id);   // custom method, code at *Impl class below

    List<MyEntity> useTheRepo(Long id);  // custom method, code at *Impl class below

}

Custom methods implementation bean:

package myapp.infrastructure.myentity;

@Component // Must be @Component !!
public class MyEntityRepositoryImpl { // must have the exact repo name + Impl !!

    @PersistenceContext
    private EntityManager entityManager;

    @Autowired
    private MyEntityRepository myEntityRepository;

    @SuppressWarnings("unused")
    public List<MyEntity> doSomeHql(Long id) {
        String hql = "SELECT eFROM MyEntity e WHERE e.id = :id";
        TypedQuery<MyEntity> query = entityManager.createQuery(hql, MyEntity.class);
        query.setParameter("id", id);
        return query.getResultList();
    }

    @SuppressWarnings("unused")
    public List<MyEntity> useTheRepo(Long id) {
        List<MyEntity> es = doSomeHql(id);
        es.addAll(myEntityRepository.findByCommentEndsWith("DO"));
        es.add(myEntityRepository.findById(2L).get());
        return es;
    }

}

Usage:

// You just autowire the the MyEntityRepository as usual
// (the Impl class is just impl detail, the clients don't even know about it)
@Service
public class SomeService {
    @Autowired
    private MyEntityRepository myEntityRepository;

    public void someMethod(String x, long y) {
        // call any method as usual
        myEntityRepository.findByCommentEndsWith(x);
        myEntityRepository.doSomeHql(y);
    }
}

And that's all, no need for any interfaces other than the Spring Data repo one you already have.


The only possible drawbacks I identified are:

  • The custom methods in the Impl class are marked as unused by the compiler, thus the @SuppressWarnings("unused") suggestion.
  • You have a limit of one Impl class. (Whereas in the regular fragment interfaces implementation the docs suggest you could have many.)
  • If you place the Impl class at a different package and your test uses only @DataJpaTest, you have to add @ComponentScan("package.of.the.impl.clazz") to your test, so Spring loads it.