This episode teaches data access with Spring Data JPA: mapping tables via @Entity, creating repositories with CrudRepository, using query methods and custom queries with @Query, configuring the datasource, and using H2 for prototyping.

Your API in episode 5 still uses hardcoded data. It's time to store data in a database. Episode 6 introduces Spring Data JPA — the most common way to access relational databases in Spring Boot.
You'll learn to map tables into Java objects, create repositories that provide CRUD operations for free, write automatic and custom queries, and set up a datasource — from in-memory H2 for development to PostgreSQL for production.
To get started, add the JPA starter dependency and the database driver to pom.xml. With Spring Initializr, just choose Spring Data JPA and H2 Database. If the project already exists, add them manually:
<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>The spring-boot-starter-data-jpa starter pulls in Hibernate as the JPA implementation, spring-data-jpa, and the default HikariCP connection pool.
An entity is a Java class that represents a table. Each entity instance is one row in the table:
@Entity
@Table(name = "items")
public class Item {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
private String description;
}@Entity marks the class as a JPA entity, @Id defines the primary key, @GeneratedValue sets up auto-increment, and @Table sets the table name. Hibernate will create the schema automatically according to the ddl-auto configuration.
A repository is an interface that handles queries. Just declare an interface that extends CrudRepository or JpaRepository, and Spring Data provides a complete implementation of CRUD operations:
public interface ItemRepository extends CrudRepository<Item, Long> {
}Without writing a single line of implementation, this repository already provides methods like save, findById, findAll, count, and deleteById. This is the power of Spring Data: the patterns that always repeat are generated automatically.
Wire the repository into the service via constructor injection:
@Service
public class ItemService {
private final ItemRepository repository;
public ItemService(ItemRepository repository) {
this.repository = repository;
}
public Item create(Item item) {
return repository.save(item);
}
public Optional<Item> findById(Long id) {
return repository.findById(id);
}
}The repository.save(item) call inserts a new row or updates an existing one, and repository.findById(id) returns an Optional that's empty if the data isn't found.
Spring Data can derive queries from method names. For example, findByNameContaining will produce a WHERE name LIKE %value% query:
public interface ItemRepository extends CrudRepository<Item, Long> {
List<Item> findByNameContaining(String keyword);
List<Item> findByDescriptionIsNotNullOrderByNameAsc();
}The thought rule is simple: start with findBy, then name the field being filtered, and end with modifiers such as Containing, OrderBy, or Between. The full keyword list is available in the Spring Data documentation.
For complex queries or native SQL, use @Query:
public interface ItemRepository extends CrudRepository<Item, Long> {
@Query("SELECT i FROM Item i WHERE i.name LIKE %:k%")
List<Item> cariByName(@Param("k") String keyword);
@Query(value = "SELECT COUNT(*) FROM items WHERE name = :n",
nativeQuery = true)
long countByName(@Param("n") String name);
}The first query uses JPQL — an object-oriented query. The second uses native SQL directly against the database. Choose JPQL for portability and native for cases that need database-specific SQL.
Spring Boot's default uses in-memory H2 — a database that disappears every time the application stops, suitable for prototyping. Enable the explicit configuration and the H2 console:
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=create-drop
spring.h2.console.enabled=true
spring.h2.console.path=/h2-consoleThe setting spring.jpa.hibernate.ddl-auto=create-drop asks Hibernate to create the schema at startup and drop it at shutdown. The H2 console can be accessed at /h2-console to inspect the database contents.
For a more realistic environment, switch the datasource to PostgreSQL:
spring.datasource.url=jdbc:postgresql://localhost:5432/belajar
spring.datasource.username=belajar
spring.datasource.password=rahasia
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialectIn production, use validate so Hibernate only checks the schema without modifying it. The proper schema migration strategy with Flyway or Liquibase will be covered in episode 10.
Episode 6 equipped you with data access using Spring Data JPA: mapping tables with @Entity, creating automatic CRUD repositories via CrudRepository, writing query methods and custom queries with @Query, and configuring H2 and PostgreSQL datasources.
Key takeaways:
@Entity maps a Java class to a database table; @Id marks the primary key.CrudRepository provides CRUD without implementation.findByNameContaining derive queries from method names.@Query writes JPQL or native SQL queries explicitly.ddl-auto=create-drop.ddl-auto=validate and managed migrations.In the next episode, episode 7, we'll discuss validation and exception handling — Jakarta Bean Validation with @Valid, @NotNull, and @Size, custom validation annotations, global exception handling with @ControllerAdvice and @ExceptionHandler, and a consistent error response format for your API.