Module 4 - Data Persistence with Spring Boot Data JPA
Lecture: Understanding Spring Data JPA
1. Introduction to Spring Data JPA
Spring Data JPA is a powerful component of the Spring ecosystem that significantly simplifies database access in Spring applications. It provides a high-level abstraction over Java Persistence API (JPA) and reduces the amount of boilerplate code needed to implement data access layers.
Key Benefits of Spring Data JPA:
- Reduced Boilerplate Code: Spring Data JPA eliminates the need to write repetitive CRUD (Create, Read, Update, Delete) operations.
- Query Methods: Automatically generates database queries based on method names.
- Repository Pattern Implementation: Provides an implementation of the repository pattern out of the box.
- Pagination and Sorting Support: Easily add pagination and sorting capabilities to your queries.
- Auditing: Automatically tracks who created or modified entities and when.
- Integration with Spring Boot: Auto-configuration simplifies setup with minimal configuration.
2. The JPA Architecture
Java Persistence API (JPA) is a specification that describes the management of relational data in Java applications. It serves as a bridge between object-oriented domain models and relational database systems.
Core Components of JPA:
- Entities: Java classes that map to database tables.
- EntityManager: Interface for interacting with the persistence context.
- Persistence Context: Manages entity instances and their lifecycle.
- JPQL (Java Persistence Query Language): SQL-like language for querying entities.
- Criteria API: Type-safe way to construct queries programmatically.
- Persistence Unit: Defines a set of entity classes managed by an EntityManager.
Spring Data JPA builds on top of JPA, providing additional features and simplifications.
3. Understanding ORM (Object-Relational Mapping)
ORM is a technique that maps objects to database tables and vice versa. It bridges the gap between object-oriented programming and relational databases.
ORM Concepts:
- Object-Table Mapping: Maps Java classes to database tables.
- Property-Column Mapping: Maps class fields to table columns.
- Relationship Mapping: Maps relationships between classes to relationships between tables.
- Inheritance Mapping: Maps class inheritance hierarchies to table structures.
Hibernate:
Hibernate is the most popular JPA implementation and the default ORM used by Spring Data JPA. It provides:
- An implementation of the JPA specification
- Additional features beyond what JPA specifies
- Efficient caching mechanisms
- Various mapping strategies for complex object graphs
4. Setting Up Spring Data JPA
To use Spring Data JPA in a Spring Boot application, you need to add the appropriate dependencies and configuration.
Dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Plus a database driver (e.g., H2, MySQL, PostgreSQL):
<!-- For H2 in-memory database -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- For PostgreSQL -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
Configuration in application.properties:
For H2 (in-memory database):
# H2 Database Configuration
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
For PostgreSQL:
# PostgreSQL Configuration
spring.datasource.url=jdbc:postgresql://localhost:5432/bookdb
spring.datasource.username=postgres
spring.datasource.password=password
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
5. Creating JPA Entities
JPA entities are Java classes that map to database tables. They are annotated with @Entity
and typically have a primary key annotated with @Id
.
import jakarta.persistence.*;
import java.time.LocalDate;
@Entity
@Table(name = "books")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String title;
@Column(length = 200)
private String author;
@Column(length = 20)
private String isbn;
@Column(name = "publication_date")
private LocalDate publicationDate;
@Column(columnDefinition = "TEXT")
private String description;
@Column(name = "page_count")
private Integer pageCount;
// Constructors, getters, setters, equals, hashCode, toString
}
Important JPA Annotations:
- @Entity: Marks a class as a JPA entity.
- @Table: Specifies the database table name.
- @Id: Marks a field as the primary key.
- @GeneratedValue: Defines how the primary key should be generated.
- @Column: Customizes the mapping between a field and a database column.
- @Temporal: Specifies the type of a date/time field.
- @Enumerated: Specifies how an enum should be persisted.
- @Transient: Marks a field as not persistent.
- @Embeddable and @Embedded: Support for embedded objects.
6. Relationship Mappings
JPA supports various types of relationships between entities:
One-to-One Relationship:
@Entity
public class BookDetail {
@Id
private Long id;
private String publisher;
private String language;
@OneToOne
@MapsId
private Book book;
// Constructors, getters, setters
}
@Entity
public class Book {
// Other fields
@OneToOne(mappedBy = "book", cascade = CascadeType.ALL)
private BookDetail bookDetail;
// Constructors, getters, setters
}
One-to-Many/Many-to-One Relationship:
@Entity
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
private List<Book> books = new ArrayList<>();
// Constructors, getters, setters
}
@Entity
public class Book {
// Other fields
@ManyToOne
@JoinColumn(name = "author_id")
private Author author;
// Constructors, getters, setters
}
Many-to-Many Relationship:
@Entity
public class Book {
// Other fields
@ManyToMany
@JoinTable(
name = "book_category",
joinColumns = @JoinColumn(name = "book_id"),
inverseJoinColumns = @JoinColumn(name = "category_id")
)
private Set<Category> categories = new HashSet<>();
// Constructors, getters, setters
}
@Entity
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany(mappedBy = "categories")
private Set<Book> books = new HashSet<>();
// Constructors, getters, setters
}
7. Spring Data Repositories
Spring Data JPA provides repository interfaces that abstract away the data access logic:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface BookRepository extends JpaRepository<Book, Long> {
// Spring Data JPA automatically implements methods from JpaRepository
// No implementation needed for basic CRUD operations
}
By extending JpaRepository<T, ID>
, the repository automatically gets methods for:
- save(entity)
- findById(id)
- findAll()
- count()
- delete(entity)
- existsById(id)
- And many more…
8. Creating Custom Query Methods
Spring Data JPA can generate query implementations based on method names:
public interface BookRepository extends JpaRepository<Book, Long> {
// Find books by title
List<Book> findByTitle(String title);
// Find books by author containing a string
List<Book> findByAuthorContaining(String authorName);
// Find books by publication date after a certain date
List<Book> findByPublicationDateAfter(LocalDate date);
// Find books by title and author
List<Book> findByTitleAndAuthor(String title, String author);
// Find books by page count range
List<Book> findByPageCountBetween(int min, int max);
// Count books by author
long countByAuthor(String author);
// Find top 3 books by page count in descending order
List<Book> findTop3ByOrderByPageCountDesc();
}
9. Defining Custom Queries with @Query
For more complex queries, you can use the @Query
annotation:
public interface BookRepository extends JpaRepository<Book, Long> {
// Using JPQL (Java Persistence Query Language)
@Query("SELECT b FROM Book b WHERE b.pageCount > ?1 AND b.publicationDate > ?2")
List<Book> findLongBooksPublishedAfterDate(int pageCount, LocalDate date);
// Using named parameters
@Query("SELECT b FROM Book b WHERE b.title LIKE %:keyword% OR b.author LIKE %:keyword%")
List<Book> searchBooks(@Param("keyword") String keyword);
// Native SQL query
@Query(value = "SELECT * FROM books WHERE EXTRACT(YEAR FROM publication_date) = ?1",
nativeQuery = true)
List<Book> findBooksPublishedInYear(int year);
}
10. Pagination and Sorting
Spring Data JPA provides built-in support for pagination and sorting:
// In the repository
Page<Book> findByAuthor(String author, Pageable pageable);
// In the service
public Page<Book> findBooksByAuthor(String author, int page, int size) {
Pageable pageable = PageRequest.of(page, size, Sort.by("title").ascending());
return bookRepository.findByAuthor(author, pageable);
}
11. Transaction Management
Spring provides declarative transaction management using the @Transactional
annotation:
@Service
public class BookService {
private final BookRepository bookRepository;
@Autowired
public BookService(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
@Transactional
public Book createBook(Book book) {
return bookRepository.save(book);
}
@Transactional(readOnly = true)
public List<Book> findAllBooks() {
return bookRepository.findAll();
}
@Transactional
public Book updateBook(Long id, Book bookDetails) {
Book book = bookRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Book not found with id: " + id));
book.setTitle(bookDetails.getTitle());
book.setAuthor(bookDetails.getAuthor());
book.setIsbn(bookDetails.getIsbn());
book.setPublicationDate(bookDetails.getPublicationDate());
book.setDescription(bookDetails.getDescription());
book.setPageCount(bookDetails.getPageCount());
return bookRepository.save(book);
}
@Transactional
public void deleteBook(Long id) {
Book book = bookRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Book not found with id: " + id));
bookRepository.delete(book);
}
}
12. Data Validation
Spring Data JPA works well with Bean Validation (JSR-380):
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.time.LocalDate;
@Entity
@Table(name = "books")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "Title is required")
@Size(max = 100, message = "Title cannot exceed 100 characters")
private String title;
@Size(max = 200, message = "Author name cannot exceed 200 characters")
private String author;
@Pattern(regexp = "^\\d{10}|\\d{13}$", message = "ISBN must be 10 or 13 digits")
private String isbn;
@Past(message = "Publication date must be in the past")
private LocalDate publicationDate;
private String description;
@Min(value = 1, message = "Page count must be at least 1")
private Integer pageCount;
// Constructors, getters, setters
}
13. Auditing with Spring Data JPA
Spring Data JPA supports automatic auditing of entities:
@Configuration
@EnableJpaAuditing
public class AuditingConfig {
@Bean
public AuditorAware<String> auditorProvider() {
return () -> Optional.of("SYSTEM");
// In a real application, you would return the current user's ID
}
}
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class Auditable {
@CreatedDate
@Column(name = "created_date", nullable = false, updatable = false)
private LocalDateTime createdDate;
@LastModifiedDate
@Column(name = "last_modified_date")
private LocalDateTime lastModifiedDate;
@CreatedBy
@Column(name = "created_by", nullable = false, updatable = false)
private String createdBy;
@LastModifiedBy
@Column(name = "last_modified_by")
private String lastModifiedBy;
// Getters and setters
}
@Entity
@Table(name = "books")
public class Book extends Auditable {
// Other fields and methods
}
14. Database Initialization and Migration
Spring Boot provides several ways to initialize and evolve your database schema:
Using schema.sql and data.sql:
Spring Boot automatically executes schema.sql
(for schema creation) and data.sql
(for data initialization) if they are present in the classpath.
Using Hibernate DDL Auto:
# Options: create, create-drop, validate, update, none
spring.jpa.hibernate.ddl-auto=update
Using Flyway or Liquibase for Database Migration:
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
With Flyway, you create migration scripts in the resources/db/migration
directory with names like V1__Create_books_table.sql
:
CREATE TABLE books (
id SERIAL PRIMARY KEY,
title VARCHAR(100) NOT NULL,
author VARCHAR(200),
isbn VARCHAR(20),
publication_date DATE,
description TEXT,
page_count INTEGER,
created_date TIMESTAMP NOT NULL,
last_modified_date TIMESTAMP,
created_by VARCHAR(50) NOT NULL,
last_modified_by VARCHAR(50)
);
15. Working with Multiple Databases
Spring Boot supports configuring multiple data sources:
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = "com.example.bookcatalog.repository.primary",
entityManagerFactoryRef = "primaryEntityManagerFactory",
transactionManagerRef = "primaryTransactionManager"
)
public class PrimaryDataSourceConfig {
@Primary
@Bean
@ConfigurationProperties("spring.datasource.primary")
public DataSourceProperties primaryDataSourceProperties() {
return new DataSourceProperties();
}
@Primary
@Bean
public DataSource primaryDataSource() {
return primaryDataSourceProperties().initializeDataSourceBuilder().build();
}
@Primary
@Bean
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(
EntityManagerFactoryBuilder builder) {
return builder
.dataSource(primaryDataSource())
.packages("com.example.bookcatalog.model.primary")
.persistenceUnit("primary")
.build();
}
@Primary
@Bean
public PlatformTransactionManager primaryTransactionManager(
@Qualifier("primaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
16. Performance Optimization
Key performance considerations when using Spring Data JPA:
- N+1 Query Problem: Use fetch joins or
@EntityGraph
to avoid N+1 queries:
public interface BookRepository extends JpaRepository<Book, Long> {
@EntityGraph(attributePaths = {"author", "categories"})
List<Book> findAll();
@Query("SELECT b FROM Book b JOIN FETCH b.author WHERE b.id = ?1")
Optional<Book> findByIdWithAuthor(Long id);
}
- Batch Processing: Use
@Modifying
for batch updates and deletes:
@Modifying
@Query("UPDATE Book b SET b.pageCount = b.pageCount + 10 WHERE b.author = ?1")
int updatePageCountForAuthor(String author);
- Caching: Use JPA’s second-level cache or Spring’s caching abstraction:
@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Book {
// Entity implementation
}
@Service
public class BookService {
@Cacheable("books")
public Book findBookById(Long id) {
return bookRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Book not found with id: " + id));
}
}
Quiz: Spring Data JPA
-
What is the primary benefit of using Spring Data JPA? a) It completely replaces SQL with a new query language b) It reduces the amount of boilerplate code needed for data access c) It eliminates the need for database design d) It automatically creates database schemas without configuration
-
Which annotation marks a Java class as a JPA entity? a) @Record b) @Table c) @Entity d) @Persistent
-
What does the @Id annotation signify in JPA? a) It marks a field as indexed for faster queries b) It designates a field as the primary key c) It indicates a field should be encrypted d) It marks a field as required
-
Which of the following is NOT a valid relationship annotation in JPA? a) @OneToOne b) @OneToMany c) @ManyToOne d) @OneToFew
-
What does the following repository method signature do?
List<Book> findByTitleContainingIgnoreCase(String title);
a) Finds books with titles that are exactly equal to the input b) Finds books with titles that contain the input string (case-insensitive) c) Finds books with titles that start with the input string d) Finds books with titles that match a regex pattern
-
In Spring Data JPA, which of the following is automatically provided when you extend JpaRepository? a) User authentication methods b) Logging of all database operations c) Basic CRUD operations like save() and findById() d) Database connection pooling
-
What is the purpose of the @Transactional annotation? a) To encrypt data in transit b) To mark a method as requiring transaction management c) To ensure data is cached d) To validate data before persistence
-
Which configuration property would you use to have Hibernate automatically update your database schema? a) spring.jpa.hibernate.ddl-auto=update b) spring.jpa.hibernate.schema=auto-update c) spring.jpa.auto-ddl=true d) spring.hibernate.schema=create
-
Which tool would you use for database schema migration in a Spring Boot application? a) Spring Migrator b) Flyway or Liquibase c) JPA Evolver d) Hibernate Migrator
-
What is the N+1 query problem in JPA? a) When N entities are loaded but only 1 is used b) When 1 entity is loaded but N properties are accessed c) When loading a collection of N entities requires N+1 database queries d) When N queries are executed instead of using a single batch operation
Quiz Answers
-
What is the primary benefit of using Spring Data JPA? Answer: b) It reduces the amount of boilerplate code needed for data access
-
Which annotation marks a Java class as a JPA entity? Answer: c) @Entity
-
What does the @Id annotation signify in JPA? Answer: b) It designates a field as the primary key
-
Which of the following is NOT a valid relationship annotation in JPA? Answer: d) @OneToFew
-
What does the following repository method signature do?
List<Book> findByTitleContainingIgnoreCase(String title);
Answer: b) Finds books with titles that contain the input string (case-insensitive)
-
In Spring Data JPA, which of the following is automatically provided when you extend JpaRepository? Answer: c) Basic CRUD operations like save() and findById()
-
What is the purpose of the @Transactional annotation? Answer: b) To mark a method as requiring transaction management
-
Which configuration property would you use to have Hibernate automatically update your database schema? Answer: a) spring.jpa.hibernate.ddl-auto=update
-
Which tool would you use for database schema migration in a Spring Boot application? Answer: b) Flyway or Liquibase
-
What is the N+1 query problem in JPA? Answer: c) When loading a collection of N entities requires N+1 database queries
By Wahid Hamdi