All articles
Spring DataExam Tips

Spring Data JPA Exam Guide (2V0-72.22)

April 23, 202613 min read

What Is Spring Data JPA

Spring Data JPA is an abstraction layer that sits on top of the Java Persistence API (JPA) and removes most of the boilerplate involved in writing data access code. Instead of implementing DAOs by hand, you declare a repository interface and Spring Data generates the implementation at runtime.

To keep three terms straight:

TermWhat It Is
JPAA Java specification (interfaces and annotations) for ORM. Defines EntityManager, @Entity, JPQL.
HibernateThe most common JPA provider — the actual implementation that talks to the database.
Spring Data JPAA Spring project that auto-implements repository interfaces on top of any JPA provider (usually Hibernate).

Spring Data JPA does not replace Hibernate — it sits above it. When you call userRepository.findById(1L), Spring Data delegates to the JPA EntityManager, which delegates to Hibernate, which executes SQL.

YOUR CODE
userRepository.findById(1L)
delegates to
Spring Data JPA
generates repository proxy · derives queries from method names
implemented by
JPA
Java Persistence API · specification
EntityManager · @Entity · JPQL
speaks
Hibernate
JPA provider
dirty checking · 1st/2nd-level cache · ORM
executes SQL via
JDBC + Database driver
DATABASE
PostgreSQL · MySQL · Oracle · …

Each layer delegates downward and depends only on the layer immediately below — you can swap Hibernate for EclipseLink without touching Spring Data, and you can drop Spring Data entirely and call JPA directly when needed.

On the 2V0-72.22 exam, Spring Data JPA falls under the Data Management section, which carries roughly 10-12% of all questions. Expect questions on the repository hierarchy, @Transactional propagation, rollback rules, derived query method naming, and the difference between JdbcTemplate and JPA-based access.

This guide walks through everything in that section, with code examples and the exam traps that cost candidates points. For the entity-mapping deep dive — relationships, fetch strategies, cascading, inheritance, and N+1 — see the companion JPA Entity Mapping Exam Guide.


Setup & Auto-configuration

To use Spring Data JPA in a Spring Boot application, add the starter:

Maven:

<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>

Gradle:

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'

Once the starter is on the classpath, Spring Boot auto-configures everything you need: a DataSource, an EntityManagerFactory, a PlatformTransactionManager, and @EnableJpaRepositories (so it scans for repository interfaces).

In a plain Spring application (no Boot), you have to enable repository scanning yourself:

@Configuration
@EnableJpaRepositories(basePackages = "com.example.repo")
public class JpaConfig { /* ... */ }

If you want to dig into how JpaRepositoriesAutoConfiguration and DataSourceAutoConfiguration decide what to register, see Spring Boot Auto-configuration Explained.

For repository slice testing, @DataJpaTest, and transactional rollback behavior in tests, see the Spring Boot Testing Exam Guide.

Exam tip: @EnableJpaRepositories is applied automatically by Spring Boot. You only add it manually in non-Boot Spring applications, or to override the default scan path.


EntityManager and @PersistenceContext

You rarely touch EntityManager directly when using Spring Data, but the exam expects you to know how it gets injected when you do — for example in a custom repository implementation.

@Repository
public class OrderQueryDao {
 
    @PersistenceContext
    private EntityManager em;
 
    public List<Order> findRecent(int limit) {
        return em.createQuery("SELECT o FROM Order o ORDER BY o.createdAt DESC", Order.class)
                 .setMaxResults(limit)
                 .getResultList();
    }
}

@PersistenceContext does not inject the raw EntityManager — it injects a thread-safe shared proxy that delegates to the real EntityManager bound to the current transaction. That's why a singleton DAO can safely hold an EntityManager field.

Exam tip: Use @PersistenceContext, not @Autowired, to inject EntityManager. The proxy created by @PersistenceContext is what makes EntityManager safe to use as a field in a singleton bean.

Exam tip: EntityManager is not thread-safe by itself. The Spring/JPA injection magic gives you a transaction-scoped proxy, not the underlying instance.


Repository Hierarchy

Every Spring Data repository extends from a small family of marker interfaces. The exam expects you to know what each level adds.

InterfaceExtendsWhat It Adds
Repository<T, ID>Marker interface. Spring detects all sub-interfaces for repository proxy creation. No methods.
CrudRepository<T, ID>RepositoryBasic CRUD: save, findById, findAll, count, delete, existsById.
ListCrudRepository<T, ID>CrudRepositorySame as CrudRepository, but returns List instead of Iterable.
PagingAndSortingRepository<T, ID>RepositoryAdds findAll(Pageable) and findAll(Sort).
JpaRepository<T, ID>ListCrudRepository, ListPagingAndSortingRepository, QueryByExampleExecutorAdds JPA-specific operations: flush, saveAndFlush, deleteAllInBatch, getReferenceById.

