3 de setembro de 2026 • Java

19 - Spring Data JPA

1. What is JDBC?

JDBC (Java Database Connectivity) is the standard Java API for communicating with relational databases.

It provides a low-level API that allows a Java application to:

  • Open database connections
  • Execute SQL statements
  • Read query results
  • Insert, update, and delete records
  • Manage transactions

Basic JDBC Example

String sql = "SELECT id, name, email FROM users";

try (Connection connection = dataSource.getConnection();
     PreparedStatement statement = connection.prepareStatement(sql);
     ResultSet resultSet = statement.executeQuery()) {

    while (resultSet.next()) {
        Long id = resultSet.getLong("id");
        String name = resultSet.getString("name");
        String email = resultSet.getString("email");

        System.out.println(id + " - " + name + " - " + email);
    }
}

With JDBC, the developer is responsible for many low-level details:

Java Application
      |
      v
   JDBC API
      |
      v
Database Driver
      |
      v
   Database

Problems with JDBC

JDBC is powerful, but applications can become repetitive:

Connection
PreparedStatement
ResultSet
SQL
ResultSet mapping
Exception handling
Connection management

For example, every query requires manually converting database rows into Java objects.

This is one of the problems that ORM frameworks attempt to solve.


2. What is Spring JDBC?

Spring JDBC is a Spring abstraction over JDBC that simplifies database access.

The main component is JdbcTemplate.

Instead of manually managing connections and statements:

jdbcTemplate.query(
    "SELECT id, name, email FROM users",
    (rs, rowNum) -> new User(
        rs.getLong("id"),
        rs.getString("name"),
        rs.getString("email")
    )
);

Spring manages much of the boilerplate.

JdbcTemplate

Typical architecture:

Application
     |
     v
JdbcTemplate
     |
     v
JDBC
     |
     v
Database Driver
     |
     v
Database

Spring JDBC is still fundamentally SQL-based.

You write SQL:

SELECT * FROM users WHERE email = ?

and Spring helps execute it.


3. What is JPA?

JPA (Java Persistence API) is a Java specification for object-relational persistence.

JPA defines concepts such as:

  • Entities
  • Entity lifecycle
  • Relationships
  • Persistence context
  • EntityManager
  • JPQL
  • Transactions
  • Mapping Java objects to database tables

JPA itself is not an implementation.

It defines a standard API and behavior.

Examples of JPA implementations include:

  • Hibernate
  • EclipseLink
  • OpenJPA

A common misconception is:

“JPA is Hibernate.”

They are not the same thing.

A better analogy is:

JPA
 |
 | specification
 v
Hibernate
 |
 | implementation
 v
Database

4. What is ORM?

ORM (Object-Relational Mapping) is a technique for mapping objects in an object-oriented programming language to relational database structures.

For example:

Java

public class User {

    private Long id;
    private String name;
    private String email;
}

Database

CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    name VARCHAR(255),
    email VARCHAR(255)
);

ORM creates a conceptual mapping:

Java Object              Database
-----------              --------
User              --->   users
id                --->   id
name              --->   name
email             --->   email

The goal is to reduce the amount of SQL and manual object mapping developers need to write.


5. What is Hibernate?

Hibernate is an ORM framework and one of the most popular implementations of JPA.

Hibernate is responsible for actually performing persistence operations.

For example:

entityManager.persist(user);

The developer doesn’t explicitly write:

INSERT INTO users ...

Hibernate can generate the SQL required to persist the entity.

Conceptually:

Java Entity
    |
    v
JPA API
    |
    v
Hibernate
    |
    v
JDBC
    |
    v
Database

Spring Boot commonly uses Hibernate as the JPA provider.


6. Spring Data JPA

Spring Data JPA is a Spring project that makes it easier to build repositories using JPA.

It sits at a higher abstraction level than JPA.

Spring Data JPA
       |
       v
      JPA
       |
       v
   Hibernate
       |
       v
      JDBC
       |
       v
    Database

