Stage 11: Spring Boot, lesson 3 of 8

Spring Data JPA repositories

Intermediate3 min read@since 17Code runs on your Java 25
Explain it forThe essentials plus production detail and pitfalls.

Extend JpaRepository<Entity, Id> and you get CRUD, paging and sorting with no implementation class.

Ways to query:

  • Derived queries from method names: findByEmail, countByLevel, existsBySlug, findTop5ByOrderByRatingDesc.
  • @Query with JPQL, or native SQL with nativeQuery = true.
  • Projections: interfaces or records that return only some columns.
  • Paging: Pageable in, Page<T> or Slice<T> out.
  • @Modifying for bulk updates and deletes.
  • Specifications or Querydsl for dynamic filters.

Auditing (@CreatedDate, @LastModifiedBy) fills in timestamps and users automatically once you add @EnableJpaAuditing.

Example

Java
public interface CourseRepository extends JpaRepository<Course, Long> {

    Optional<Course> findBySlug(String slug);

    List<Course> findTop5ByLevelOrderByRatingDesc(Level level);

    Page<CourseCard> findByTitleContainingIgnoreCase(String q, Pageable pageable);   // projection

    @Query("select c from Course c where c.price <= :max and c.published = true")
    List<Course> affordable(@Param("max") BigDecimal max);

    @Modifying(clearAutomatically = true)
    @Transactional
    @Query("update Course c set c.published = false where c.updatedAt < :cutoff")
    int unpublishStale(@Param("cutoff") Instant cutoff);
}

public interface CourseCard {          // interface projection
    String getTitle();
    BigDecimal getPrice();
}

Page<CourseCard> page = repo.findByTitleContainingIgnoreCase(
        "spring", PageRequest.of(0, 20, Sort.by("rating").descending()));

Common mistake

Running a @Modifying query outside a transaction (it fails with TransactionRequiredException), or trusting entities loaded before a bulk update. Bulk updates bypass the persistence context, so use clearAutomatically = true.

Under the hood

save() on an entity with an assigned (not generated) id calls merge, which runs a SELECT first; implement Persistable or use generated ids. deleteAll() loads every entity and deletes them one by one, so prefer deleteAllInBatch() or a @Modifying query. Page runs an extra COUNT query; use Slice for infinite scrolling.

Check yourself

Which method name is a valid derived query?

How this connects

Where this leads

You've reached the end of this thread. Try a learning path for what's next.

Part of Job-ready backend developer.

Was this lesson helpful?

Finished reading? Mark it complete to track your progress.