Version note: This hierarchy reflects Spring Data 3.x (released late 2022, used with Spring Framework 6 / Spring Boot 3). In Spring Data 2.x, PagingAndSortingRepository extended CrudRepository and JpaRepository extended PagingAndSortingRepository directly. If your study materials reference the older layout, that is why.

Visualised (Spring Data 3.x):

Spring Data JPA repository interface hierarchyThe Repository marker interface is extended by CrudRepository (basic CRUD methods) and PagingAndSortingRepository (Pageable and Sort support). Each has a List variant (ListCrudRepository, ListPagingAndSortingRepository) that returns List instead of Iterable. JpaRepository extends both List variants and QueryByExampleExecutor, adding JPA-specific methods: flush, saveAndFlush, deleteAllInBatch, getReferenceById.Repository<T, ID>marker · no methodsCrudRepositorysave · findById · findAllcount · delete · existsByIdreturns Iterable<T>PagingAndSortingRepositoryfindAll(Pageable)findAll(Sort)ListCrudRepositoryreturns List<T>ListPagingAndSortingRepositoryList variant of pagingJpaRepository<T, ID>+ flush · saveAndFlush · deleteAllInBatch+ getReferenceById · QueryByExampleExecutor

Spring Data creates a proxy implementation of your interface at startup. The proxy is itself a Spring bean — created during the bean lifecycle like any other component, with all the BeanPostProcessor hooks applied.

A typical repository declaration:

public interface UserRepository extends JpaRepository<User, Long> {
    // No code needed — Spring Data implements all CRUD and JPA methods automatically
}

You can also extend just CrudRepository if you want a leaner API and don't need JPA-specific methods:

public interface ProductRepository extends CrudRepository<Product, Long> {
    Optional<Product> findBySku(String sku);
}

Exam tip: JpaRepository extends ListCrudRepository and ListPagingAndSortingRepository. It is the JPA-specific terminal interface — it adds flush(), saveAndFlush(), deleteAllInBatch(), and getReferenceById() (which returns a lazy proxy without hitting the database).