Spring Data JPA provides features such as:

  • Repository interfaces
  • CRUD operations
  • Query methods
  • Pagination
  • Sorting
  • @Query
  • Integration with Spring transactions
  • Reduced boilerplate

Instead of implementing:

save()
findById()
findAll()
delete()

you can simply extend a repository interface.

public interface UserRepository
        extends JpaRepository<User, Long> {
}

Spring Data generates the implementation automatically.


7. JDBC vs Spring JDBC vs JPA vs Spring Data JPA

TechnologyMain ResponsibilitySQL Required?Abstraction
JDBCDirect database accessYesLow
Spring JDBCSimplify JDBCYesLow/Medium
JPAPersistence specificationUsually noHigh
HibernateJPA/ORM implementationUsually noHigh
Spring Data JPASimplify JPA repositoriesUsually noVery High

A useful way to remember it:

JDBC

Spring JDBC

JPA

Hibernate

Spring Data JPA

However, this is not a strict replacement hierarchy.

Spring Data JPA uses JPA, and Hibernate is commonly the JPA provider underneath it.


8. JPA Architecture

A typical Spring Boot application using Spring Data JPA looks like this:

+-----------------------------+
|       REST Controller       |
+-----------------------------+
              |
              v
+-----------------------------+
|       Service Layer         |
+-----------------------------+
              |
              v
+-----------------------------+
|    Spring Data Repository   |
+-----------------------------+
              |
              v
+-----------------------------+
|            JPA              |
|        EntityManager        |
+-----------------------------+
              |
              v
+-----------------------------+
|          Hibernate          |
|        JPA Provider         |
+-----------------------------+
              |
              v
+-----------------------------+
|            JDBC             |
+-----------------------------+
              |
              v
+-----------------------------+
|          Database           |
+-----------------------------+

Important components

Entity

Represents a persistent object.

@Entity
public class User {
}

EntityManager

JPA’s main API for managing entities.

@PersistenceContext
private EntityManager entityManager;

Persistence Context

A managed collection of entity instances associated with an EntityManager.

It is one of the most important concepts in JPA.

Hibernate

The implementation that performs ORM and generates SQL.

Database

The actual persistence layer.


9. Persistence Context

The Persistence Context is a central concept in JPA.

It acts as a first-level cache and keeps track of managed entities.

For example:

User user = entityManager.find(User.class, 1L);

The entity becomes managed by the persistence context.

If we modify it:

user.setName("John");

we don’t necessarily need:

entityManager.update(user);

Instead, Hibernate can detect the change and synchronize it with the database when the transaction is committed.

This mechanism is called:

Dirty Checking

Database
   |
   v
EntityManager
   |
   v
Persistence Context
   |
   v
Managed Entity
   |
   | change
   v
Dirty Checking
   |
   v
SQL UPDATE

10. Entity Lifecycle

JPA entities have different lifecycle states.

The four main states are:

Transient
    |
    | persist()
    v
Persistent / Managed
    |
    | detach()
    v
Detached
    |
    | remove()
    v
Removed

10.1 Transient

An object is transient when it has been created but is not associated with the persistence context.

User user = new User();

user.setName("John");
user.setEmail("john@example.com");

At this point:

Java Object
    |
    X
Database

Nothing has been persisted.


10.2 Managed

An entity becomes managed when it is associated with a persistence context.

For example:

entityManager.persist(user);

Now:

User
 |
 v
Persistence Context

Hibernate tracks changes to the entity.


10.3 Detached

An entity becomes detached when it is no longer associated with the persistence context.

For example:

entityManager.detach(user);

Changes to the detached entity are not automatically synchronized with the database.


10.4 Removed

An entity is marked for deletion:

entityManager.remove(user);

The actual SQL DELETE is normally executed when the persistence context is flushed.


11. Understanding JPA Internals

Consider:

