Spring Boot, Spring Data JPA – Rest CRUD API example

Introduction

Why Spring Data

Spring data simplifies the data access operations to a very greater extent. The most compelling feature of spring data is the ability to create repository implementations automatically, at runtime, from a repository interface. Hence, you only define the dao interface and the operations you want to perform such as save, read, delete and spring data will provide the implementations at runtime. Not only this, it also provides many inbuilt features for paginations, querying, auditing etc. Spring data provides support for both relational and NoSql DB.

What is Spring Data JPA

Among the many modules provided by Spring Data such as Spring Data Solr, Spring Data MongoDB, Spring Data REST to deal with the persistence layer, Spring Data JPA is also a sub project to deal with persisitence layer. Spring data provides enhanced support for JPA based implementations. We only require to define our repository interfaces, including custom finder methods, and Spring will provide the implementation automatically at run-time.

Different Spring Data Repositories

Lets discuss about different interfaces provided by spring data JPA to deal with persistence layer.

Repository - It is the central interface in the spring data repository abstraction. This is defined as a marker interface.

CrudRepository - CrudRepository provides methods such as save(), findOne(), findAll(), delete() etc. for the CRUD operations. This interface extends the Repository interface. While creating our dao interface we extend this interface with our custom interface and just invoke the abstract methods defined in CrudRepository and spring data will take care of providing the corresponding implementations at run-time.

JpaRepository - It is a JPA specific extension of Repository. It extends CrudRepository and provides some extra JPA related method such as flushing the persistence context and delete record in a batch.

PagingAndSortingRepository - It also extends CrudRepository provide methods to do pagination and sorting records.

Server Side implementation

Define Data Model

Following is the entity class. UserDetails.java

package com.maissen.model;

@Entity
@Table
public class UserDetails {
	
	@Id
	@Column
	@GeneratedValue(strategy = GenerationType.AUTO)
	private long id;
	@Column
	private String firstName;
	@Column
	private String lastName;
	@Column
	private String email;
	@Column
	private String password;
	
	//getters and setters goes here
	

@Entity annotation indicates that the class is a persistent Java class. – @Table annotation provides the table that maps this entity. – @Id annotation is for the primary key. – @GeneratedValue annotation is used to define generation strategy for the primary key. GenerationType.AUTO means Auto Increment field. – @Column annotation is used to define the column in database that maps annotated field.

Create Spring Rest APIs Controller

Finally, we create a controller that provides APIs for creating, retrieving, updating, deleting and finding data

@RestController
@RequestMapping("/user")
public class UserController {
	
	@Autowired
	private UserService userService;
	
	@RequestMapping(value = "/list", method = RequestMethod.GET)
	public ResponseEntity> userDetails() {
        
		List userDetails = userService.getUserDetails();
		return new ResponseEntity>(userDetails, HttpStatus.OK);
	}

	@RequestMapping(value = "/{id}", method = RequestMethod.GET)
	public ResponseEntity findById(@PathVariable(name = "id", value = "id") Long id) {

		UserDetails userDetail = userService.findById(id);
		return new ResponseEntity(userDetail, HttpStatus.OK);
	}

	@RequestMapping(method = RequestMethod.POST)
	public ResponseEntity findById(@RequestBody UserDetails userDetails) {

		UserDetails userDetail = userService.save(userDetails);
		return new ResponseEntity(userDetail, HttpStatus.OK);
	}

	@RequestMapping(value = "/email/{email:.+}", method = RequestMethod.GET)
	public ResponseEntity findById(@PathVariable(name = "email", value = "email") String email) {

		UserDetails userDetail = userService.findByEmail(email);
		return new ResponseEntity(userDetail, HttpStatus.OK);
	}

    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    public ResponseEntity delete(@PathVariable(name = "id", value = "id") Long id) {

        userService.delete(id);
        return new ResponseEntity("success", HttpStatus.OK);
    }
}

@CrossOrigin is for configuring allowed origins. – @RestController annotation is used to define a controller and to indicate that the return value of the methods should be be bound to the web response body. – @RequestMapping("/user") declares that all Apis’ url in the controller will start with /api. – We use @Autowired to inject UserService bean to local variable.

Defining Service class

Following is the service class that acts as a bridge between controller and Repository.

@Service
public class UserServiceImpl implements UserService {
	
	@Autowired
	private UserDao userDao;

	@Override
	public List getUserDetails() {
		return (List) userDao.findAll();
	}

	@Override
	public UserDetails findById(Long id) {
		return  userDao.findOne(id);
	}

	@Override
	public UserDetails save(UserDetails user) {
		return  userDao.save(user);
	}

	@Override
	public UserDetails findByEmail(String email) {
		return  userDao.findByEmail(email);
	}

	@Override
	public void delete(Long id) {
		userDao.delete(id);
	}
}

Defining DAO class

The UserDao interface extends JpaRepository which has different crud methods such as create, findOne, delete etc and hence our UserDao automatically inherits them which is available for our service class to use. Its spring data which will generate the implementations at run time for these crud methods. Hence, we dont have to provide the implementations.

Notice the generic parameters in JpaRepository. Based on these parameters, Spring data will perform different crud operations at run time on our behalf.

Apart from the different default methods defined in the JpaRepository, we have defined our own method - findByEmail(). We have one parameter defined in our entiry class as email and by the naming conventions as findByEmail, Spring data will automatically identify that this is a search operation by email of the user.

public interface UserDao extends JpaRepository<UserDetails, Long> {
	
