Test your Spring Data JPA knowledge with 20 interview questions covering JPA repositories, entity mapping, relationships, JPQL, query methods, pagination, caching, and performance optimization.
Below are all 20 questions covered in this quiz, grouped by topic. Each question includes the correct answer and a detailed explanation to help you prepare for your next interview.
What is the difference between JpaRepository and CrudRepository in Spring Data JPA?
JpaRepository extends CrudRepository and adds JPA-specific methods like flush(), saveAndFlush(), and batch delete, plus it extends PagingAndSortingRepository
JpaRepository extends PagingAndSortingRepository, which in turn extends CrudRepository. This means JpaRepository inherits all CRUD methods and adds JPA-specific features: flush(), saveAndFlush(), deleteInBatch(), and getOne(). It also provides built-in pagination and sorting through PagingAndSortingRepository. Use CrudRepository when you only need basic CRUD; use JpaRepository when you need pagination, sorting, or JPA-specific batch operations.
How would you implement a custom repository method that requires complex logic beyond what derived queries or @Query can provide?
Create a custom interface with the method signature, implement it in a class with the Impl suffix, and have your repository extend both JpaRepository and the custom interface
Spring Data JPA supports custom repository implementations via a fragment pattern: (1) Define a custom interface: interface UserRepositoryCustom { List<User> findUsersWithComplexCriteria(...); } (2) Create an implementation class named with the Impl suffix: class UserRepositoryCustomImpl implements UserRepositoryCustom { @PersistenceContext EntityManager em; ... } (3) Have your main repository extend both: interface UserRepository extends JpaRepository<User, Long>, UserRepositoryCustom {}. Spring Data automatically detects the Impl class and delegates calls to it. This is ideal for Criteria API queries, stored procedures, or any logic that requires the EntityManager directly.
What is the purpose of the @Entity annotation in JPA?
It marks a class as a JPA entity that is mapped to a database table and managed by the persistence context
The @Entity annotation (jakarta.persistence.Entity) marks a Java class as a JPA entity, meaning it will be mapped to a database table. The persistence provider (e.g., Hibernate) manages the lifecycle of entity instances within a persistence context. Every entity must have a no-arg constructor and a primary key annotated with @Id. Without @Entity, JPA will not recognize the class for ORM mapping.
How do you define a derived query method in a Spring Data JPA repository?
By naming the method following Spring Data conventions, e.g., findByEmailAndStatus(String email, Status status)
Spring Data JPA can automatically generate query implementations from method names. The method name is parsed into subject and predicate: findBy is the subject, followed by entity property names combined with operators like And, Or, Between, LessThan, Like, OrderBy, etc. For example, findByLastNameAndAgeGreaterThan(String lastName, int age) generates a query filtering by lastName and age > the given value. This eliminates boilerplate query code for common queries.
What is the @Query annotation used for in Spring Data JPA?
It allows you to define custom JPQL or native SQL queries directly on repository methods
The @Query annotation lets you define custom JPQL or native SQL queries on repository methods when derived query methods are not sufficient. For JPQL: @Query("SELECT u FROM User u WHERE u.email = :email"). For native SQL: @Query(value = "SELECT * FROM users WHERE email = :email", nativeQuery = true). You can use named parameters with @Param or positional parameters (?1, ?2). @Query also supports update/delete operations when combined with @Modifying and @Transactional.
What is the difference between JPQL and native SQL queries in Spring Data JPA?
JPQL operates on entity objects and their fields, while native SQL operates directly on database tables and columns
JPQL (Java Persistence Query Language) is an object-oriented query language that queries entity objects rather than database tables. You use entity names and field names (e.g., SELECT u FROM User u WHERE u.email = :email). It is database-agnostic, so switching databases requires no query changes. Native SQL queries use actual table and column names and are database-specific, but they give you access to database-specific features like window functions, CTEs, or vendor-specific syntax that JPQL does not support.
What is a Projection in Spring Data JPA and when would you use one?
A Projection is an interface or class that defines a subset of entity fields to retrieve, reducing the data fetched from the database
Spring Data JPA Projections let you return a subset of an entity's fields instead of the full entity. Interface-based projections define getter methods for the desired fields: interface UserSummary { String getName(); String getEmail(); }. Class-based projections (DTO projections) use constructor expressions. You can also use @Value with SpEL for computed properties. Projections improve performance by selecting only the needed columns. Use them in repository methods: List<UserSummary> findByStatus(Status status);. For JPQL, use constructor expressions: SELECT new com.example.UserDto(u.name, u.email) FROM User u.
What is the difference between FetchType.LAZY and FetchType.EAGER in JPA?
LAZY defers loading of the association until it is accessed; EAGER loads the association immediately with the parent entity
FetchType.LAZY creates a proxy and only loads the associated data when the getter is first called, while FetchType.EAGER loads the data immediately in the same query as the parent entity. The defaults are: @OneToMany and @ManyToMany default to LAZY; @ManyToOne and @OneToOne default to EAGER. In practice, LAZY is generally preferred to avoid loading unnecessary data, but you must be aware of LazyInitializationException if you access a lazy association outside a transaction.
How do you map a bidirectional @OneToMany / @ManyToOne relationship correctly in JPA?
Use @OneToMany(mappedBy = "parentField") on the parent side and @ManyToOne with @JoinColumn on the child side, making the child the owning side
In a bidirectional OneToMany/ManyToOne relationship, the @ManyToOne side is the owning side (it holds the foreign key). The parent uses @OneToMany(mappedBy = "order") to indicate it is the inverse side, where "order" is the field name on the child entity. The child uses @ManyToOne with @JoinColumn(name = "order_id") to define the foreign key column. Always synchronize both sides in code: when adding a child, set the parent reference on the child AND add the child to the parent's collection.
What is the difference between @ManyToMany with a join table and modeling the join table as a separate entity?
Modeling the join table as a separate entity allows you to add extra columns (e.g., timestamps, roles) to the relationship, which @ManyToMany with @JoinTable cannot do
A simple @ManyToMany with @JoinTable creates a join table with only the two foreign keys. If you need additional attributes on the relationship (e.g., a role in a User-Project relationship, or enrolledAt in a Student-Course relationship), you must model the join table as a separate entity with two @ManyToOne relationships and the extra fields. This is common in real-world applications and gives you full control over the join table's schema, lifecycle, and querying.
Consider the following entity mapping:
```java
@Entity
public class Department {
@Id @GeneratedValue
private Long id;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List<Employee> employees = new ArrayList<>();
}
```
What does `orphanRemoval = true` do?It automatically deletes Employee entities from the database when they are removed from the parent's collection, even without an explicit delete call
orphanRemoval = true means that when a child entity is removed from the parent's collection (e.g., department.getEmployees().remove(employee)), JPA will automatically delete that child entity from the database. This is different from CascadeType.REMOVE, which only deletes children when the parent itself is deleted. orphanRemoval handles the case where a child is "orphaned" by being disassociated from its parent. Use it when children have no meaning without their parent (e.g., order items without an order).
How do you implement pagination in Spring Data JPA?
By accepting a Pageable parameter in your repository method and returning a Page<T> object
Spring Data JPA provides built-in pagination through the Pageable interface. Pass a PageRequest (created via PageRequest.of(page, size, sort)) to any repository method that accepts Pageable. The return type can be Page<T> (includes total count and metadata), Slice<T> (knows if there is a next slice without counting), or List<T>. Example: Page<User> findByStatus(Status status, Pageable pageable); In controllers, Spring can auto-resolve Pageable from query parameters like ?page=0&size=10&sort=name,asc.
What is the N+1 query problem in JPA, and how do you solve it?
When loading N parent entities triggers N additional queries to fetch their lazy-loaded associations; solved with JOIN FETCH, @EntityGraph, or @BatchSize
The N+1 problem occurs when you load a list of N entities and accessing each entity's lazy association triggers a separate SQL query, resulting in 1 (for the parent list) + N (for each child) queries. Solutions include: (1) JOIN FETCH in JPQL: SELECT o FROM Order o JOIN FETCH o.items; (2) @EntityGraph on repository methods to specify which associations to eagerly load per query; (3) @BatchSize(size = 50) on the association to load lazy collections in batches instead of one-by-one. Always monitor your SQL logs with spring.jpa.show-sql=true to detect this problem.
What is the purpose of @EntityGraph in Spring Data JPA?
It specifies which associations should be eagerly fetched for a specific query, overriding the default fetch strategy without modifying the entity mapping
The @EntityGraph annotation allows you to define which associations to eagerly load on a per-query basis, without changing the entity's default fetch type. This is a cleaner alternative to writing JOIN FETCH queries for every variation. Example: @EntityGraph(attributePaths = {"items", "items.product"}) on a repository method will eagerly load the items collection and each item's product in a single query. You can also define named entity graphs on the entity class with @NamedEntityGraph and reference them in the repository.
What does @Transactional do when applied to a Spring Data JPA service method?
It wraps the method execution in a database transaction, ensuring all operations either commit together or roll back on failure
The @Transactional annotation creates a transactional proxy around the method. All database operations within the method share the same transaction and persistence context. If the method completes normally, the transaction commits; if a RuntimeException is thrown, it rolls back. Key attributes: readOnly = true for read-only queries (enables optimizations), propagation to control nested transaction behavior (REQUIRED, REQUIRES_NEW, etc.), isolation for isolation level, and rollbackFor to specify which checked exceptions trigger rollback. Note: @Transactional only works on public methods when using proxy-based AOP.
What happens if you call a @Transactional method from within the same class in Spring?
The @Transactional annotation is ignored because Spring's proxy-based AOP does not intercept self-invocation within the same class
Spring's @Transactional relies on proxy-based AOP. When an external caller invokes a transactional method, the call goes through the proxy, which manages the transaction. However, when a method within the same class calls another @Transactional method directly (self-invocation), it bypasses the proxy and the transaction annotation is ignored. Solutions include: (1) refactoring the transactional method into a separate bean; (2) injecting the bean into itself; (3) using AspectJ mode instead of proxy mode. This is one of the most common Spring transaction pitfalls.
How does JPA auditing work with @CreatedDate and @LastModifiedDate?
JPA auditing automatically populates these fields when entities are created or updated, enabled via @EnableJpaAuditing and @EntityListeners(AuditingEntityListener.class)
Spring Data JPA auditing automatically tracks creation and modification timestamps. To enable it: (1) Add @EnableJpaAuditing to a @Configuration class; (2) Add @EntityListeners(AuditingEntityListener.class) to your entity; (3) Annotate fields with @CreatedDate, @LastModifiedDate, @CreatedBy, and/or @LastModifiedBy. For @CreatedBy/@LastModifiedBy, implement an AuditorAware<T> bean that returns the current user. A common pattern is to create a @MappedSuperclass base entity with these audit fields so all entities inherit them.
What is the difference between Hibernate's first-level cache and second-level cache?
First-level cache is session-scoped and always enabled; second-level cache is SessionFactory-scoped, shared across sessions, and must be explicitly configured
The first-level cache (L1) is the persistence context itself, scoped to a single EntityManager/Session. It ensures that within a transaction, the same entity is returned as the same Java object (identity guarantee). It is always on and cannot be disabled. The second-level cache (L2) is scoped to the SessionFactory and shared across all sessions. It must be explicitly enabled with a provider (EhCache, Hazelcast, Caffeine) and configured per entity with @Cacheable and @Cache. L2 cache is useful for frequently read, rarely modified data. Use with caution in distributed environments.
What is the difference between optimistic and pessimistic locking in JPA?
Optimistic locking uses a @Version field to detect concurrent modifications at commit time; pessimistic locking acquires database locks to prevent concurrent access entirely
Optimistic locking adds a @Version column (int, Integer, long, Long, or Timestamp) to the entity. On update, JPA checks if the version matches the database; if another transaction modified it, an OptimisticLockException is thrown. This is best for low-contention scenarios. Pessimistic locking uses SELECT ... FOR UPDATE database locks, acquired via entityManager.lock(entity, LockModeType.PESSIMISTIC_WRITE) or @Lock(LockModeType.PESSIMISTIC_WRITE) on repository methods. This blocks other transactions from reading/writing the row. Use pessimistic locking when conflicts are frequent and the cost of retrying is high.
What is the difference between JPA and Hibernate?
JPA is a specification that defines the ORM standard; Hibernate is the most popular implementation of that specification
JPA (Jakarta Persistence API, formerly Java Persistence API) is a specification that defines standard interfaces and annotations for object-relational mapping in Java (e.g., @Entity, EntityManager, JPQL). Hibernate is the most widely used JPA implementation (provider). Hibernate implements all JPA interfaces and adds its own extensions (e.g., @NaturalId, Criteria API enhancements, multi-tenancy, Envers for auditing). EclipseLink is another JPA provider. Spring Data JPA abstracts over JPA, so you typically code against JPA interfaces while Hibernate runs underneath.
Take the interactive quiz and get your score with a personalized topic breakdown.
Start the Quiz20 questions · 30 min
Java20 questions · 30 min
Java20 questions · 30 min
Java20 questions · 30 min
Java20 questions · 30 min
Java20 questions · 30 min
Spring Boot20 questions · 30 min
Spring Boot20 questions · 30 min
Spring Boot20 questions · 30 min
Spring Boot20 questions · 30 min
Spring Boot20 questions · 30 min
DevOps20 questions · 30 min
Join thousands of developers mastering in-demand skills with Amigoscode. Try it free today.