Article
Limit and Offset in Spring Data JPA
Limit and offset are two properties often requested for requesting data out of a database. Implementing them with a SQL query is also not that difficult. In this article I will describe how to achieve this with the magic of Spring Data JPA repositories (without SQL).
The Entity
First, we need a class describing the entities which should be retrieved from the database. We annotate the class with @Entity to mark it as a representative of a database table. The class describes how the table in the database should look like. Each object of this class would be stored as one row in this table (in a relational database).
1@Entity
2@Data
3public class Employee {
4
5 @Id
6 @GeneratedValue(strategy = GenerationType.AUTO)
7 private Integer id;
8
9 private String name;
10
11}
@Datais an annotation of Lombok generating automatically i.a. constructor, getter and setter methods.@Idmarks the primary key. (The data type could also be different.)@GeneratedValuegenerates an id automatically. The user does not have to set an id manually.
The Repository
As a next step, we would have to implement an interface which has the name of the class plus the term Repository. Additionally, it has to extend either CrudRepository, PagingAndSortingRepository or JpaRepository. The first repository does not offer the options for paging and sorting while the last one offers the most functionality.
1public interface EmployeeRepository extends JpaRepository<Employee, Integer> { }
- For the extending repository, it has to be specified for which class this interface functions and which datatype the primary key has.
In the repository, no method has to be defined and no class has to implement any method. It automatically offers the typical CRUD functions (create, read, update and delete) and find functions with paging and sorting. Therefore, we could just say things like repository.findAll(PageRequest.of(1,5). This would return a Page
Limit and Offset
To send limit and request and get a List
1public class OffsetBasedPageRequest implements Pageable {
2
3 private int limit;
4
5 private int offset;
6
7 // Constructor could be expanded if sorting is needed
8 private Sort sort = new Sort(Sort.Direction.DESC, "id");
9
10 public OffsetBasedPageRequest(int limit, int offset) {
11 if (limit < 1) {
12 throw new IllegalArgumentException("Limit must not be less than one!");
13 }
14 if (offset < 0) {
15 throw new IllegalArgumentException("Offset index must not be less than zero!");
16 }
17 this.limit = limit;
18 this.offset = offset;
19 }
20
21 @Override
22 public int getPageNumber() {
23 return offset / limit;
24 }
25
26 @Override
27 public int getPageSize() {
28 return limit;
29 }
30
31 @Override
32 public long getOffset() {
33 return offset;
34 }
35
36 @Override
37 public Sort getSort() {
38 return sort;
39 }
40
41 @Override
42 public Pageable next() {
43 // Typecast possible because number of entries cannot be bigger than integer (primary key is integer)
44 return new OffsetBasedPageRequest(getPageSize(), (int) (getOffset() + getPageSize()));
45 }
46
47 public Pageable previous() {
48 // The integers are positive. Subtracting does not let them become bigger than integer.
49 return hasPrevious() ?
50 new OffsetBasedPageRequest(getPageSize(), (int) (getOffset() - getPageSize())): this;
51 }
52
53 @Override
54 public Pageable previousOrFirst() {
55 return hasPrevious() ? previous() : first();
56 }
57
58 @Override
59 public Pageable first() {
60 return new OffsetBasedPageRequest(getPageSize(), 0);
61 }
62
63 @Override
64 public boolean hasPrevious() {
65 return offset > limit;
66 }
67}
Then, we could create objects of this class in the service layer, call the repository and expose the list.
1@Service
2@Slf4j
3public class EmployeeServiceImpl implements EmployeeService {
4
5 @Autowired
6 private EmployeeRepository employeeRepository;
7
8 @Override
9 public List<Employee> getAllEmployees(int limit, int offset) {
10 log.debug("Get all Employees with limit {} and offset {}", limit, offset);
11 Pageable pageable = new OffsetBasedPageRequest(limit, offset);
12 return employeeRepository.findAll(pageable).getContent();
13 }
14}
Connection to Database
For the whole solution to function, the app should connect to a database. Connections to different types of databases are possible. To keep it easy, we would just mention the option to connect to a H2 in-memory database.
First step would be adding the H2 dependencies in the dependency management programme (e.g. Gradle or Maven). Afterwards, I usually add only the information about enabling the console to application.properties.
1spring.h2.console.enabled=true
2spring.h2.console.path=/h2
Have some success with providing the options of limit and offset.