# Spring Data JPA and Hibernate — Entities, Repositories and Queries

Source: https://skills101.in/learn/spring-boot/spring-data-jpa-hibernate — Skills101 free tutorial (Advanced Java & Spring Boot). Updated 2026-11-09.

Writing JDBC for every table (lesson 2) is repetitive. **JPA** (Jakarta Persistence API) lets you work with Java objects while **Hibernate** (the JPA implementation Spring Boot uses) writes the SQL. **Spring Data JPA** goes further: you write an *interface* and get the implementation for free.

![The Course entity class with @Entity and @Id maps to a course table with id, title, fee and seats columns; Hibernate turns save into an INSERT statement](https://skills101.in/learn-img/spring-boot/entity-table.svg)

## 1. An entity

```java
// File: course-api/src/main/java/com/skills101/courses/course/Course.java
package com.skills101.courses.course;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Course {

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

    private String title;
    private int fee;    // in rupees
    private int seats;

    protected Course() {
        // JPA needs a no-argument constructor
    }

    public Course(String title, int fee, int seats) {
        this.title = title;
        this.fee = fee;
        this.seats = seats;
    }

    public Long getId() { return id; }
    public String getTitle() { return title; }
    public int getFee() { return fee; }
    public int getSeats() { return seats; }

    public void update(String title, int fee, int seats) {
        this.title = title;
        this.fee = fee;
        this.seats = seats;
    }
}
```

| Annotation | Meaning |
|---|---|
| `@Entity` | this class is stored in a table (named `course`) |
| `@Id` | the primary key |
| `@GeneratedValue(strategy = IDENTITY)` | the database generates ids (auto-increment) |
| `@Column(unique = true, nullable = false)` | column rules (see `Student.email`) |
| `@Table`, `@Transient`, `@Enumerated` | table name/constraints, skip a field, store enums |

JPA needs a **no-argument constructor** (it can be `protected`) and must be able to read the fields. Notice there's no setter for `id` — the database owns it.

## 2. A repository: no implementation needed

```java
// File: course-api/src/main/java/com/skills101/courses/course/CourseRepository.java
package com.skills101.courses.course;

import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

public interface CourseRepository extends JpaRepository<Course, Long> {

    // Spring Data writes the SQL from the method name
    List<Course> findByFeeLessThanEqualOrderByFeeAsc(int maxFee);

    boolean existsByTitleIgnoreCase(String title);

    // or write JPQL yourself (it uses class and field names, not table names)
    @Query("select c from Course c where lower(c.title) like lower(concat('%', :word, '%'))")
    List<Course> search(String word);
}
```

`JpaRepository<Course, Long>` already gives you `save`, `findById`, `findAll`, `existsById`, `deleteById`, `count`, paging and sorting.

**Derived queries**: Spring Data reads the method name and writes the query:

| Method | Generated WHERE |
|---|---|
| `findByTitle(String t)` | `title = ?` |
| `findByFeeLessThanEqualOrderByFeeAsc(int max)` | `fee <= ? order by fee asc` |
| `existsByTitleIgnoreCase(String t)` | `upper(title) = upper(?)` |
| `findByTitleContainingIgnoreCase(String w)` | `upper(title) like %?%` |
| `countByCourseId(Long id)` | `count(*) … course_id = ?` |

For anything complex, write **JPQL** in `@Query` — it uses **entity and field names** (`Course`, `c.title`), not table names. Native SQL is possible with `nativeQuery = true`.

## 3. Proving it works: a @DataJpaTest

```java
// File: course-api/src/test/java/com/skills101/courses/course/CourseRepositoryTest.java
package com.skills101.courses.course;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;

// Starts only JPA + an in-memory database; each test is rolled back afterwards
@DataJpaTest
class CourseRepositoryTest {

    @Autowired
    CourseRepository repository;

    @Test
    void derivedQueriesWork() {
        repository.save(new Course("Python Basics", 1999, 50));
        repository.save(new Course("Java Full Stack", 4999, 30));
        repository.save(new Course("Git Basics", 0, 100));

        assertThat(repository.findByFeeLessThanEqualOrderByFeeAsc(2000))
                .extracting(Course::getTitle)
                .containsExactly("Git Basics", "Python Basics");
        assertThat(repository.existsByTitleIgnoreCase("java full stack")).isTrue();
        assertThat(repository.search("basics")).hasSize(2);
    }
}
```

This test passes in our build: free courses first (`Git Basics`, then `Python Basics` at ₹1999), the case-insensitive check finds "java full stack", and the JPQL search finds both "basics" courses.

## 4. What Hibernate does

- **Generates SQL** from entities and method calls (`spring.jpa.show-sql=true` prints it while learning).
- **Dirty checking**: inside a transaction, change a loaded entity's fields and the UPDATE runs automatically at commit — that's why `CourseService.update` never calls `save`.
- **First-level cache**: loading the same id twice in one transaction hits the database once.
- **Schema generation** with `ddl-auto` (fine for learning; use Flyway/Liquibase migrations for real apps).

## 5. Entity states

| State | Meaning |
|---|---|
| Transient | `new Course(...)` — not known to JPA yet |
| Managed | saved or loaded in the current transaction; changes are tracked |
| Detached | the transaction ended; changes are no longer tracked |
| Removed | scheduled for deletion |

## Common mistakes

- No `@Id` → startup error "No identifier specified for entity".
- Using table/column names in JPQL instead of entity/field names.
- A typo in a derived method name (`findByTitel`) → startup error "No property 'titel' found".
- Making entities `final` or without a no-arg constructor.

## Quick recap

- `@Entity` + `@Id` map a class to a table; Hibernate writes the SQL.
- `JpaRepository` gives CRUD; derived query names and `@Query` (JPQL) give the rest.
- Inside a transaction, managed entities are saved automatically (dirty checking).
- Test repositories with `@DataJpaTest`.


Practice quiz and free certificate: https://skills101.in/learn/spring-boot/spring-data-jpa-hibernate