@Transactional
public void updateUser(Long id) {

    User user = userRepository.findById(id)
            .orElseThrow();

    user.setName("John");
}

There is no explicit:

userRepository.save(user);

Yet the database can be updated.

Why?

Step 1

The transaction starts.

Transaction START

Step 2

JPA loads the entity.

Database
   |
   v
Hibernate
   |
   v
Persistence Context
   |
   v
User

Step 3

The entity is managed.

user.setName("John");

Step 4

Hibernate detects the change through dirty checking.

Step 5

The transaction commits.

Hibernate generates:

UPDATE users
SET name = 'John'
WHERE id = ?;

Step 6

Transaction completes.

Transaction COMMIT

This is one of the most important differences between JPA and simple JDBC-style programming.


12. Setting Up a Spring Data JPA Application

For our example, imagine a simple application called:

Online Store

The application contains:

Customer
Order
Product

We will use:

Spring Boot
Spring Data JPA
Hibernate
PostgreSQL

A typical dependency is:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

And a PostgreSQL driver:

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <scope>runtime</scope>
</dependency>

13. Creating Entities

An entity represents a persistent object.

@Entity
@Table(name = "customers")
public class Customer {

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

    private String name;

    private String email;

    // constructors
    // getters
    // setters
}

This creates a conceptual mapping:

Customer
   |
   v
customers table

14. JPA Annotations

Some of the most common annotations are:

@Entity

Marks a class as a JPA entity.

@Entity
public class Customer {
}

@Table

Defines the database table.

@Table(name = "customers")

@Id

Defines the primary key.

@Id
private Long id;

@GeneratedValue

Defines how the ID is generated.

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

@Column

Customizes column mapping.

@Column(name = "customer_email", nullable = false)
private String email;

@Transient

Marks a field that should not be persisted.

@Transient
private String temporaryValue;

@Enumerated

Maps Java enums to database values.

Prefer:

@Enumerated(EnumType.STRING)
private CustomerStatus status;

instead of:

@Enumerated(EnumType.ORDINAL)

STRING is generally safer because changing enum order won’t change the stored meaning.


15. Spring Data JPA Repositories

Repositories provide the data-access abstraction.

public interface CustomerRepository
        extends JpaRepository<Customer, Long> {
}

There is no implementation class.

Spring Data generates it.

Conceptually:

CustomerRepository
        |
        v
Spring Data JPA
        |
        v
Generated Implementation
        |
        v
JPA / Hibernate

16. CrudRepository

CrudRepository provides basic CRUD operations.

public interface CustomerRepository
        extends CrudRepository<Customer, Long> {
}

Common methods:

save()
findById()
findAll()
existsById()
count()
deleteById()
delete()
deleteAll()

Example:

Customer customer = new Customer();

customer.setName("John");
customer.setEmail("john@example.com");

customerRepository.save(customer);

17. PagingAndSortingRepository

PagingAndSortingRepository provides pagination and sorting capabilities.

For example:

Page<Customer> customers =
        repository.findAll(
            PageRequest.of(0, 20)
        );

This means:

Page 0
20 records

Sorting:

PageRequest.of(
    0,
    20,
    Sort.by("name").ascending()
);

18. JpaRepository

JpaRepository provides JPA-specific repository functionality and is the most commonly used repository interface in Spring Boot applications.

public interface CustomerRepository
        extends JpaRepository<Customer, Long> {
}

It provides CRUD functionality plus features such as:

  • Pagination
  • Sorting
  • Batch-related operations
  • JPA-specific behavior

In many applications:

JpaRepository

is the default choice.


19. Repository Hierarchy

Conceptually:

Repository
    |
    v
CrudRepository
    |
    v
PagingAndSortingRepository
    |
    v
JpaRepository

The exact inheritance structure can vary between Spring Data versions, but the important idea is that more specialized repositories provide additional capabilities.


20. Inbuilt CRUD Methods

Spring Data provides many methods without requiring implementation.

Create

customerRepository.save(customer);