Exam tip: The @Repository annotation is not required on Spring Data interfaces. Spring detects them via @EnableJpaRepositories (or Boot's auto-config) and creates the proxy automatically. @Repository is for traditional @Component-style DAOs.

Paging and Sorting

PagingAndSortingRepository (and therefore JpaRepository) gives you Pageable and Sort for free:

public interface OrderRepository extends JpaRepository<Order, Long> {
    Page<Order> findByStatus(OrderStatus status, Pageable pageable);
    List<Order> findByCustomerId(Long customerId, Sort sort);
}
 
// Usage
Pageable page = PageRequest.of(0, 20, Sort.by("createdAt").descending());
Page<Order> results = orderRepository.findByStatus(OrderStatus.OPEN, page);
 
results.getTotalElements();  // total row count (executes COUNT query)
results.getTotalPages();     // total page count
results.getContent();        // List<Order> for current page

Spring Data offers three return types for paginated queries:

Return TypeExecutes COUNT QueryUse When
Page<T>YesYou need total count / total pages (UI pagination with "page X of Y")
Slice<T>No"Load more" UX — you only need to know if a next page exists
List<T>NoYou just want the slice without metadata

Exam tip: Page<T> triggers an extra SELECT COUNT(*) query to compute total count. If you don't need the total, return Slice<T> to avoid the cost.


Derived Query Methods

The most distinctive feature of Spring Data is query derivation from method names. You declare a method following a naming convention and Spring Data generates the JPQL query at startup.

The pattern is:

<verb> [Distinct] [Top<N> | First<N>] By <Property> [Operator] [And|Or <Property> [Operator]] [OrderBy <Property> Asc|Desc]

Verbs (subject keywords):

VerbPurposeReturn Type
find...By / read...By / get...By / query...ByReadEntity, Optional<Entity>, List<Entity>, Page<Entity>, Stream<Entity>
count...ByCount rowslong
exists...ByCheck existenceboolean
delete...By / remove...ByDeletevoid or long (count of deleted rows)

Operator keywords (predicate):

KeywordTranslates To
Is, Equals= ? (default if omitted)
Not<> ?
LessThan, LessThanEqual< ?, <= ?
GreaterThan, GreaterThanEqual> ?, >= ?
BetweenBETWEEN ? AND ?
Like, NotLikeLIKE ?
StartingWith, EndingWith, ContainingLIKE 'x%', LIKE '%x', LIKE '%x%'
In, NotInIN (?), NOT IN (?)
IsNull, IsNotNullIS NULL, IS NOT NULL
True, False= true, = false
IgnoreCaseLOWER(x) = LOWER(?)
OrderBy<Property>Asc / DescORDER BY x ASC / DESC

Examples building from simple to complex:

public interface UserRepository extends JpaRepository<User, Long> {
 
    Optional<User> findByEmail(String email);
 
    List<User> findByLastNameAndFirstName(String last, String first);
 
    List<User> findByAgeBetween(int min, int max);
 
    List<User> findByEmailContainingIgnoreCase(String fragment);
 
    List<User> findByStatusInOrderByCreatedAtDesc(Collection<Status> statuses);
 
    long countByStatus(Status status);
 
    boolean existsByEmail(String email);
 
    // Property path traversal — User has an Address, Address has a city
    List<User> findByAddress_City(String city);
 
    // Limiting results
    Optional<User> findFirstByOrderByCreatedAtDesc();
    List<User> findTop5ByStatusOrderByCreatedAtDesc(Status status);
}

Exam tip: Derived query method names are validated at application startup. A typo in a property name (findByEmial) will throw PropertyReferenceException and the context will fail to start. Catch these in tests, not in production.

Exam tip: When a property name is ambiguous (e.g., addressCity could mean address.city or a property literally named addressCity), use an underscore as an explicit separator: findByAddress_City.


@Query — JPQL and Native

Derived methods cover most cases, but they get unwieldy with more than two or three predicates. For anything more complex, use @Query.

JPQL with named parameters:

public interface OrderRepository extends JpaRepository<Order, Long> {
 
    @Query("SELECT o FROM Order o WHERE o.customer.email = :email AND o.status = :status")
    List<Order> findByCustomerEmailAndStatus(@Param("email") String email,
                                             @Param("status") OrderStatus status);
}

JPQL with positional parameters:

@Query("SELECT o FROM Order o WHERE o.total > ?1")
List<Order> findOrdersOverAmount(BigDecimal amount);

Native SQL:

@Query(value = "SELECT * FROM orders WHERE total > :amount",
       nativeQuery = true)
List<Order> findOrdersOverAmountNative(@Param("amount") BigDecimal amount);

Modifying queries (UPDATE / DELETE):

@Modifying
@Transactional
@Query("UPDATE User u SET u.status = :status WHERE u.lastLoginAt < :cutoff")
int deactivateInactiveUsers(@Param("status") Status status,
                            @Param("cutoff") Instant cutoff);

Exam tip: @Modifying queries must run inside a transaction. If the calling code has no @Transactional, you will get TransactionRequiredException. Either annotate the method with @Transactional or rely on a transactional service layer.

Exam tip: Native queries (nativeQuery = true) bypass JPQL parsing. They are not portable across databases — LIMIT works in PostgreSQL/MySQL but not Oracle (which uses ROWNUM or FETCH FIRST).


@Transactional

@Transactional is the densest topic in Data Management — propagation, isolation, rollback rules, and the self-invocation pitfall together account for 2-4 exam questions. The full breakdown lives in a dedicated guide: Spring @Transactional Exam Guide. Read it before the exam.

Exam tip: REQUIRED is the default. Default rollback covers RuntimeException and Error only — checked exceptions commit. @Transactional on private, non-public, or final methods is silently ignored under CGLIB proxies.

JdbcTemplate (Briefly)

The Data Management section also covers Spring's lower-level JDBC abstraction, JdbcTemplate. It is not deprecated and remains the right tool when you don't want JPA's overhead — for simple read paths, complex SQL, or performance-critical code.

@Repository
public class CustomerJdbcDao {
 
    private final JdbcTemplate jdbc;
 
    public CustomerJdbcDao(JdbcTemplate jdbc) {
        this.jdbc = jdbc;
    }
 
    public Customer findById(Long id) {
        return jdbc.queryForObject(
            "SELECT id, name, email FROM customers WHERE id = ?",
            (rs, rowNum) -> new Customer(rs.getLong("id"),
                                          rs.getString("name"),
                                          rs.getString("email")),
            id
        );
    }
 
    public int updateEmail(Long id, String email) {
        return jdbc.update(
            "UPDATE customers SET email = ? WHERE id = ?",
            email, id
        );
    }
}

JdbcTemplate is auto-configured by Spring Boot whenever a DataSource bean exists.

Exam tip: JdbcTemplate is thread-safe once configured and is meant to be used as a singleton bean (which is what Spring Boot gives you).

Exam tip: JdbcTemplate translates JDBC's checked SQLException into Spring's unchecked DataAccessException hierarchy. Your code never has to declare throws SQLException.

The same translation applies to JPA exceptions — Hibernate's ConstraintViolationException, OptimisticLockException, etc. all get re-thrown as DataAccessException subclasses. The most common ones to recognize on the exam:

ExceptionThrown When
DataIntegrityViolationExceptionA constraint is violated (NOT NULL, FOREIGN KEY, CHECK)
DuplicateKeyExceptionA unique constraint (typically primary key) is violated
EmptyResultDataAccessExceptionqueryForObject finds zero rows when one was expected
IncorrectResultSizeDataAccessExceptionA query returns more rows than expected (e.g., 2+ for queryForObject)
OptimisticLockingFailureExceptionA @Version-based optimistic lock check fails
CannotAcquireLockExceptionThe database refused a lock (deadlock or lock timeout)
QueryTimeoutExceptionA statement exceeded its configured timeout

All extend DataAccessException, which extends RuntimeException — so they trigger rollback by default.

Exam tip: All DataAccessException subclasses are unchecked (RuntimeException) and trigger @Transactional rollback by default. You don't need rollbackFor to handle data access failures.

Exam tip: JdbcTemplate integrates with @Transactional through DataSourceTransactionManager. JPA uses JpaTransactionManager. Spring Boot picks the right one based on what's on the classpath.


Common Exam Traps

A condensed checklist of the gotchas that cost points. Skim this before the exam.

  • Self-invocation skips @Transactional. A this.foo() call on a transactional method in the same bean bypasses the proxy.
  • @Modifying without @Transactional throws TransactionRequiredException.
  • Catching the exception inside the @Transactional method prevents rollback. The proxy only rolls back if the exception escapes.
  • Checked exceptions do not trigger rollback unless you set rollbackFor. Only RuntimeException and Error do, by default.
  • Lazy loading outside a transaction throws LazyInitializationException. Initialize collections inside the transactional method or use a fetch join.
  • Derived query property typos crash the application context at startup with PropertyReferenceException.
  • REQUIRES_NEW uses a separate connection. Two transactions touching the same row from the same thread can self-deadlock.
  • @Transactional on private or final methods is silently ignored by the proxy.
  • @Transactional on protected / package-private methods is also silently ignored by default. Spring's AnnotationTransactionAttributeSource has publicMethodsOnly = true out of the box, so non-public methods are skipped regardless of whether you use JDK dynamic or CGLIB proxies.
  • readOnly = true is a hint, not enforcement. A method can still write — the database will not stop it.

Frequently Asked Questions

Is Spring Data JPA on the 2V0-72.22 exam?

Yes. The Data Management section covers Spring Data JPA repositories, @Transactional (propagation, isolation, rollback rules), and JdbcTemplate. It accounts for roughly 10-12% of the exam. Plan to spend solid time on @Transactional — it is the densest topic.

What's the difference between JPA, Hibernate, and Spring Data JPA?

JPA is the specification (interfaces and annotations). Hibernate is the most common JPA provider — it implements the spec. Spring Data JPA is a Spring project that auto-implements your repository interfaces on top of any JPA provider, eliminating most DAO boilerplate.

Which propagation level is the default?

Propagation.REQUIRED. If a transaction is already in progress, the method joins it. If not, a new transaction starts.

When does @Transactional roll back?

By default, only on RuntimeException and Error. Checked exceptions commit the transaction unless you explicitly set rollbackFor = SomeCheckedException.class.

Do I need to know JdbcTemplate for the exam?

Yes — at a basic level. Know that it is thread-safe, that it translates SQLException into Spring's DataAccessException hierarchy, that it is auto-configured when a DataSource exists, and the difference between query, queryForObject, and update. Deep SQL knowledge is not required.


Next Steps

This guide covers the Data Management section of the 2V0-72.22 exam end to end. To round out the rest of the syllabus:

When you are ready to practice with realistic exam-style questions on Spring Data JPA and @Transactional, our question bank has hundreds of items modeled on the 2V0-72.22 format — including the propagation and rollback edge cases that catch most candidates.

Practice This Topic

Reinforce what you've learned with free practice questions and detailed explanations.