Spring Framework Tutorial 0/14 lessons ~6 min read Lesson 11

    JDBC, JPA & Spring Data

    Spring offers a ladder of data-access abstractions.

    Course progress0%
    Focus
    8 guided sections
    Practice signal
    Examples included
    Career prep
    Foundation builder

    Introduction

    Spring offers a ladder of data-access abstractions. Pick the lowest rung that solves your problem; you can always climb. All three styles share a unified DataAccessException hierarchy, so error handling stays consistent no matter which one you choose.

    Understanding the topic

    The ladder:

    • JdbcTemplate — thin wrapper over JDBC. SQL stays explicit; boilerplate disappears. Best for reports, bulk loads and performance-critical paths.
    • Spring ORM — integrates Hibernate / JPA: manage entities, let the ORM emit SQL. Best for rich domain models with relationships.
    • Spring Data JPA — declare a repository interface, get queries generated from method names. Best for CRUD-heavy domains where 80% of queries are obvious.

    Syntax reference

    The same query, three abstraction levels:

    java
    // 1. JdbcTemplate — you own the SQL
    List<Order> orders = jdbc.query(
    "select id, total from orders where customer_id = ?",
    (rs, i) -> new Order(rs.getLong("id"), rs.getBigDecimal("total")),
    customerId);
    // 2. JPA EntityManager — JPQL, ORM emits SQL
    List<Order> orders = em
    .createQuery("select o from Order o where o.customer.id = :id", Order.class)
    .setParameter("id", customerId)
    .getResultList();
    // 3. Spring Data — derive the query from the method name
    public interface OrderRepository extends JpaRepository<Order, Long> {
    List<Order> findByCustomerId(Long customerId);
    }

    Informative example

    Custom queries when method names get awkward — use @Query:

    java
    public interface OrderRepository extends JpaRepository<Order, Long> {
    @Query("""
    select o from Order o
    where o.customer.id = :customerId
    and o.placedAt >= :since
    order by o.placedAt desc
    """)
    List<Order> recentFor(@Param("customerId") Long customerId,
    @Param("since") Instant since);
    // Native SQL escape hatch
    @Query(value = "select count(*) from orders where total > :min", nativeQuery = true)
    long countAbove(@Param("min") BigDecimal min);
    }

    Real-world use

    Most real apps use Spring Data for everyday CRUD and reach for JdbcTemplate when they hit a performance ceiling (bulk inserts, complex reports, OLAP-style queries). The two coexist happily — they share the same DataSource and the same exception hierarchy.

    Best practices

    • Use JdbcTemplate for reports and bulk loads where SQL control matters.
    • Use Spring Data for CRUD-heavy domains where 80% of queries are obvious.
    • Fetch only the columns you need — avoid select * via projections (interface- or DTO-based).
    • Wrap every multi-statement workflow in @Transactional at the service layer.

    Common mistakes

    • Returning JPA entities from controllers — they trigger lazy loads outside transactions and serialise more than you intended.
    • N+1 query problems — fetch relationships explicitly with JOIN FETCH or entity graphs.
    • Calling repository methods in a loop where a single batched query would do.

    Hands-on exercise

    Try this: build a tiny BookRepository three ways — once with JdbcTemplate, once with EntityManager, once with Spring Data — backed by an H2 database. Implement findByAuthor(String) in each. Confirm the generated SQL with spring.jpa.show-sql=true and compare the line counts.

    Ready to mark this lesson complete?Track your journey across the entire course.