Read

customerRepository.findById(id);
customerRepository.findAll();

Update

customer.setName("New Name");

customerRepository.save(customer);

Delete

customerRepository.deleteById(id);

21. Query Methods

One of the most useful Spring Data JPA features is derived query methods.

Spring Data can create queries based on method names.

Example:

List<Customer> findByName(String name);

Spring Data interprets the method name and generates the appropriate query.

Another example:

Optional<Customer> findByEmail(String email);

Conceptually:

SELECT *
FROM customers
WHERE email = ?;

22. More Query Method Examples

Find by two fields

Optional<Customer> findByNameAndEmail(
    String name,
    String email
);

Find by partial text

List<Customer> findByNameContaining(String name);

Find by starting text

List<Customer> findByNameStartingWith(String prefix);

Greater than

List<Product> findByPriceGreaterThan(BigDecimal price);

Less than

List<Product> findByPriceLessThan(BigDecimal price);

Ordering

List<Customer> findByNameOrderByNameAsc();

Multiple conditions

List<Customer> findByNameContainingAndEmailContaining(
    String name,
    String email
);

23. When Query Methods Become a Problem

Derived query methods are convenient:

findByNameAndEmailAndStatusAndCreatedAtAfter(...)

But extremely complex method names can become difficult to read and maintain.

For example:

findByNameContainingAndStatusAndCreatedAtBetweenAndEmailContainingOrderByNameAsc(...)

At some point, @Query or a more advanced query mechanism becomes more appropriate.


24. @Query

The @Query annotation allows us to define queries explicitly.

Example:

@Query("""
    SELECT c
    FROM Customer c
    WHERE c.email = :email
""")
Optional<Customer> findCustomerByEmail(
    @Param("email") String email
);

This uses JPQL, not SQL.


25. JPQL vs SQL

SQL operates primarily on tables:

SELECT *
FROM customers
WHERE email = ?;

JPQL operates on entities:

SELECT c
FROM Customer c
WHERE c.email = :email

The important difference is:

SQL  -> Database tables and columns

JPQL -> Entities and their fields

Hibernate translates JPQL into database-specific SQL.


26. Native Queries

Sometimes you need database-specific SQL.

You can use:

@Query(
    value = """
        SELECT *
        FROM customers
        WHERE email = :email
    """,
    nativeQuery = true
)
Optional<Customer> findByEmailNative(
    @Param("email") String email
);

This executes SQL directly.

Use native queries carefully because they reduce database portability.


27. Modifying Queries

For UPDATE and DELETE queries, use @Modifying.

Example:

@Modifying
@Query("""
    UPDATE Customer c
    SET c.status = :status
    WHERE c.id = :id
""")
int updateStatus(
    @Param("id") Long id,
    @Param("status") CustomerStatus status
);

Usually this operation should execute inside a transaction:

@Transactional

28. JPA Relationships

Real-world applications rarely consist of isolated entities.

Entities usually have relationships.

The four major JPA relationships are:

One-to-One
One-to-Many
Many-to-One
Many-to-Many

29. One-to-One

A One-to-One relationship means:

One entity is associated with one other entity.

Example:

Customer
   |
   | 1
   |
   | 1
   v
CustomerProfile

Example:

@Entity
public class Customer {

    @Id
    @GeneratedValue
    private Long id;

    @OneToOne
    private CustomerProfile profile;
}

30. One-to-Many

One entity is associated with multiple entities.

Example:

Customer
   |
   | 1
   |
   +---- Order
   |
   +---- Order
   |
   +---- Order
@OneToMany
private List<Order> orders;

A customer can have many orders.


31. Many-to-One

Many entities belong to one entity.

For example:

Order
   |
   +---- Customer
   |
   +---- Customer
   |
   +---- Customer

Actually, the relationship is:

Customer 1 <------ N Orders

The Order entity commonly owns the foreign key:

@ManyToOne
@JoinColumn(name = "customer_id")
private Customer customer;

