8

I want to extend a JpaRepository with a custom implementation, so i add a MyRepositoryCustom interface and a MyRepositoryImpl class extending this interface.

Is there a way to call methods from JpaRepository inside my custom class?

Note: This was also asked as a comment on https://stackoverflow.com/a/11881203/40064, but I think it is common enough to deserve a separate question.

Community
  • 1
  • 1
Wim Deblauwe
  • 25,113
  • 20
  • 133
  • 211
  • We generally recommend not to extend `JpaRepository` as it exposes store specific API which clients usually shouldn't be aware of. – Oliver Drotbohm Feb 08 '15 at 14:03
  • 1
    I agree, I use `CrudRepository` as much as possible, but this is a migration project, so sometimes I need some things from `JpaRepository` – Wim Deblauwe Feb 08 '15 at 14:05

2 Answers2

14

tl;dr

To inject the core repository interface into a custom implementation, inject a Provider<RepositoryInterface> into the custom implementation.

Details

The core challenge to get that working is setting up the dependency injection correctly as you are about to create a cyclic dependency between the object you're about to extend and the extension. However this can be solved as follows:

interface MyRepository extends Repository<DomainType, Long>, MyRepositoryCustom {
  // Query methods go here
}

interface MyRepositoryCustom {
  // Custom implementation method declarations go here
}

class MyRepositoryImpl implements MyRepositoryCustom {

  private final Provider<MyRepository> repository;

  @Autowired
  public MyRepositoryImpl(Provider<MyRepository> repository) {
    this.repository = repository;
  }

  // Implement custom methods here
}

The most important part here is using Provider<MyRepository> which will cause Spring to create a lazily-initialized proxy for that dependency even while it's creating an instance for MyRepository in the first place. Inside the implementation of your custom methods you can then access the actual bean using the ….get()-method.

Provider is an interface from the @Inject JSR and thus a standardized interface and requires an additional dependency to that API JAR. If you want to stick to Spring only, you can used ObjectFactory as an alternative interface but get the very same behavior.

Oliver Drotbohm
  • 80,157
  • 18
  • 225
  • 211
  • I guess `MyCustomRepository` should be `MyRepositoryCustom` ? Also `Repository` takes 2 arguments. I am well aware you know this, but it would make the answer more complete for others who stumble upon it :) – Wim Deblauwe Feb 08 '15 at 13:28
  • I've fixed the type signature as you expected. There's no draw back in Manish's answer, it's a completely different purpose. His answer targets all repositories (which you indicated you didn't want and let me add my answer in the first place). Plus, in my use case you get access to the entire repository, not only `SimpleJpaRepository`. – Oliver Drotbohm Feb 08 '15 at 14:02
  • 1
    Would it be possible to support this by allowing to make `MyRepositoryImpl` abstract? I don't know if spring data could take that abstract class and "add" the implementations of the CrudRepo to it? It would avoid the workaround with `Provider` – Wim Deblauwe Feb 18 '15 at 14:43
  • Thanks Oliver. This should be part of the official documentation, I was looking for this since monthes. – JR Utily Feb 26 '15 at 10:00
  • What package is Provider in? Every library has its own Provider class. It this javax.inject.Provider? – Calicoder Oct 12 '18 at 17:43
  • @WimDeblauwe Abstract classes require a subclass to implement the missing behaviour so that it can be instantiated. I am super sure you know this by now, just if someone stumbles upon this as me ;D – nonNumericalFloat Jan 31 '21 at 19:45
1

The section titled Adding custom behaviour to all repositories in the documentation should help you.

For example (only for illustration purposes):

public interface ExtendedJpaRepository<T, ID extends Serializable>
       extends JpaRepository<T, ID> {
    T findFirst();
    T findLast();
}

public class ExtendedJpaRepositoryImpl<T, ID extends Serializable>
       extends SimpleJpaRepository<T, ID>
       implements ExtendedJpaRepository<T, ID> {
  public ExtendedJpaRepositoryImpl(Class<T> domainClass, EntityManager em) {
    super(domainClass, entityManager);
  }

  public T findFirst() {
    List<T> all = findAll();

    return !all.isEmpty() ? all.get(0) : null;
  }

  public T findLast() {
    List<T> all = findAll();

    return !all.isEmpty() ? all.get(all.size() - 1) : null;
  }
}

Then, configure ExtendedJpaRepositoryImpl for use as per the instructions given in the documentation linked above.

Since ExtendedJpaRepositoryImpl extends SimpleJpaRepository (which is an implementation of JpaRepository), all methods from JpaRepository can be called from ExtendedJpaRepositoryImpl.

manish
  • 19,695
  • 5
  • 67
  • 91