Spring Boot RESTful service with Pagination

Vidisha Pal
6 min readMar 29, 2022

--

We are going to implement a Spring Boot RESTful service with retrieves data from the database with server side pagination.

For simplicity, we will implement this feature with H2 database.

Let us assume that we have a personal blog where users can add comments, view all existing comments and find all comments made by a particular user.

The Spring boot app will save new user comments from this blog in an in-memory database and we will then be able to retrieve the user comments.

To begin, we open Spring Initializr from https://start.spring.io/ and add the below dependencies. Click on Generate and after the project downloads, we open it in our IDE of choice (I am using IntelliJ ).

After we open the project in our IDE, we add the following files .

Controller class — CommentsController.java

Service class — CommentService.java

Domain class — Comment.java

Data Transfer Object class — CommentDTO.java

Repository interface — CommentRepository.java

Configuration file — application.yml

We add H2 database config in application.yml. We will create a database called myblog.

Next, we add the DTO and domain classes.

Comment.java

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.util.Date;
import java.util.UUID;

@Entity
@Table(name="comment")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Comment {

@Id
@GeneratedValue()
private UUID id;

@Column(nullable = false)
private String userId;

@Column(nullable = false)
private String description;

private Date createdDate;

@Column(nullable = false)
private String createdBy;

private Date updatedDate;

}

CommentDTO.java

import lombok.Builder;
import lombok.Data;

import javax.validation.constraints.NotNull;
import java.util.Date;
import java.util.UUID;

@Data
@Builder
public class CommentDTO {

private UUID id;

private String userId;

@NotNull(message = "comment cannot be empty")
private String description;

private Date createdDate;

private Date updatedDate;

private String createdBy;

}

CommentRespositoy.java

Our repository interface will support Pagination from Spring Data’s Pageable interface. The interface extends Spring Data’s JpaRepository. This repository already supports pagination.

We have also define a method findAllByUserId() that lets us retrieve all comments based on a userId.

import com.pal.vidisha.springreact.myblog.domain.Comment;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;
import java.util.Optional;
import java.util.UUID;

public interface CommentRepository extends JpaRepository <Comment, UUID> {

Optional<List<Comment>> findAllByUserId(String userId, Pageable page);
}

CommentService.java

The service class has three methods.

The saveComment() saves new comments. We call Spring Data Jpa’s save() method to save a new comment.

The getAllComments() retrieves all comments from the database. We pass an argument of type Pageable when we make a call Spring Data JPA’s findAll() method.

commentRepository.findAll(paging);

The getCommentsByUserId() retrieves all comments made by a user. This method calls the findAllByUserId() we defined earlier in CommentRepository interface. We pass the userId and Pageable object as arguments.

import com.pal.vidisha.springreact.myblog.domain.Comment;
import com.pal.vidisha.springreact.myblog.dto.CommentDTO;
import com.pal.vidisha.springreact.myblog.repository.CommentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
@Transactional
public class CommentService {

@Autowired
CommentRepository commentRepository;

public void saveComment(CommentDTO commentDTO) {
Comment comment = Comment
.builder()
.description(commentDTO.getDescription())
.createdDate(new Date())
.createdBy(commentDTO.getCreatedBy())
.userId(commentDTO.getUserId())
.build();
commentRepository.save(comment);
}

public List<CommentDTO> getAllComments(Pageable paging){

Iterable<Comment> comments =
commentRepository.findAll(paging);
List<CommentDTO> commentsDTO = new ArrayList<>();

comments.forEach( comment -> {
commentsDTO.add(CommentDTO
.builder()
.id(comment.getId())
.userId(comment.getUserId())
.description(comment.getDescription())
.createdBy(comment.getCreatedBy())
.createdDate(comment.getCreatedDate())
.build());
});

return commentsDTO;
}


public List<CommentDTO> getCommentsByUserId(
String userId,
Pageable page) {
Optional<List<Comment>> comments =
commentRepository.findAllByUserId(userId, page);

if (comments.isPresent()) {
return comments.get()
.stream()
.map( comment -> CommentDTO.builder()
.id(comment.getId())
.description(comment.getDescription())
.userId(comment.getUserId())
.createdBy(comment.getCreatedBy())
.createdDate(comment.getCreatedDate())
.build())
.collect(Collectors.toList());

}
return null;
}
}

