219

I am looking into Spring Data JPA. Consider the below example where I will get all the crud and finder functionality working by default and if I want to customize a finder then that can be also done easily in the interface itself.

@Transactional(readOnly = true)
public interface AccountRepository extends JpaRepository<Account, Long> {

  @Query("<JPQ statement here>")
  List<Account> findByCustomer(Customer customer);
}

I would like to know how can I add a complete custom method with its implementation for the above AccountRepository? Since its an Interface I cannot implement the method there.

gresdiplitude
  • 1,665
  • 1
  • 15
  • 27
Sharad Yadav
  • 2,951
  • 5
  • 20
  • 18

13 Answers13

359

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:

kenny_k
  • 3,831
  • 5
  • 30
  • 41
axtavt
  • 239,438
  • 41
  • 511
  • 482
  • 29
    Can this custom implementation inject the actual repository, so it can use the methods defined there? Specifically, I'd like to reference various find* functions defined in the Repository interface in a higher level find implementation. Since those find*() functions don't have an implementation, I can't declare them in the Custom interface or Impl class. – JBCP Oct 23 '14 at 16:42
  • 21
    I've followed this answer, unfortunately now Spring Data is trying to find the property "customMethod" on my "Account" object as it is trying to automatically generate a query for all methods defined on the AccountRepository. Any way to stop this? – Nick Foote Jun 25 '15 at 19:47
  • 51
    @NickFoote note that the name of the class that you implement your repository should be: `AccountRepositoryImpl` not: `AccountRepositoryCustomImpl`, etc. - it's very strict naming convention. – Xeon Jul 13 '15 at 01:46
  • 1
    @JBCP, did you ever find an answer to this? – end-user Aug 14 '15 at 17:08
  • 5
    @end-user: yes, your impl object can inject the repository, no problem – JBCP Aug 14 '15 at 18:06
  • @JBCP: would you have a code snippet to demonstrate where/how? – end-user Aug 14 '15 at 18:49
  • 1
    @end-user - I updated the answer above with the example, its very simple, although I don't think its necessarily obvious. – JBCP Aug 14 '15 at 20:28
  • 1
    I see a problem in this specification. If you autowire AccountRepository in AccountRepositoryImpl you will have his own methods inside what is a weird loop. I would autowire another interface that extends the CRUD. – Esteban S Nov 30 '15 at 17:30
  • 2
    *Impl filename is important. I had a implementation class with same name as Repository interface (in another package) and Spring didn't start. – Xdg Apr 20 '16 at 09:49
  • 3
    @JBCP, thank you, this is exactly the issue I had to solve. I wanted to extend a repo which I'd already added custom find methods such as `findByFooAndBar()`. I didn't think to simply inject the repository (which already extends this) into the custom impl, because it feels like that would create a circular reference or something? but it works so... – wired00 Jul 01 '16 at 00:48
  • 5
    @wired00 I think it does create a circular reference and I can't see how @JBCP got it working. When I try and do something similar I end up with an exception: `Error creating bean with name 'accountRepositoryImpl': Bean with name 'accountRepositoryImpl' has been injected into other beans [accountRepository] in its raw version as part of a circular reference, but has eventually been wrapped.` – Robert Hunt Jul 01 '16 at 07:48
  • 1
    Actually, it does seems to work unless my custom repository implementation extends `QueryDslRepositorySupport` – Robert Hunt Jul 01 '16 at 07:54
  • @roberthunt actually I've been using this with no problem – wired00 Jul 01 '16 at 08:02
  • 6
    Yeah, see my previous comment about it not working if you're extending `QueryDslRepositorySupport` You must also inject the repository via field or setter injection rather than constructor injection otherwise it won't be able to create the bean. It does seem to work but the solution feels a bit 'dirty', I'm not sure if there are any plans to improve how this works from the Spring Data team. – Robert Hunt Jul 01 '16 at 08:19
  • 2
    injecting repository into the custom impl doesn't work in spring boot 1.4.3. You'll get a bean circular dependency error and the application fails to start. Instead, you can inject entitymanager (thanks to @jelies) and entityinformation (thanks to @NealeU) – mpprdev Jan 30 '17 at 02:00
  • I also putted @Repository to my customrepository Impl but it is not working. is it need some configuration to scan that class as repository? – Mahdi Jan 31 '17 at 13:12
  • 1
    I was putting my AccountRepositoryImpl class inside of my AccountRepositoryCustom file and having the same problem @NickFoote did. I needed to make sure that 1) the interface was named XRepositoryCustom per the naming convention, 2) the Impl class was also named correctly (without *Custom*) and 3) *RepositoryImpl was in its own file. Then everything worked. – Geyser14 Apr 25 '17 at 20:45
  • so what is the solution? get same issue with sring 1.5 – robert trudel Jun 26 '17 at 13:41
  • Is this the recommended way to do it if I want to have my own way of persisting resources? E.g. having a m-to-n relationship and wanting to enforce that the resource and its association has to be created in one run ([see also my question](https://stackoverflow.com/questions/47664170/jparepository-and-many-to-many-relations)). – Stefan Falk Dec 05 '17 at 23:25
  • why is AccountRepository extending AccountRepositoryCustom? – Kalpesh Soni Sep 26 '18 at 19:16
  • Not able to instantiate main Repository after adding Helper Custom repository though i had annotated with @repository after that also getting same error – ThinkTank Mar 14 '19 at 13:33
  • Not able to instantiate main Repository after adding Helper Custom repository though i had annotated with @repository after that also getting same error – ThinkTank Mar 14 '19 at 13:34
  • 2
    Solved the circular dependency by using the Lazy annotation with the Autowired annotation in the custom method implementation. – tsnorri Apr 01 '19 at 14:24
  • 1
    Just notice that the custom interface should contains the @NoRepositoryBean as JPARepository does to exclude repository interfaces from being picked up and thus in consequence getting an instance being created. – David Peláez May 07 '20 at 08:49
84

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);
        ...
    }
}
Community
  • 1
  • 1