This is one of the most common relationships in real applications.


32. One-to-Many + Many-to-One

These two annotations are frequently used together.

Customer

@OneToMany(mappedBy = "customer")
private List<Order> orders;

Order

@ManyToOne
@JoinColumn(name = "customer_id")
private Customer customer;

Database:

customers
----------------
id
name
email


orders
----------------
id
customer_id
total

The foreign key is stored in:

orders.customer_id

33. Many-to-Many

A Many-to-Many relationship means:

Many records can be associated with many other records.

Example:

Student              Course

Student A ---------- Course A
     |  \             |
     |   \------------ Course B
     |
Student B ---------- Course B

A common implementation uses a join table:

students
courses
student_courses

Example:

@ManyToMany
@JoinTable(
    name = "student_courses",
    joinColumns = @JoinColumn(name = "student_id"),
    inverseJoinColumns = @JoinColumn(name = "course_id")
)
private Set<Course> courses;

Database:

students
---------
id
name


courses
---------
id
name


student_courses
---------------
student_id
course_id

34. Owning Side of a Relationship

One of the most important concepts in JPA relationships is the owning side.

The owning side is responsible for managing the relationship mapping, particularly the foreign key or join table.

For example:

@ManyToOne
@JoinColumn(name = "customer_id")
private Customer customer;

Here, Order is the owning side.

The Customer side can use:

@OneToMany(mappedBy = "customer")
private List<Order> orders;

The mappedBy tells JPA:

The other entity owns this relationship.


35. Cascade Operations

Cascade controls whether operations performed on one entity should propagate to related entities.

Example:

@OneToMany(
    mappedBy = "customer",
    cascade = CascadeType.ALL
)
private List<Order> orders;

Possible cascade types include:

PERSIST
MERGE
REMOVE
REFRESH
DETACH
ALL

Be careful with:

CascadeType.ALL

especially when it includes:

REMOVE

For example, deleting a customer could potentially delete all associated orders.

Cascade should be chosen based on the actual ownership/lifecycle relationship between entities.


36. Fetch Types

JPA relationships can use different fetching strategies.

EAGER

Load the relationship immediately.

@ManyToOne(fetch = FetchType.EAGER)

LAZY

Load the relationship only when needed.

@ManyToOne(fetch = FetchType.LAZY)

In general, prefer LAZY loading where practical and explicitly fetch related data when you actually need it.

This helps avoid unnecessarily loading large object graphs.


37. The N+1 Query Problem

One of the most common JPA performance problems is the N+1 query problem.

Suppose:

List<Customer> customers =
    customerRepository.findAll();

Then:

for (Customer customer : customers) {
    customer.getOrders().size();
}

Depending on the mapping and query strategy, this can result in:

1 query -> load customers

N queries -> load orders for each customer

For 100 customers:

1 + 100 = 101 queries

This can seriously affect performance.

Solutions can include:

  • JOIN FETCH
  • Entity graphs
  • DTO projections
  • Carefully designed queries
  • Batch fetching

38. JOIN FETCH

Example:

@Query("""
    SELECT c
    FROM Customer c
    JOIN FETCH c.orders
    WHERE c.id = :id
""")
Optional<Customer> findCustomerWithOrders(
    @Param("id") Long id
);

This tells Hibernate to retrieve the customer and its orders in the same query.

The goal is to avoid unnecessary additional queries.


39. Pagination

Never assume that loading thousands or millions of database records into memory is acceptable.

Instead of:

List<Customer> customers =
    customerRepository.findAll();

use pagination:

Page<Customer> customers =
    customerRepository.findAll(
        PageRequest.of(0, 50)
    );

This requests:

Page: 0
Size: 50

Pagination is especially important for:

  • REST APIs
  • Admin screens
  • Search endpoints
  • Large tables
  • Reporting systems

40. Transactions and Spring Data JPA

Spring Data JPA works closely with Spring’s transaction management.

