Implement global filter query which involves several Entity with Spring Data Jpa.

Background

It is common that we need to implement similar filter query in our application. For example, we have User and Post entities whereby both contains status field. We would want to ensure that when these Entity is retrieved, only ACTIVE ones will be returned.

In this tutorial we will implement a global filter by utilising Spring Data Jpa repositoryBaseClass.

Integration Tests

There are two tests which involves two entities - User and Country. User contains a status field while Country does not.

When findAll is triggered for User then only ACTIVE users will be returned.

class UserRepositoryTests {

    @Autowired
    private UserRepository users;

    @BeforeEach
    void setup() {
        users.saveAll(List.of(
                new User("Rashidi Zin", ACTIVE),
                new User("John Doe", INACTIVE)
        ));
    }

    @Test
    @DisplayName("Given there are two users with status ACTIVE and INACTIVE, when findAll is invoked, then only ACTIVE users are returned")
    void findAll() {
        assertThat(users.findAll())
                .extracting("status")
                .containsOnly(ACTIVE);
    }

}

However, when findAll is triggered for Country then all countries will be returned.

class CountryRepositoryTests {

    @Autowired
    private CountryRepository countries;

    @BeforeEach
    void setup() {
        countries.saveAll(List.of(
                new Country("DE", "Germany"),
                new Country("MY", "Malaysia")
        ));
    }

    @Test
    @DisplayName("Given there are two countries, when findAll is invoked, then both countries are returned")
    void findAll() {
        assertThat(countries.findAll())
                .hasSize(2)
                .extracting("isoCode")
                .containsOnly("DE", "MY");
    }

}

Configuration Class

We will start by defining repositoryBaseClass

class JpaCustomBaseRepository<T, ID> extends SimpleJpaRepository<T, ID> {

    public JpaCustomBaseRepository(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
    }

    @Override
    public List<T> findAll() {
        var hasStatusField = Stream.of(ReflectionUtils.getDeclaredMethods(getDomainClass())).anyMatch(field -> field.getName().equals("status"));
        return hasStatusField ? findAll(where((root, query, criteriaBuilder) -> root.get("status").in(ACTIVE))) : super.findAll();
    }

}

In JpaCustomBaseRepository we will define a method findAll which will be used by all Entity to retrieve data. This method will filter any Entity with status field and return only ACTIVE ones.

To recap, User contains status field while Country does not. Therefore, when findAll is triggered for User then only ACTIVE users will be returned. However, when findAll is triggered for Country then all countries will be returned.

Next we will inform Spring Data Jpa to use JpaCustomBaseRepository as the base class for all Entity by defining @EnableJpaRepositories in JpaConfiguration.

@Configuration
@EnableJpaRepositories(
        basePackages = "zin.rashidi.boot.data.jpa",
        repositoryBaseClass = JpaCustomBaseRepository.class
)
class JpaConfiguration {

}

Verification

To ensure that our implementation is working as expected, we will execute tests defined in UserRepositoryTests and CountryRepositoryTests.

Both tests should pass.