jelies
  • 9,110
  • 5
  • 50
  • 65
59

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, e.g., 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.
acdcjunior
  • 132,397
  • 37
  • 331
  • 304
28

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
    }
}
Danila Piatov
  • 1,201
  • 15
  • 15
  • 1
    This worked. I want to emphasize the importance of the name of the parameter in the constructor must follow the convention in this answer (must be `accountRepositoryBasic`). Otherwise spring complained about there being 2 bean choices for injection into my `*Impl` constructor. – goat Sep 14 '18 at 20:08
  • so what is the use of AccountRepository – Kalpesh Soni Sep 26 '18 at 19:24
  • @KalpeshSoni the methods from both `AccountRepositoryBasic` and `AccountRepositoryCustom` will be available via an injected `AccountRepository` – geg Feb 06 '19 at 23:31
  • 2
    Can you please provide the way the context should be created? I am not able to put it all together. Thank you. – franta kocourek Mar 30 '20 at 06:39
  • This causes circular dependency error also. – Walking Corpse Oct 27 '22 at 04:24
17

This is limited in usage, but for simple custom methods you can use default interface methods like:

import demo.database.Customer;
import org.springframework.data.repository.CrudRepository;

public interface CustomerService extends CrudRepository<Customer, Long> {


    default void addSomeCustomers() {
        Customer[] customers = {
            new Customer("Józef", "Nowak", "nowakJ@o2.pl", 679856885, "Rzeszów", "Podkarpackie", "35-061", "Zamknięta 12"),
            new Customer("Adrian", "Mularczyk", "adii333@wp.pl", 867569344, "Krosno", "Podkarpackie", "32-442", "Hynka 3/16"),
            new Customer("Kazimierz", "Dejna", "sobieski22@weebly.com", 996435876, "Jarosław", "Podkarpackie", "25-122", "Korotyńskiego 11"),
            new Customer("Celina", "Dykiel", "celina.dykiel39@yahoo.org", 947845734, "Żywiec", "Śląskie", "54-333", "Polna 29")
        };

        for (Customer customer : customers) {
            save(customer);
        }
    }
}

EDIT:

In this spring tutorial it is written:

Spring Data JPA also allows you to define other query methods by simply declaring their method signature.

So it is even possible to just declare method like:

Customer findByHobby(Hobby personHobby);

and if object Hobby is a property of Customer then Spring will automatically define method for you.

Tomasz Mularczyk
  • 34,501
  • 19
  • 112
  • 166
6

Im using the following code in order to access generated find methods from my custom implementation. Getting the implementation through the bean factory prevents circular bean creation problems.

public class MyRepositoryImpl implements MyRepositoryExtensions, BeanFactoryAware {

    private BrandRepository myRepository;

