Module: Database Integration

Spring Data JPA

This section will guide you through integrating a database with your Spring Boot application using Spring Data JPA. We'll cover the basics of setting up JPA, defining entities, creating repositories, and performing CRUD operations.

What is Spring Data JPA?

Spring Data JPA simplifies database access in Spring applications. It leverages the Java Persistence API (JPA) to interact with relational databases. Instead of writing boilerplate code for common database operations, Spring Data JPA provides a repository abstraction that handles much of the work for you.

Key Benefits:

  • Reduced Boilerplate: Less code to write for CRUD operations.
  • Repository Abstraction: Provides a consistent interface for data access.
  • Automatic Query Generation: Derives queries from method names.
  • Easy Integration: Seamlessly integrates with Spring Boot.

Prerequisites

  • Java Development Kit (JDK): Version 8 or higher.
  • Maven or Gradle: Build tool for managing dependencies.
  • Integrated Development Environment (IDE): IntelliJ IDEA, Eclipse, or VS Code.
  • Database: MySQL, PostgreSQL, H2 (in-memory), or any other JPA-compatible database. We'll use H2 for simplicity in this tutorial.

Step 1: Add Dependencies

First, add the necessary dependencies to your pom.xml (Maven) or build.gradle (Gradle) file.

Maven (pom.xml):

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

Gradle (build.gradle):

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'com.h2database:h2'
}

Explanation:

  • spring-boot-starter-data-jpa: Provides the core Spring Data JPA functionality.
  • h2: Adds the H2 in-memory database as a runtime dependency. You can replace this with the driver for your preferred database (e.g., mysql-connector-java, postgresql).

Step 2: Configure Database Connection

Spring Boot automatically configures a data source when it finds the appropriate driver on the classpath. For H2, no explicit configuration is usually needed. However, you can customize the connection properties in src/main/resources/application.properties or application.yml.

application.properties:

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=create  # Creates tables automatically
spring.jpa.show-sql=true # Shows SQL statements in the console

application.yml:

spring:
  datasource:
    url: jdbc:h2:mem:testdb
    driverClassName: org.h2.Driver
    username: sa
    password:
  jpa:
    hibernate:
      ddl-auto: create
    show-sql: true

Explanation:

  • spring.datasource.url: The JDBC URL for connecting to the database. jdbc:h2:mem:testdb creates an in-memory database named testdb.
  • spring.datasource.driverClassName: The fully qualified name of the JDBC driver class.
  • spring.datasource.username: The database username.
  • spring.datasource.password: The database password.
  • spring.jpa.hibernate.ddl-auto: Controls how JPA handles database schema creation and updates. create creates the schema if it doesn't exist. Other options include update and create-drop. Caution: create-drop will drop the database schema when the application stops.
  • spring.jpa.show-sql: Enables logging of SQL statements to the console. Useful for debugging.

Step 3: Define the Entity

An entity represents a table in your database. Create a Java class annotated with @Entity to define your entity.

import javax.persistence.*;

@Entity
@Table(name = "products") // Optional: Specify the table name
public class Product {

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

    private String name;

    private double price;

    public Product() {}

    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }

    // 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 double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}

Explanation:

  • @Entity: Marks the class as a JPA entity.
  • @Table(name = "products"): Specifies the database table name. If omitted, the table name will be derived from the class name (e.g., product).
  • @Id: Marks the id field as the primary key.
  • @GeneratedValue(strategy = GenerationType.IDENTITY): Configures the primary key to be auto-generated by the database (using the IDENTITY strategy, which is common for MySQL and H2).
  • Getters and setters are required for JPA to access and modify the entity's fields.

Step 4: Create the Repository

A repository provides an interface for interacting with the database. Spring Data JPA automatically implements this interface based on the method names you define.

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

public interface ProductRepository extends JpaRepository<Product, Long> {
    // You can add custom query methods here
}

Explanation:

  • JpaRepository<Product, Long>: Extends the JpaRepository interface, providing basic CRUD operations for the Product entity, using Long as the primary key type.
  • Spring Data JPA automatically generates implementations for methods like save(), findById(), findAll(), deleteById(), etc.
  • You can define custom query methods by adding methods with specific names (e.g., findByPriceGreaterThan()).

Step 5: Use the Repository in a Service

Create a service class to use the repository and perform business logic.

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

import java.util.List;

@Service
public class ProductService {

    @Autowired
    private ProductRepository productRepository;

    public Product saveProduct(Product product) {
        return productRepository.save(product);
    }

    public Product findProductById(Long id) {
        return productRepository.findById(id).orElse(null); // Handle not found case
    }

    public List<Product> findAllProducts() {
        return productRepository.findAll();
    }

    public void deleteProductById(Long id) {
        productRepository.deleteById(id);
    }
}

Explanation:

  • @Service: Marks the class as a Spring service component.
  • @Autowired: Injects the ProductRepository into the service.
  • The methods demonstrate how to use the repository's methods to perform CRUD operations. orElse(null) handles the case where findById() doesn't find a product with the given ID.

Step 6: Expose the Service through a Controller

Create a REST controller to expose the service's functionality.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/products")
public class ProductController {

    @Autowired
    private ProductService productService;

    @PostMapping
    public Product createProduct(@RequestBody Product product) {
        return productService.saveProduct(product);
    }

    @GetMapping("/{id}")
    public Product getProductById(@PathVariable Long id) {
        return productService.findProductById(id);
    }

    @GetMapping
    public List<Product> getAllProducts() {
        return productService.findAllProducts();
    }

    @DeleteMapping("/{id}")
    public void deleteProduct(@PathVariable Long id) {
        productService.deleteProductById(id);
    }
}

Explanation:

  • @RestController: Marks the class as a REST controller.