Example:

@Transactional
public void createOrder(Order order) {

    orderRepository.save(order);

    // additional database operations
}

The transaction ensures that the operations participate in the same transactional context.

For example:

BEGIN TRANSACTION

Save Order
Save Order Items
Update Inventory

COMMIT

If an appropriate exception causes rollback:

BEGIN TRANSACTION

Save Order
Save Order Items
Update Inventory

ERROR

ROLLBACK

41. Read-Only Transactions

For operations that only read data, you can use:

@Transactional(readOnly = true)

Example:

@Transactional(readOnly = true)
public Customer getCustomer(Long id) {
    return customerRepository.findById(id)
            .orElseThrow();
}

This communicates the intent that the transaction is read-only.

However, readOnly = true is primarily an optimization/hint rather than an absolute guarantee that no write can ever occur.


42. Entity Design Best Practices

Use a generated ID when appropriate

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

Avoid exposing entities directly from APIs

Prefer:

Entity
  |
  v
Service
  |
  v
DTO
  |
  v
Controller

instead of returning JPA entities directly.


Be careful with Lombok

Avoid blindly using:

@Data

on entities.

Generated equals(), hashCode(), and toString() can cause problems with:

  • Lazy relationships
  • Bidirectional relationships
  • Proxy objects
  • Recursive calls
  • Performance

43. Avoid Bidirectional Relationships Unless Needed

It is tempting to create:

Customer -> orders
Order -> customer

This can be useful, but bidirectional relationships increase complexity.

For example:

Customer
   |
   v
Orders
   |
   v
Customer
   |
   v
Orders
   |
   ...

This can cause problems with:

  • JSON serialization
  • toString()
  • equals()
  • hashCode()
  • Unexpected lazy loading

Use bidirectional relationships when they provide real value.


44. Keep the Service Layer in Control

A common architecture is:

Controller
    |
    v
Service
    |
    v
Repository
    |
    v
Database

Example:

@Service
public class CustomerService {

    private final CustomerRepository repository;

    public CustomerService(CustomerRepository repository) {
        this.repository = repository;
    }

    @Transactional
    public Customer create(Customer customer) {
        return repository.save(customer);
    }

    @Transactional(readOnly = true)
    public Customer findById(Long id) {
        return repository.findById(id)
                .orElseThrow();
    }
}

The service layer is a good place to define business transactions.


45. Don’t Treat JPA as Magic

JPA reduces boilerplate, but SQL still exists underneath.

When you write:

customerRepository.findByEmail(email);

the database still executes SQL.

Conceptually:

Repository
    |
    v
Spring Data
    |
    v
JPA
    |
    v
Hibernate
    |
    v
SQL
    |
    v
Database

Understanding the generated SQL is extremely important for performance troubleshooting.


46. Common JPA Problems

46.1 N+1 Queries

1 query + N queries

Solution:

  • Fetch joins
  • Entity graphs
  • DTO projections
  • Batch fetching

46.2 Loading Too Much Data

Avoid:

findAll()

when the table contains millions of rows.

Use:

Page<T>

or projections.


46.3 EAGER Relationships

Large object graphs can be loaded unintentionally.

Prefer lazy relationships where appropriate.


46.4 LazyInitializationException

This can happen when accessing a lazy relationship after the persistence context is no longer available.

Example:

customer.getOrders();

outside the appropriate persistence context.

The solution is not simply:

FetchType.EAGER

Instead, design the query/service boundary so that the required data is fetched intentionally.


47. Entity vs DTO

A JPA entity represents persistence.

A DTO represents data exchanged between application boundaries.

Example:

public record CustomerResponse(
    Long id,
    String name,
    String email
) {
}

A controller can return:

Customer Entity
      |
      v
CustomerResponse DTO
      |
      v
HTTP Response

This prevents the API contract from being tightly coupled to the database model.


48. A Complete Example

Entity