    public MyBean findOne(int first, int second) {
        return myRepository.findOne(new Id(first, second));
    }

    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        myRepository = beanFactory.getBean(MyRepository.class);
    }
}
Peter Rietzler
  • 471
  • 5
  • 11
5

Considering your code snippet, please note that you can only pass Native objects to the findBy### method, lets say you want to load a list of accounts that belongs certain costumers, one solution is to do this,

@Query("Select a from Account a where a."nameoffield"=?1")
List<Account> findByCustomer(String "nameoffield");

Make sue the name of the table to be queried is thesame as the Entity class. For further implementations please take a look at this

Samba
  • 181
  • 2
  • 4
5

I liked Danila's solution and started using it but nobody else on the team liked having to create 4 classes for each repository. Danila's solution is the only one here that let's you use the Spring Data methods in the Impl class. However, I found a way to do it with just a single class:

public interface UserRepository extends MongoAccess, PagingAndSortingRepository<User> {

    List<User> getByUsername(String username);


    default List<User> getByUsernameCustom(String username) {
        // Can call Spring Data methods!
        findAll();

        // Can write your own!
        MongoOperations operations = getMongoOperations();
        return operations.find(new Query(Criteria.where("username").is(username)), User.class);
    }
}

You just need some way of getting access to your db bean (in this example, MongoOperations). MongoAccess provides that access to all of your repositories by retrieving the bean directly:

public interface MongoAccess {
    default MongoOperations getMongoOperations() {
        return BeanAccessor.getSingleton(MongoOperations.class);
    }
}

Where BeanAccessor is:

@Component
public class BeanAccessor implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    public static <T> T getSingleton(Class<T> clazz){
        return applicationContext.getBean(clazz);
    }

    public static <T> T getSingleton(String beanName, Class<T> clazz){
        return applicationContext.getBean(beanName, clazz);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        BeanAccessor.applicationContext = applicationContext;
    }

}

Unfortunately, you can't @Autowire in an interface. You could autowire the bean into a MongoAccessImpl and provide a method in the interface to access it, but Spring Data blows up. I don't think it expects to see an Impl associated even indirectly with PagingAndSortingRepository.

virtual-g
  • 51
  • 2
  • 2
4

If you want to be able to do more sophisticated operations you might need access to Spring Data's internals, in which case the following works (as my interim solution to DATAJPA-422):

public class AccountRepositoryImpl implements AccountRepositoryCustom {

    @PersistenceContext
    private EntityManager entityManager;

    private JpaEntityInformation<Account, ?> entityInformation;

    @PostConstruct
    public void postConstruct() {
        this.entityInformation = JpaEntityInformationSupport.getMetadata(Account.class, entityManager);
    }
    
    @Override
    @Transactional
    public Account saveWithReferenceToOrganisation(Account entity, long organisationId) {
        entity.setOrganisation(entityManager.getReference(Organisation.class, organisationId));
        return save(entity);
    }

    private Account save(Account entity) {
        // save in same way as SimpleJpaRepository
        if (entityInformation.isNew(entity)) {
            entityManager.persist(entity);
            return entity;
        } else {
            return entityManager.merge(entity);
        }
    }

}
NealeU
  • 1,264
  • 11
  • 23
3

There is another issue to be considered here. Some people expect that adding custom method to your repository will automatically expose them as REST services under '/search' link. This is unfortunately not the case. Spring doesn't support that currently.

This is 'by design' feature, spring data rest explicitly checks if method is a custom method and doesn't expose it as a REST search link:

private boolean isQueryMethodCandidate(Method method) {    
  return isQueryAnnotationPresentOn(method) || !isCustomMethod(method) && !isBaseClassMethod(method);
}

This is a qoute of Oliver Gierke:

This is by design. Custom repository methods are no query methods as they can effectively implement any behavior. Thus, it's currently impossible for us to decide about the HTTP method to expose the method under. POST would be the safest option but that's not in line with the generic query methods (which receive GET).

For more details see this issue: https://jira.spring.io/browse/DATAREST-206

  • That is unfortunate, I've wasted so much time trying to find out, what I've made wrong, and finally, I understand that there is no such feature. Why would they even implement that functionality? To have less beans? To have all dao methods in one place? I could have achieved that in other ways. Does anyone know what is the goal of "adding behaviour to single repositories" feature? – Skeeve Feb 08 '16 at 12:16
  • You can expose any repository methods via REST by simply adding the `@RestResource(path = "myQueryMethod")` annotation to the method. The quote above is just stating that Spring doesn't know how you want it mapped (i.e. GET vs POST etc.) so it's up to you to specify it via the annotation. – GreenGiant Jun 08 '18 at 01:17
