ne key feature of Spring is its support for the Java Persistence API (JPA), which is a Java specification for accessing, persisting, and managing data between Java objects/classes and a database. In this tutorial, we will learn how to use JPA with Spring Boot to build robust and efficient applications.
Setting up the Project
Firstly, we will kickstart the Spring Boot project. The simplest way to do that is by means of the Spring Initializr online application.
Add the following dependencies, as you can see from the following picture:
- Spring Data JPA
- H2 Database ( or the Database you are using)
- Spring Web
Alternatively, if you are using the Spring CLI, you can init the project as follows:
$ spring init -dweb,data-jpa,h2 samplewebapp
Finally, by opening your project configuration, you should have the following dependencies in place:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency>
As next step, import the project in your IDE before we add our Classes in it.
Defining the Entity
An entity in JPA represents a table in the database. To create an entity, we need to create a Java class and annotate it with @Entity. Each field in the class represents a column in the table. The following code defines an entity called Person with three fields: id, name, and surname:
public class Person { @Id @GeneratedValue(strategy = GenerationType.AUTO) Long id; String name; String surname; public Person(String name, String surname) { super(); this.name = name; this.surname = surname; } // Getter / Setters omitted for brevity }
The following Class Diagram summarizes your Entity definition:
@GeneratedValue is a JPA annotation that you can use to specify how to generate the value for the primary key field. The strategy attribute of @GeneratedValue specifies the generation strategy to use.
The GenerationType.AUTO strategy lets the persistence provider choose an appropriate strategy for the particular database. This is useful when you want to use a single set of code across multiple databases, and each database has its own way of generating primary keys.
JPA Classes
The Java Persistence API classes to use very depending on your Spring Boot versions. If using Spring Boot 2, you will need to import javax.persistence packages. On the other hand, Spring Boot 3 applications need to import the jakarta.persistence packages in the Entity Class.
Creating the repository
The repository is a layer between the database and the application that provides an interface for performing CRUD (create, read, update, delete) operations on the Person entity. To create a repository, we need to create an interface that extends the JpaRepository interface. The JpaRepository interface provides methods for performing basic CRUD operations and pagination.
public interface PersonRepository extends JpaRepository<Person, Long> { List<Person> findBySurname(String surname); }
As you can see from the above code, we are including only an extra method findBySurname to search a Person by surname.
Creating the service
The service layer is responsible for managing transactions and handling business logic. To create a service, we need to create an interface and its implementation. The following code defines a PersonService Class with a savePerson method for saving a Person
object to the database:
@Service public class PersonService { @Autowired private PersonRepository personRepository; public Person savePerson(Person person) { return personRepository.save(person); } public List<Person> findAll() { return personRepository.findAll(); } public Optional<Object> findById(long l) { return Optional.of(personRepository.findById(l)); } public Optional<List<Person>> findBySurname(String surname) { return Optional.of(personRepository.findBySurname(surname)); } }
The PersonService class has several methods for performing operations on Person objects.
- savePerson: takes a Person object as an argument and saves it to the repository using the save method of PersonRepository.
- findAll: retrieves all Person objects from the repository using the findAll method of PersonRepository.
- findById: takes a long value as an argument and returns an Optional object containing the Person object with the matching ID, using the findById method of PersonRepository.
- findBySurname: takes a String value as an argument and returns an Optional object containing a list of Person objects with the matching surname, using the findBySurname method of PersonRepository.
Optional in the Service Class
- By returning an Optional allows the caller of the method to check if a value is present before attempting to access it. This can prevent NullPointerExceptions from being thrown if the value is null. It makes the intention of the method clearer.
Creating the controller
The controller is responsible for handling incoming HTTP requests and returning the appropriate response.
This controller defines several endpoints for performing CRUD operations on Person objects. The @RestController annotation indicates that this class is a controller and the @RequestMapping annotation specifies the base path for the endpoints defined in the class.
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/persons") public class PersonController { private final PersonService personService; @Autowired public PersonController(PersonService personService) { this.personService = personService; } @PostMapping public Person savePerson(@RequestBody Person person) { return personService.savePerson(person); } @GetMapping public List<Person> findAll() { return personService.findAll(); } @GetMapping("/{id}") public Optional<Object> findById(@PathVariable long id) { return personService.findById(id); } @GetMapping("/surname/{surname}") public Optional<List<Person>> findBySurname(@PathVariable String surname) { return personService.findBySurname(surname); } }
Each endpoint simply calls the corresponding method of the PersonService class to perform the desired operation and returns the result to the client.
Coding the Application Class
Finally, we will add an Application Class which contains the @SpringBootApplication in it. We will show how to peform a basic Test run from within the Application Class using the CommandLineRunner. The CommandLineRunner bean is useful for running code that should be executed when the application starts up. It
@SpringBootApplication public class DemoApplication { private static final Logger log = LoggerFactory.getLogger(DemoApplication.class); public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @Bean public CommandLineRunner demo(PersonService personService) { return (args) -> { // save a couple of persons personService.savePerson(new Person("Jack", "Smith")); personService.savePerson(new Person("Joe", "Black")); personService.savePerson(new Person("Martin", "Bauer")); // fetch all persons log.info("Persons found with findAll():"); log.info("-------------------------------"); for (Person person : personService.findAll()) { log.info(person.toString()); } log.info(""); // fetch an individual person by ID personService.findById(1L) .ifPresent(person -> { log.info("Person found with findById(1L):"); log.info("--------------------------------"); log.info(person.toString()); log.info(""); }); log.info("Person found with findBySurname('Black'):"); log.info("--------------------------------------------"); Optional<List<Person>> people = personService.findBySurname("Smith"); if (people.isPresent()) { people.get().forEach(person -> { log.info(people.toString()); }); } log.info(""); }; } }
Then, you can run your application by simply right-clicking on your main Class from the IDE. For example:
Alternatively, you can also run it as follows:
mvn clean install spring-boot:run
or:
java -jar target/demo-0.0.1-SNAPSHOT.jar
Finally, here is the output provided by the SpringApplication class:
Persons found with findAll(): ------------------------------- Person [id=1, name=Jack, surname=Smith] Person [id=2, name=Joe, surname=Black] Person [id=3, name=Martin, surname=Bauer] Person found with findById(1L): -------------------------------- Optional[Person [id=1, name=Jack, surname=Smith]] Person found with findBySurname('Black'): -------------------------------------------- Optional[[Person [id=1, name=Jack, surname=Smith]]]
Testing the JPA application using the REST Controller
In this section we will provide some curl scripts to Test the JPA Application using the curl command line tool.
Firstly, save a new Person
object:
curl -X POST -H "Content-Type: application/json" -d '{"name": "John", "surname": "Doe"}' http://localhost:8080/persons
Then, retrieve a list of all Person
objects:
curl http://localhost:8080/persons
Next, fetch a single Person
object by ID:
curl http://localhost:8080/persons/1
Finally, retrieve a list of Person
objects by surname:
curl http://localhost:8080/persons/surname/Doe
Testing the Entity with a Spring Boot Test Class
Finally, we will add a Test Class to test the PersonService using JUnit 5 and AssertJ libraries:
@SpringBootTest class PersonServiceTest { @Autowired private PersonService personService; @Test public void testSavePerson() { Person person = new Person("John", "Doe"); Person savedPerson = personService.savePerson(person); assertThat(savedPerson).isNotNull(); assertThat(savedPerson.getId()).isNotNull(); assertThat(savedPerson.getName()).isEqualTo(person.getName()); assertThat(savedPerson.getSurname()).isEqualTo(person.getSurname()); } @Test public void testFindAll() { List<Person> persons = personService.findAll(); assertThat(persons).isNotEmpty(); } @Test public void testFindById() { Optional<Object> person = personService.findById(1L); assertThat(person).isPresent(); } @Test public void testFindBySurname() { Optional<List<Person>> persons = personService.findBySurname("Doe"); assertThat(persons).isPresent(); } }
Run the above example from the IDE and verify that all Service methods are passing the test:
Conclusion
In conclusion, Spring Boot with JPA is a powerful combination for developing web applications quickly and efficiently. With JPA, it’s easy to map Java objects to database tables and execute complex queries with just a few lines of code. And the H2 database is a lightweight, in-memory database that is well-suited for development and testing purposes. Together, Spring Boot, JPA, and H2 make it easy to build and deploy robust, data-driven applications.
Source:
Spring Boot 2 JPA: https://github.com/fmarchioni/masterspringboot/tree/master/jpa/samplewebapp
Source Boot 3 JPA: https://github.com/fmarchioni/masterspringboot/tree/master/jpa/samplewebapp3
Found the article helpful? if so please follow us on Socials