A detailed guide on How to hide a specific Spring data repository methods. Includes an example of restricting specific Spring data repository methods and creating a read-only repository using a custom repository interface.
Tutorial Contents
Overview
This tutorial is a step by step guide on How to hide certain repository methods in Spring Data repositories. Also, it covers examples of creating a generic read-only repository and creating a repository that allows search operation only on a specific columns. Before that, the tutorial takes you through a basic setup and the concepts around the intermediate repository interface.
If you are looking for basic understanding on the query methods, we recommend reading our Spring Data JPA – Query Methods article.
Why to Hide/Expose Repository Methods?
Firstly, we will understand why it is important to hide or allow only a certain methods in a Repository and what are the circumstances, it will be useful. However, If you are clear with the concepts, you can jump to the next section.
In order to perform various CRUD (Create, Read, Update, and Delete) operations on database entities, Spring Data provides repository interfaces. Such repository interfaces allows you to define query methods without writing any implementation. Spring Data at runtime, derives native SQL queries based on the query method names and parameters. Also these interfaces, by default expose a bunch of CRUD operations to the clients.
However, in some cases we may want to restrict user operations to a certain query operations. For example, a reporting service may want to execute various read queries on the tables and it may not want to update it. In other words, we may want to create a read-only repository that does not create, update or delete entities in database tables. On the other hand, we may want the find operations to run only on a few indexed columns. Thus, we wish to limit the read queries only on the indexed columns and hide others. There are a variety of use cases, in which we will not want to expose all the operations on the a database tables.
Basic Setup
Before we create an entity bean we have to make sure we have dependencies set. For example, we will need to add Spring Data JPA dependency in our Spring Boot project.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Code language: Java (java)
Next, is our example Student Entity Bean.
@Entity
@Data
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long student_id;
private String firstName;
private String lastName;
private int year;
}
Code language: Java (java)
Extend Repository Interface
As you might be aware that adding Spring Data JPA dependency makes CrudRepository and JPARepository available. Also, both of these interfaces contain a variety of query method declarations. Thus, in a normal scenario we would extend our Student Repository from one of these interfaces. However, that will expose all of the predefined query methods on our repository.
In order to avoid that, we will create our own intermediate super interface, which extends directly from Repository. Repository is a super interface in Spring Data repository hierarchy and does not contain any methods. Thus, we can declare only the required methods in our super interface. Once that is done, we will extend other repositories from our super interface. As the super interface acts as an intermediator we will call it intermediate repository interface.
Create @NoRepositoryBean interface extending Repository.
@NoRepositoryBean
public interface LimitedRepository<T, ID>
extends Repository<T, ID> {
}
Code language: Java (java)
Above snippet shows how our intermediate repository will look at the minimal. In the next steps, we can add only the required methods in this interface and extend our concrete repository interfaces from this.
What is NoRepositoryBean Annotation?
Spring Data repositories allow us to write repository interfaces for entity beans. At runtime, Spring Data creates proxy implementations for such repositories. Such proxies contain implementations of the query methods defined in the repository or any of its parent interfaces.
However, we can use @NoRepositoryBean on an interface that we don’t want to have a proxy implementation. Most commonly, this annotation is used on the intermediate repository interfaces, to which other repository interfaces are going to extend.
For example, the LimitedRepository interface is created as a super interface for the StudentRepository. Thus we do not want proxies to be created for LimitedRepository.
Hide Repository Methods
Now that we understood the concepts of an intermediate interface and @NoRepositoryBean annotation, we will demonstrate how to enable only a specific operations on repository.
Let’s consider, we wish all the find operations to be restricted to the primary key column only. Also, we want all of our repositories to follow this behaviour.
In order to do that we will create a generic intermediate repository interface having findById() method defined.
@NoRepositoryBean
public interface LimitedRepository<T, ID>
extends Repository<T, ID> {
Optional<T> findById(ID id);
}
Code language: Java (java)
Now, we will extend all of our repositories from the LimitedRepository class to make them follow this behaviour. Next is StudentRepository that supports find operations on Id column only.
@Repository
public interface StudentRepository
extends LimitedRepository<Student, Long> {
}
Code language: Java (java)
We can only invoke findById() method on this repository as shown below.
@GetMapping("/students/{id}")
public Student getStudent(@PathVariable long id) {
return studentRepository.findById(id)
.orElseThrow(StudentNotFoundException::new);
}
Code language: Java (java)
However, repositories can also define additional methods if required. Next, is an example of StudentRepository that supports find by first name of a student. As this find operation is specific for a Student it is not part of the generic repository interface.
@Repository
public interface StudentRepository
extends LimitedRepository<Student, Long> {
List<Student> findByFirstName(String firstName);
}
Code language: Java (java)
Read-Only Repository
This section covers a quick example of creating a generic read-only repository in Spring Data. Sometimes, our applications only intend to read data from database. For such scenarios it is a good practice to disallow all the write operations to prevent accidental updates to database.
Create a generic Read-Only Repository.
@NoRepositoryBean
public interface ReadOnlyRepository<T, ID>
extends Repository<T, ID> {
Optional<T> findById(ID var1);
boolean existsById(ID var1);
Iterable<T> findAll();
Iterable<T> findAllById(Iterable<ID> var1);
long count();
}
Code language: Java (java)
Extend the intermediate repository into the repositories that needs read-only behaviour.
@Repository
public interface StudentRepository
extends ReadOnlyRepository<Student, Long> {
}
Code language: Java (java)
Any repository interface that extends from ReadOnlyRepository is by default read-only. However, they can still add any specific methods definitions that they want to support.
Summary
This tutorial covered a Step by Step approach of Hiding or Restricting a specific Spring data repository query methods. To achieve this, firstly we need to create an intermediate repository interface that extends from Repository. Secondly, the intermediate interface should define only the specific methods and mark itself with @NoRepositoryBean. Lastly, all the concrete repository interfaces should extend from the intermediate repository.
Apart from the conceptual understanding, we also covered examples of allowing find operations only on a specific columns or creating a read-only repository by disallowing any write, delete, or update operations.
For more on Spring Data JPA visit our series of tutorials at Hands on Spring Data JPA.