1

I faced with this using mongo and spring. So let's assume we use MongoRepository to provided base crud operations, and let's say we need to implement some custom criteria query operation using mongoTemplate. To achieve one interface to inject repository for crud and custom we need to specify:

Custom interface:

public interface UserCustomRepository {
 List<User> findAllUsersBySomeCriteria(UserCriteriaRequest criteriaRequest);
}

UserRepository interface 'must' first extends UserCustomRepository and then MongoRepository

@Repository
public interface UserRepository extends UserCustomRepository, MongoRepository<User, ObjectId> {
}

UserRepositoryImpl must have the same name as what crud interface with *Impl suffix.

@Component
@NoArgsConstructor
@AllArgsConstructor(onConstructor = @__(@Autowired))
public class UserRepositoryImpl implements UserCustomRepository {

 private MongoTemplate mongoTemplate;

 @Override
 public List<User> findAllUsersBySomeCriteria(UserCriteriaRequest criteriaRequest){
  //some impl
 }
}

Let's impl some service - here we inject only UserRepository interface and use methods from crud repository and custom class impl.

@Service
@NoArgsConstructor
@AllArgsConstructor(onConstructor = @__(@Autowired))
public class UserService {

 private UserRepository userReposityry;

 public List<User> getUserByCriteria(UserCriteriaRequest request) {
   userRepository.findById(request.getUserId); // Crud repository method
   userRepository.findAllUsersBySomeCriteria(request); // custom method.
 }
}
mryba
  • 11
  • 1
  • 3
  • Wow ;-) - but not of topic here: I have never seen @AllArgsConstructor(onConstructor = @__(@Autowired)) what should I think of that. Very nice or better to implement it standard way without lombok. One thought it depends how the setup of your collegues (people that need to read the code) is. If they all are familiar it all fine. Having people changing a lot or not wanting to bve familiar with lombok might be confused. I could guess that it looks like a new syntax / way of plugin in functionality into java classes/objects. - Ok, I just looked it up: lombok states it to be experimental! – Dirk Schumacher Nov 25 '21 at 07:40
  • @RequiredArgsConstructor works really nicely for setting up constructor injection - just need to make repo private final so it knows what to put in the constructor. – UpsideDownRide Jul 30 '22 at 22:08
  • @UpsideDownRide you have right, not needed to add arg : (onConstructor = `@__(@Autowired))` - it can be simpled: `@AllArgsConstructor private final UserRepository userRepository; // final field is a key here.` – mryba Aug 01 '22 at 07:35
  • @DirkSchumacher - lombok seems to be very popular in new projects in my observation. It is very fit, no need to generate boilerplate code. But team still need to decide to use or not :) – mryba Aug 01 '22 at 07:41
0

I extends the SimpleJpaRepository:

public class ExtendedRepositoryImpl<T extends EntityBean> extends SimpleJpaRepository<T, Long>
    implements ExtendedRepository<T> {

    private final JpaEntityInformation<T, ?> entityInformation;

    private final EntityManager em;

    public ExtendedRepositoryImpl(final JpaEntityInformation<T, ?> entityInformation,
                                                      final EntityManager entityManager) {
       super(entityInformation, entityManager);
       this.entityInformation = entityInformation;
       this.em = entityManager;
    }
}

and adds this class to @EnableJpaRepositoryries repositoryBaseClass.

Devilluminati
  • 328
  • 1
  • 15
0

I use SimpleJpaRepository as the base class of repository implementation and add custom method in the interface,eg:

public interface UserRepository  {
    User FindOrInsert(int userId);
}

@Repository
public class UserRepositoryImpl extends SimpleJpaRepository implements UserRepository {

    private RedisClient redisClient;

    public UserRepositoryImpl(RedisClient redisClient, EntityManager em) {
        super(User.class, em);
        this.redisClient = redisClient;
    }


@Override
public User FindOrInsert(int userId) {

    User u = redisClient.getOrSet("test key.. User.class, () -> {
        Optional<User> ou = this.findById(Integer.valueOf(userId));
        return ou.get();
    });
    …………
    return u;
}
LoremIpsum
  • 1,652
  • 18
  • 27