@Entity
@Table(name = "customers")
public class Customer {

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

    @Column(nullable = false)
    private String name;

    @Column(nullable = false, unique = true)
    private String email;

    // getters and setters
}

Repository

public interface CustomerRepository
        extends JpaRepository<Customer, Long> {

    Optional<Customer> findByEmail(String email);

    List<Customer> findByNameContainingIgnoreCase(String name);
}

Service

@Service
public class CustomerService {

    private final CustomerRepository repository;

    public CustomerService(CustomerRepository repository) {
        this.repository = repository;
    }

    @Transactional
    public Customer create(Customer customer) {
        return repository.save(customer);
    }

    @Transactional(readOnly = true)
    public Customer findById(Long id) {
        return repository.findById(id)
                .orElseThrow();
    }
}

Controller

@RestController
@RequestMapping("/customers")
public class CustomerController {

    private final CustomerService service;

    public CustomerController(CustomerService service) {
        this.service = service;
    }

    @GetMapping("/{id}")
    public Customer findById(@PathVariable Long id) {
        return service.findById(id);
    }
}

The complete flow is:

HTTP Request
     |
     v
Controller
     |
     v
Service
     |
     v
Repository
     |
     v
Spring Data JPA
     |
     v
JPA
     |
     v
Hibernate
     |
     v
JDBC
     |
     v
PostgreSQL

49. Spring Data JPA Mental Model

A useful mental model is:

              YOUR CODE
                  |
                  v
        Spring Data Repository
                  |
                  v
                 JPA
                  |
          +-------+-------+
          |               |
          v               v
   EntityManager      Persistence
                       Context
          |
          v
       Hibernate
          |
          v
         JDBC
          |
          v
       Database

And for an entity:

Java Object
     |
     | @Entity
     v
JPA Entity
     |
     v
Persistence Context
     |
     | dirty checking
     v
Hibernate
     |
     | generated SQL
     v
Database

50. Best Practices Summary

Repository

  • Prefer JpaRepository for most Spring Data JPA applications.
  • Keep repositories focused on persistence operations.
  • Use derived query methods for simple queries.
  • Use @Query for more complex queries.
  • Use native SQL only when there is a clear reason.

Transactions

  • Define transaction boundaries at the service layer.
  • Use @Transactional for business operations requiring atomicity.
  • Consider readOnly = true for read operations.
  • Understand rollback behavior.

Relationships

  • Understand the owning side.
  • Use mappedBy correctly.
  • Avoid unnecessary bidirectional relationships.
  • Be careful with cascading deletes.
  • Prefer LAZY loading where appropriate.

Performance

  • Watch for N+1 queries.
  • Use pagination for large datasets.
  • Don’t blindly use findAll().
  • Understand generated SQL.
  • Use fetch joins, projections, or entity graphs when appropriate.

Entities

  • Keep entities focused on persistence/domain behavior.
  • Avoid exposing entities directly as API contracts.
  • Use DTOs for external boundaries.
  • Be careful with equals(), hashCode(), and toString().
  • Avoid blindly using Lombok @Data on entities.

51. Final Comparison

JDBC
 |
 | Direct database communication
 v
Spring JDBC
 |
 | JDBC with Spring abstractions
 v
JPA
 |
 | Persistence specification
 v
Hibernate
 |
 | ORM implementation
 v
Spring Data JPA
 |
 | Repository abstraction
 v
Your Application

The key idea is:

Spring Data JPA does not replace the database, SQL, JDBC, JPA, or Hibernate. It provides a higher-level repository abstraction that makes working with JPA much easier.

Understanding what happens underneath the repository is essential.

When you write:

customerRepository.findById(id);

you should mentally understand that there is a chain underneath:

Repository

Spring Data JPA

JPA / EntityManager

Persistence Context

Hibernate

JDBC

SQL

Database

That mental model is what allows you to use Spring Data JPA effectively rather than simply treating it as “magic CRUD.”

← Voltar para o blog