Search code examples
spring-data-jpaspring-data

Spring JPA custom repository implementation does not work


I'm using Spring Data JPA 1.10.11.RELEASE

I have a base repository that all my other repositories extend. This part works.

@NoRepositoryBean
public interface BaseRepository<T, ID> extends JpaRepository<T, Long> {
    T findById(ID id);
    List<T> getByIds(Collection<ID> ids);
    Map<ID, T> getMapByIds(Collection<ID> ids);
}

public class BaseRepositoryImpl<T, ID> extends
    SimpleJpaRepository<T, Long> implements BaseRepository<T, ID> {
    
    // implementations
}

I also want to declare a custom interface for some of the repositories to extend. So I declared an interface and an "Impl" class:

public interface TestRepository<T> {
    int myTestMethod(OffsetDateTime threshold);
}

@Repository
public class TestRepositoryImpl<T> implements TestRepository<T> {

    @Override
    public int myTestMethod(OffsetDateTime threshold) {
        System.out.println("myTestMethod");
        return 100;
    }
}

Then, I make an existing, working repository to extend this new interface:

@Repository
public interface MyEntityDataRepository extends
    BaseRepository<MyEntity, MyEntityId>,
    TestRepository<MyEntity> {

    MyEntity findBySomeCriteria(....);
}

Note: this repository worked before extending TestRepository, but after extending it as above, the application context will fail to start with an error:

Caused by: org.springframework.data.mapping.PropertyReferenceException: No property myTestMethod found for type MyEntity!
    at org.springframework.data.mapping.PropertyPath.<init>(PropertyPath.java:77)
    at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:329)
    at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:309)
    at org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:272)
    at org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:243)
    at org.springframework.data.repository.query.parser.Part.<init>(Part.java:76)
......

The configuration looks like:

<jpa:repositories
  base-package="com.xxx.repository"
  base-class="com.xxx.repository.BaseRepositoryImpl"
  transaction-manager-ref="transactionManager"
  entity-manager-factory-ref="entityManagerFactory"
/>

I felt I've been following precisely the example in the Spring Data documentation. I have also tried to play with the interface and impl names, and added

repository-impl-postfix="Impl"

to the configuration. All was futile. I got the same error every time.

Have anyone seen this problem and resolved it? Your help is much appreciated.


Solution

  • I'm going to answer my own question after my investigation.

    It is not obvious (at least not to me) in Spring's documentation here that the CustomRepository/Impl mechanism must only be used for a single repository. If you want to create some custom implementation to be inherited by multiple repositories, you'll have to customize the base repository, which is going to be used to back all repository beans.

    So I ended up adding an int myTestMethod(OffsetDateTime threshold) implementation to the base repository impl BaseRepositoryImpl. This method will be used to back the method declared in MyEntityDataRepository which extends TestRepository. Note: you must let your repository to extend the interface that declares the custom method. Otherwise, the function in the base repository impl will not be available to the repository bean which is a proxy for the interfaces only, not the base repository impl.

    Also, you can actually override the base repository's implementation if you customize the same method in the entity repository.

    This is not ideal, but it works. I'd hope I can restrict the custom method availability to some repositories only. One way to do that is to split the different "groups" of repositories into separate disjoint packages and declare a distinct base-class for each separate search path. But that may not make sense in many cases.