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 calledAccountRepositoryCustomImpl
, the custom interface name plusImpl
- 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, sayMyEntityRepository
the custom methods (in addition to your Spring Data methods) - Create a class
MyEntityRepositoryImpl
(theImpl
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.
- This class can even inject
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.