CommentController.java

The controller class is annotated with @RestController and @RequestMapping(“/comments”).

All API requests to http://localhost:8010/comments, will be directed to this controller.

Similar to the service class, we have three methods in the controller class.

The createComment() method saves new comments to the database.

The getComments() methods retrieves all comments. We define two parameters annotated with @RequestParams called page and pageSize.

The page parameter determines which page is to be returned as part of pagination. The first page is considered to be 0, and then incremented by one for subsequent pages. The default value is 0, which is the first page.

The pageSize parameter determines how many records will be returned for that particular page. We set a default value of 5.

Both page and pageSize are optional, which means that if not present in our API request, the default values will be considered.

We define a Pageable variable with the page and pageSize parameters. This variable is used to query our database with the pagination details that we had passed in the RequestParams annotated parameters page and pageSize.

Pageable paging  = PageRequest.of(page, pageSize);

The final method getCommentsByUser() retrieves all comments made by a user. This method also has pagination implemented similar to the getComments() method.

import com.pal.vidisha.springreact.myblog.dto.CommentDTO;
import com.pal.vidisha.springreact.myblog.service.CommentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.Date;
import java.util.List;

@RestController
@RequestMapping("/comments")
@CrossOrigin(origins = "http://localhost:3000/")
public class CommentsController {


@Autowired
CommentService commentService;

@PostMapping(
consumes = {
MediaType.APPLICATION_JSON_VALUE ,
MediaType.APPLICATION_XML_VALUE},
produces= {
MediaType.APPLICATION_JSON_VALUE ,
MediaType.APPLICATION_XML_VALUE
})
public ResponseEntity<CommentDTO> createComment(
@Valid @RequestBody CommentDTO commentDTO
) throws Exception {

commentDTO.setCreatedDate(new Date());
commentService.saveComment(commentDTO);

return new ResponseEntity<>(commentDTO, HttpStatus.CREATED);
}

@GetMapping(
consumes = {
MediaType.APPLICATION_JSON_VALUE ,
MediaType.APPLICATION_XML_VALUE},
produces= {
MediaType.APPLICATION_JSON_VALUE ,
MediaType.APPLICATION_XML_VALUE
})
public ResponseEntity<List<CommentDTO>> getComments(
@RequestParam(defaultValue = "5", required = false)
Integer pageSize,
@RequestParam(defaultValue = "0", required = false)
Integer page
) throws Exception {

Pageable paging = PageRequest.of(page, pageSize);

List<CommentDTO> commentsDTO =
commentService.getAllComments(paging);

return new ResponseEntity<>(
commentsDTO, HttpStatus.CREATED);
}

@GetMapping(
path = "/user/{userId}",
consumes = {
MediaType.APPLICATION_JSON_VALUE ,
MediaType.APPLICATION_XML_VALUE
},
produces = {
MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE
})
public ResponseEntity<List<CommentDTO>> getCommentsByUser(
@PathVariable String userId,
@RequestParam(defaultValue= "0", required = false)
Integer page ,
@RequestParam(defaultValue= "5", required = false)
Integer pageSize
) throws Exception {
Pageable paging = PageRequest.of(page, pageSize);

List<CommentDTO> commentsDTOs =
commentService.getCommentsByUserId(userId, paging);

return new ResponseEntity<List<CommentDTO>>(
commentsDTOs, HttpStatus.ACCEPTED);
}

}

Adding comments

I have added a few comments with POST requests.

The database has 8 rows.

Retrieve comments with pagination

Let us now retrieve comments. As we can see there are six comments made by user with userId = 4.

If we make make the below API GET request, we can see that five comments are returned. The default values of page=0 and pageSize=5 is considered.

http://localhost:8010/comments/user/4

Next, let us retrieve the next comments made by userId=4.

Our request URL has query params ?page=1&pageSize=5.

As mentioned before, the first page is 0, so page=1 returns the second page. Since this user has six comments in total, we get the last comment with this API request.

http://localhost:8010/comments/user/4?page=1&pageSize=5

Finally, if we make an API call with page=2, we should not get any result.

http://localhost:8010/comments/user/4?page=2&pageSize=5

Thank you for reading!

--

--