Skip to main navigation Skip to main content Skip to page footer

Spring Boot - Spring Data JPA

| Java Spring Boot

Spring Boot simplifies working with relational databases via Spring Data JPA, enabling you to build powerful database-backed applications quickly. Here you will see how to build a simple Spring JPA application.

Setting Up Spring Boot Project

// pom.xml 
<dependencies>
    <!-- Spring Boot Starter for JPA -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <!-- Database Driver (H2 for in-memory DB) -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

// application.properties
// H2 Database Configuration
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=update
spring.h2.console.enabled=true

Create JPA Entity

We create a Model class named User, which is annotated to function as an Entity in a JPA (Java Persistence API) context.

  1. Entity Annotation:
    • The @Entity annotation marks this class as a JPA entity, meaning it represents a table in a relational database. Each instance of the User class corresponds to a row in the table.
    • The annotated table will be called the same as the class by default. It can be changed using the name argument in the annotation.
  2. Primary Key and ID Generation:
    • The @Id annotation indicates that the id field is the primary key for this entity.
    • The @GeneratedValue annotation specifies how the primary key value is generated. Here, the GenerationType.IDENTITY strategy is used, which means the database will automatically generate the primary key values (e.g., auto-increment).
  3. Attributes:
    • The User class contains three attributes: id, name, and email. These represent the columns in the corresponding database table:
      • id is the primary key.
      • name and email are additional columns to store a user's name and email, respectively.
  4. Accessors (Getters and Setters):
    • The class includes getter and setter methods for each attribute. These methods allow other parts of the application to access and modify the values of id, name, and email. For example:
      • getId() retrieves the value of the id attribute.
      • setId(Long id) assigns a new value to the id attribute.
    • Similar methods exist for name and email.
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String email;

    // Getters and Setters
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

Create a Spring Data JPA Repository

The UserRepository interface extends JpaRepository<User, Long>. This means:

  • User: The entity type that this repository manages. It corresponds to the User class annotated with @Entity.
  • Long: The type of the primary key for the User entity, which is defined as Long in the User class.

By extending JpaRepository, UserRepository automatically inherits several powerful CRUD (Create, Read, Update, Delete) and query methods without needing additional code. For example: 

  • save(User user) to save or update a User in the database.
  • findById(Long id) to retrieve a User by its primary key.
  • deleteById(Long id) to delete a User by its primary key.
  • findAll() to retrieve all users.

The repository includes a custom method declaration: User findByEmail(String email).

  • Spring Data JPA automatically generates the implementation for this method based on its name.
  • The method searches for a User entity with the specified email in the database.
  • The name findByEmail follows the Spring Data method naming conventions, where findBy is a keyword, and email corresponds to the email field in the User entity.
  • This enables you to perform a specific query without writing the actual query manually.

The UserRepository serves as a bridge between the application and the database.

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
    // Custom Query Methods (if needed)
    User findByEmail(String email);
}

Key Notes:

  • You can add more custom methods if required, such as List<User> findByName(String name), and Spring Data JPA will automatically implement them.
  • If more complex queries are needed (e.g., joining multiple tables), you can use JPQL or native SQL queries with annotations like @Query.

Other Noticeable Repository Types:

Repository TypeKey FeaturesUse Case
JpaRepositoryFull JPA support, CRUD, paginationJPA-based applications with rich features
CrudRepositoryBasic CRUD operationsSimple CRUD use cases
PagingAndSortingRepositoryCRUD + Pagination/SortingAdding pagination and sorting
RepositoryMarker interface, custom methodsCustom repository implementation
MongoRepositoryMongoDB-specific featuresMongoDB applications
ElasticsearchRepositoryElasticsearch-specific searchElasticsearch integration
ReactiveCrudRepositoryReactive CRUD operationsReactive programming
KeyValueRepositoryKey-value storage supportRedis or key-value stores
Neo4jRepositoryGraph database supportNeo4j integration

Implement the Service Layer

The service layer acts as an intermediary between the controller and the repository, encapsulating business logic and ensuring a clean separation of concerns.

The @Service annotation marks the class as a Spring service component, making it eligible for dependency injection and detection during component scanning. It indicates that this class contains business logic related to the User entity.

To connect the Service with the Repository, we use constructor dependency injection.

Basically the Service class wraps and combines the repository methods for use throughout the application. For example a Controller class wouldn't use the Repository directly, but the corresponding Service.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserService {

    private final UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User createUser(String name, String email) {
        User user = new User();
        user.setName(name);
        user.setEmail(email);
        return userRepository.save(user);
    }

    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    public User getUserByEmail(String email) {
        return userRepository.findByEmail(email);
    }
}

Key Notes

  • Reusability: The service methods encapsulate commonly used logic, making them reusable across different parts of the application, such as controllers or other services.
  • Separation of Concerns: The service handles business logic (e.g., creating a user) and delegates database operations to the UserRepository.
  • Error Handling: (Not present in this code) Real-world applications often include error handling in services, such as checking for duplicate emails before creating a user or handling cases where a user is not found.

Create a REST Controller

Now we define a REST Controller to handle HTTP requests related to User operations. 

Class-Level Annotations

  • @RestController: Marks this class as a RESTful web controller. It combines @Controller and @ResponseBody, meaning all methods return data (e.g., JSON) directly to the client.
  • @RequestMapping("/api/users"): Maps all requests with the base path /api/users to this controller.

As with the Repository into the Service, we inject the Service via constructor injection. Furthermore we define some simple HTTP endpoints:

  1. POST /api/users:
    • Annotation: @PostMapping.
    • Purpose: To create a new user.
    • Details: Accepts a User object in the request body (mapped with @RequestBody) and calls userService.createUser() to save it.
    • Returns: The newly created User.
  2. GET /api/users:
    • Annotation: @GetMapping.
    • Purpose: To fetch all users.
    • Details: Calls userService.getAllUsers() and returns a list of all users.
    • Returns: A List<User> containing all users.
  3. GET /api/users/{email}:
    • Annotation: @GetMapping("/{email}").
    • Purpose: To fetch a specific user by their email.
    • Details: Extracts the email from the URL path using @PathVariable and calls userService.getUserByEmail(email).
    • Returns: The User object matching the email
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/users")
public class UserController {

    private UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping
    public User createUser(@RequestBody User user) {
        return userService.createUser(user.getName(), user.getEmail());
    }

    @GetMapping
    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }

    @GetMapping("/{email}")
    public User getUserByEmail(@PathVariable String email) {
        return userService.getUserByEmail(email);
    }
}

That was the last part of the application. Now you can run it and access it's endpoints.

Advantages of Spring Data JPA

  • Boilerplate-Free: Eliminates the need for writing custom DAO logic.
  • Powerful Query Support: Use methods like findByEmail or define custom queries with @Query.
  • Integration: Works seamlessly with Spring Boot for configuration and testing.
Back