9

I have a custom repository declared like below(written in Kotlin):

interface FooRepository : JpaRepository<Foo, Int> {
    fun findByFoo(foo: String): List<Foo>

    fun findByBar(bar: String): List<Foo> {
        //custom implementation
    }
}

data class Foo(var id: Int, var foo: String, var bar: String)

Both methods satisfy the naming convention of JPA repository, but I want to implement the second method (FooRepository.findByBar) on my own. How can I prevent JPA from creating a query for it?
Note that my custom implementation involves computation logic, thus the @Query annotation that allows for a custom query doesn't meet my requirement.
Besides, in the real situation, it's necessary and reasonable to do this, so don't post your answer or comment if you're trying to suggest for a "better" design pattern, like placing the implementation in the service layer, etc.

0x269
  • 688
  • 8
  • 20
  • 1
    If you want disable query creation then you simply break Spring naming convention for that method, so instead of `findByBar` name it something like `searchByBar` or `fetchByBar` – Nikolai Shevchenko Apr 05 '22 at 07:32
  • 4
    @NikolaiShevchenko Thanks for your solution, but it doesn't work pitifully. When I renamed it to `fetchByBar`, I still got a `QueryCreationException`, saying: No property 'fetchByBar' found for type 'Foo'! – 0x269 Apr 05 '22 at 09:07
  • Maybe this https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.customize-base-repository might offer a solution? Creating a custom method there that still has some context (at least the of SimpleJpaRepository) and gets used when "referenced" by signature in an interface? I didn't try it tough and I dislike it, as it imposes one single Base Repository for the whole application though. I'm facing the same situation as the original question and probably will just roll with the *CustomImpl approach and re-creating base-functionality there.. – icyerasor Feb 25 '23 at 13:53
  • 1
    OP, @icyerasor, I posted another answer that works in Kotlin, allows any method names and does not require another interface, so you do not lose context. – Bruno Medeiros Jun 19 '23 at 13:40

3 Answers3

1

TL;DR

If you want your repository custom method to just delegate to another method, writing a non-abstract method in your interface is the way to go.

I Kotlin, though, you need to add the -Xjvm-default=all compile parameter, otherwise Spring will think your method does not have a body and will fail initialization anyway.


I just had the same problem today on a Spring/Kotlin project, and it seemed such a simple use case, I was wondering how we didn't have more people complaining about it.

It turns out this is a corner case of how Kotlin by default handles non-abstract methods in interfaces!

The solution everyone recommends, which is creating a separate interface and implementation for the custom methods, doesn't really work if you want to eventually delegate to a auto-generated method.

As I needed my findByBar equivalent to delegate, my first try was to simply write a body in the interface method, which is totally supported by Kotlin, that eventually delegates to an auto-generated method. To my surprise, Spring was still crashing saying that I needed to add an @Query annotation.

Then, after seeing some other people successfully using default methods in Java (e.g 1 and 2), I gave it a try. I created the same thing, in Java, with a default method in the interface, and it worked!

As Kotlin must be as capable as Java, it was just a matter of time until I figured I just needed the -Xjvm-default=all compiler parameter (full issue history here) so my non-abstract method was properly generated as a default interface method.

Bruno Medeiros
  • 2,251
  • 21
  • 34
0

You can simply create another interface like 'FooRepositoryCustom' and also create a Implementation class for that (FooRepositoryCustomImpl) and do your implementation on that FooRepositoryCustomImpl class and extend the FooRepositoryCustom interface to your FooRepository interface.

FooRepositoryCustom:

public interface FooRepositoryCustom {
    fun findByBar(bar: String): List<Foo>;
}

FooRepositoryCustomImpl:

public class FooRepositoryCustomImpl implements FooRepositoryCustom {
    @Override
    public fun findByBar(bar: String): List<Foo> {
          //your implementation goes here
    }
}

FooRepository:

interface FooRepository : JpaRepository<Foo, Int>, FooRepositoryCustom {

}
  • 5
    Thanks for your answer. This solution does work, but my self-implemented method will lose the context of JpaRepository, which is not intended. – 0x269 Apr 05 '22 at 09:14
0

Best practise: As suggested by Heshan, you can add custom interfaces to your repository interface. Your implementation will be loaded (file name with "Impl" suffix) and it will override the default methods of the same name.

Extending JpaRepository: Why do you want to extend the JpaRepository interface? If you do this, Spring will attempt to create a repository instance itself. To prevent this, annotate your repository interface with @NoRepositoryBean.

@NoRepositoryBean
public interface FooRepositoryCustom extends JpaRepository<Foo, Int> {
}

Reference: I am trying to do something similar with Spring Data Elasticsearch - override existing repository methods. I followed their documentation, see https://docs.spring.io/spring-data/elasticsearch/docs/4.1.15/reference/html/#repositories.custom-implementations

StephenRUK
  • 41
  • 2