	UserDetails findByEmail(String email);
	
}

Now we can use JpaRepository’s methods: save(), findOne(), findById(), findAll(), count(), delete(), deleteById()… without implementing these methods.

Spring data is not limited to use of only JPA and some standards methods. Even it is possible to define some custom methods and custom operations too. You can also make use of JPA provided entitymanager to perform custom data manipulation as well as use hibernate session to make advanatage of different functions provided by hibernate.

Using EntityManager in Spring Data

Following is the implementation that uses entitymanager to save data in db while using spring data.

@Repository
public class UserDaoImpl implements Userdao {
	
	@PersistenceContext
    private EntityManager entityManager;

    @Override
    public void save(UserDetails user) {
        entityManager.persist(user);
    }
	
}

Pagination and Filter with Spring Data JPA

To help us deal with this situation, Spring Data JPA provides way to implement pagination with PagingAndSortingRepository.

PagingAndSortingRepository extends CrudRepository to provide additional methods to retrieve entities using the pagination abstraction.

public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
  Page<T> findAll(Pageable pageable);
}

findAll(Pageable pageable): returns a Page of entities meeting the paging condition provided by Pageable object.

Spring Data also supports many useful Query Creation from method names that we’re gonna use to filter result in this example such as:

Page<Tutorial> findByPublished(boolean published, Pageable pageable);
Page<Tutorial> findByTitleContaining(String title, Pageable pageable);

You can find more supported keywords inside method names here.

Spring Data Page

Let’s look at the Page object.

Page is a sub-interface of Slice with a couple of additional methods. It contains total amount of elements and total pages of the entire list.

public interface Page<T> extends Slice<T> {
  static <T> Page<T> empty();
  static <T> Page<T> empty(Pageable pageable);
  long getTotalElements();
  int getTotalPages();
  <U> Page<U> map(Function<? super T,? extends U> converter);
}

If the number of items increases, the performance could be affected, it’s the time you should think about Slice.

A Slice object knows less information than a Page, for example, whether the next one or previous one is available or not, or this slice is the first/last one. You can use it when you don’t need the total number of items and total pages.

public interface Slice<T> extends Streamable<T> {
  int getNumber();
  int getSize();
  int getNumberOfElements();
  List<T> getContent();
  boolean hasContent();
  Sort getSort();
  boolean isFirst();
  boolean isLast();
  boolean hasNext();
  boolean hasPrevious();
  ...
}

Spring Data Pageable

Now we’re gonna see the Pageable parameter in Repository methods above. Spring Data infrastructure will recognizes this parameter automatically to apply pagination and sorting to database.

The Pageable interface contains the information about the requested page such as the size and the number of the page.

public interface Pageable {
  int getPageNumber();
  int getPageSize();
  long getOffset();
  Sort getSort();
  Pageable next();
  Pageable previousOrFirst();
  Pageable first();
  boolean hasPrevious();
  ...
}

So when we want to get pagination (with or without filter) in the results, we just add Pageable to the definition of the method as a parameter.

Page<Tutorial> findAll(Pageable pageable);
Page<Tutorial> findByPublished(boolean published, Pageable pageable);
Page<Tutorial> findByTitleContaining(String title, Pageable pageable);

This is how we create Pageable objects using PageRequest class which implements Pageable interface:

Pageable paging = PageRequest.of(page, size);
  • page: zero-based page index, must NOT be negative.

  • size: number of items in a page to be returned, must be greater than 0.

@Repository
public interface UserPageableDao extends JpaRepository<UserDetails, Long> {
    Page listUsers(Pageable pageable);
}

From service implementation we can invoke this Repository method as below:

public List getUserDetails1() {
        Pageable pageable = PageRequest.of(0, 3, Sort.Direction.DESC,"createdOn");
        Page page = userPageableDao.listUsers(pageable);
        return page.getContent();
    }
	
//findOne() is removed.
@Override
public UserDetails findById(Long id) {
	return  userDao.findById(id).get();
}

//delete(id) is removed
@Override
public void delete(Long id) {
	userDao.deleteById(id);
}
  • getContent() to retrieve the List of items in the page.

  • getNumber() for current Page.

  • getTotalElements() for total items stored in database.

  • getTotalPages() for number of total pages

Database Scripts

Following are some sample DML. We will be creating some dummy user details using following insert statements.

create table User_Details (id integer not null auto_increment, email varchar(255), first_Name varchar(255), last_Name varchar(255), password varchar(255), primary key (id)) ENGINE=InnoDB;

INSERT INTO user_details(email,first_Name,last_Name,password) VALUES ('admin@admin.com','admin','admin','admin');

INSERT INTO user_details(email,first_Name,last_Name,password) VALUES ('john@gmail.com','john','doe','johndoe');

INSERT INTO user_details(email,first_Name,last_Name,password) VALUES ('sham@yahoo.com','sham','tis','shamtis');

Run Application

1. Run Application.java as a java application.+

2. Now hit the url - localhost:8080/user/list and you can see following.

Last updated