In Week 3, we built REST APIs with controllers, DTOs, validation, and CRUD operations.
Now, in Week 4, it’s time to connect your APIs to a real database using Spring Data JPA and Hibernate, and understand entity relationships, queries, and performance best practices.
What is JPA and Hibernate?
JPA (Java Persistence API) is a standard interface for working with relational databases in Java.
Hibernate is the most popular JPA implementation, providing extra features like caching, lazy loading, and advanced queries.
Think of JPA as the contract and Hibernate as the implementation.
Why Use Spring Data JPA?
Spring Data JPA simplifies database access by:
- Auto-generating queries
- Handling boilerplate CRUD code
- Supporting pagination and sorting
- Integrating seamlessly with Spring Boot
Step 1: Add Dependencies
In your 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>
<!-- Optional: MySQL/PostgreSQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>Start with H2 (in-memory DB) for development. Switch to MySQL/PostgreSQL for production.
Step 2: Configure Database
application.yml example:
spring:
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: sa
password:
jpa:
hibernate:
ddl-auto: update
show-sql: true
ddl-auto: updateautomatically creates tables based on your entities. Usevalidatein production.
Step 3: Create Entities & Relationships
User Entity
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Order> orders = new ArrayList<>();
// Getters & Setters
}Order Entity
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String product;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
// Getters & Setters
}Relationships Explained
| Type | Use Case |
| ---------- | ----------------------------- |
| OneToOne | User profile, unique relation |
| OneToMany | User → Orders |
| ManyToMany | Student → Course |
Step 4: Repository Layer
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}Why this is powerful:
- Spring auto-generates queries
- Supports custom query methods based on naming conventions
Example:
List<User> findByNameContaining(String name);Step 5: CRUD with Service Layer
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User saveUser(User user) {
return userRepository.save(user);
}
public List<User> getAllUsers() {
return userRepository.findAll();
}
public Optional<User> getUserById(Long id) {
return userRepository.findById(id);
}
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
}Following Service → Repository → Controller keeps your architecture clean and scalable.
Step 6: Pagination & Sorting
@GetMapping
public Page<User> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size
) {
Pageable pageable = PageRequest.of(page, size, Sort.by("name").ascending());
return userRepository.findAll(pageable);
}Benefits:
- Handles large datasets efficiently
- Professional APIs always implement pagination
Step 7: JPQL vs Native Queries
JPQL (Object-Oriented Query)
@Query("SELECT u FROM User u WHERE u.name LIKE %:name%")
List<User> searchByName(@Param("name") String name);Native SQL
@Query(value = "SELECT * FROM users WHERE name LIKE %:name%", nativeQuery = true)
List<User> searchByNameNative(@Param("name") String name);Use JPQL for portability; Native SQL for complex or DB-specific queries.
Step 8: Performance Tips
- Use
fetch = FetchType.LAZYfor large relations - Add indexes for frequently searched columns
- Avoid
N+1 queries(useJOIN FETCH) - Enable query caching if needed
Example:
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
@Cacheable
private List<Order> orders;Week 4 Recap
✅ JPA + Hibernate basics
✅ Entity relationships
✅ Repository layer
✅ CRUD services
✅ Pagination & sorting
✅ JPQL vs Native queries
✅ Performance best practices
What’s Next? (Week 5)
👉 Spring Boot Security: JWT Authentication Step-by-Step
We’ll cover:
- Spring Security architecture
- JWT login flow
- Role-based access
- Password hashing
- Protecting REST APIs
📌 Action Items
✔ Create User + Order entities
✔ Implement OneToMany relationship
✔ Test CRUD operations in Postman
✔ Push code to GitHub