Prakhar Singh

Spring Data JPA: In-Depth Notes + Interview Questions

Cover Image for Spring Data JPA: In-Depth Notes + Interview Questions
Prakhar Singh
Prakhar Singh

Spring Data JPA: In-Depth Notes + Interview Questions


1. What is Spring Data JPA?

Spring Data JPA is part of the Spring Data family. It sits on top of JPA (Java Persistence API) and Hibernate (the default JPA provider) to eliminate boilerplate data access code.

Layered Architecture

text
Your Code
    ↓
Spring Data JPA       ← Repositories, Query Methods
    ↓
JPA (javax/jakarta.persistence)  ← Standard API
    ↓
Hibernate             ← JPA Implementation (ORM)
    ↓
JDBC
    ↓
Database (MySQL, PostgreSQL, H2, etc.)

Key Dependencies (pom.xml)

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!-- Choose your DB driver -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

2. application.properties Configuration

properties
# DataSource
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=secret
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# JPA / Hibernate
spring.jpa.hibernate.ddl-auto=update
# Options: none | validate | update | create | create-drop

spring.jpa.show-sql=true
spring.jpa.format-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect

# Naming strategy
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

ddl-auto Values Explained

ValueBehaviour
noneDo nothing to schema
validateValidate schema, throw error if mismatch
updateUpdate schema (add columns, don't drop)
createDrop and recreate schema on startup
create-dropCreate on startup, drop on shutdown

Production rule: Use validate or none. Use Flyway/Liquibase for migrations.


3. JPA Entity — Complete Mapping

java
import jakarta.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "users",
       uniqueConstraints = @UniqueConstraint(columnNames = "email"))
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "full_name", nullable = false, length = 100)
    private String name;

    @Column(unique = true, nullable = false)
    private String email;

    @Column(name = "is_active", columnDefinition = "BOOLEAN DEFAULT TRUE")
    private boolean active = true;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private Role role;

    @CreationTimestamp
    @Column(name = "created_at", updatable = false)
    private LocalDateTime createdAt;

    @UpdateTimestamp
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;

    // Getters, Setters, Constructors
}

@GeneratedValue Strategies

StrategyDescription
IDENTITYAuto-increment by DB (MySQL, PostgreSQL serial)
SEQUENCEUses DB sequence object (PostgreSQL default)
TABLEUses a separate table to generate IDs (portable, slow)
AUTOJPA picks strategy based on DB (default)
UUIDGenerates UUID (Spring Data 3+)
java
// UUID strategy example
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;

4. Entity Relationships

@OneToOne

java
@Entity
public class User {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "profile_id", referencedColumnName = "id")
    private UserProfile profile;
}

@Entity
public class UserProfile {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String bio;
    private String avatarUrl;

    @OneToOne(mappedBy = "profile")
    private User user;
}

@OneToMany / @ManyToOne (Most Common)

java
@Entity
public class Department {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToMany(mappedBy = "department", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<Employee> employees = new ArrayList<>();
}

@Entity
public class Employee {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "department_id")
    private Department department;
}

@ManyToMany

java
@Entity
public class Student {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @JoinTable(
        name = "student_course",
        joinColumns = @JoinColumn(name = "student_id"),
        inverseJoinColumns = @JoinColumn(name = "course_id")
    )
    private Set<Course> courses = new HashSet<>();
}

@Entity
public class Course {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;

    @ManyToMany(mappedBy = "courses")
    private Set<Student> students = new HashSet<>();
}

5. Cascade Types

CascadeTypeEffect
PERSISTSave parent → also saves children
MERGEUpdate parent → also updates children
REMOVEDelete parent → also deletes children
REFRESHRefresh parent → also refreshes children
DETACHDetach parent → also detaches children
ALLAll of the above
java
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)

orphanRemoval = true — deletes child records when removed from parent's collection.


6. Fetch Types

FetchTypeBehaviour
LAZY (default for collections)Data loaded only when accessed
EAGER (default for single entities)Data loaded immediately with parent
java
// Always prefer LAZY for collections to avoid N+1 problems
@OneToMany(fetch = FetchType.LAZY)
private List<Order> orders;

N+1 Problem & Fix

Problem: Loading 100 employees fires 1 query for employees + 100 queries for their departments.

Fix — Use @EntityGraph or JOIN FETCH:

java
// In Repository
@EntityGraph(attributePaths = {"department"})
List<Employee> findAll();

// OR with JPQL
@Query("SELECT e FROM Employee e JOIN FETCH e.department")
List<Employee> findAllWithDepartment();

7. Spring Data Repositories

Repository Hierarchy

text
Repository (marker interface)
    └── CrudRepository<T, ID>        → Basic CRUD
            └── PagingAndSortingRepository<T, ID>  → + Pagination & Sorting
                    └── JpaRepository<T, ID>       → + JPA-specific (flush, batch, etc.)

JpaRepository — Built-in Methods

java
public interface UserRepository extends JpaRepository<User, Long> { }
MethodDescription
save(entity)Insert or Update
saveAll(entities)Batch insert/update
findById(id)Returns Optional<T>
findAll()All records
findAllById(ids)Find by list of IDs
existsById(id)Check existence
count()Total count
deleteById(id)Delete by ID
delete(entity)Delete entity
deleteAll()Delete everything
flush()Force sync to DB
saveAndFlush(entity)Save + immediate flush
findAll(Pageable)Paginated results
findAll(Sort)Sorted results

8. Derived Query Methods (Method Name Queries)

Spring generates SQL automatically from method names.

java
public interface UserRepository extends JpaRepository<User, Long> {

    // SELECT * FROM users WHERE name = ?
    List<User> findByName(String name);

    // SELECT * FROM users WHERE email = ? AND active = ?
    Optional<User> findByEmailAndActive(String email, boolean active);

    // SELECT * FROM users WHERE name LIKE '%keyword%'
    List<User> findByNameContaining(String keyword);

    // SELECT * FROM users WHERE created_at BETWEEN ? AND ?
    List<User> findByCreatedAtBetween(LocalDateTime start, LocalDateTime end);

    // SELECT * FROM users WHERE age > ?
    List<User> findByAgeGreaterThan(int age);

    // SELECT * FROM users ORDER BY name ASC
    List<User> findByActiveOrderByNameAsc(boolean active);

    // SELECT COUNT(*) FROM users WHERE role = ?
    long countByRole(Role role);

    // DELETE FROM users WHERE active = false
    void deleteByActive(boolean active);

    // Check existence
    boolean existsByEmail(String email);
}

Keyword Reference

KeywordExampleSQL
AndfindByNameAndEmailWHERE name=? AND email=?
OrfindByNameOrEmailWHERE name=? OR email=?
Is, EqualsfindByNameIsWHERE name=?
BetweenfindByAgeBetweenWHERE age BETWEEN ? AND ?
LessThanfindByAgeLessThanWHERE age < ?
GreaterThanfindByAgeGreaterThanWHERE age > ?
LikefindByNameLikeWHERE name LIKE ?
ContainingfindByNameContainingWHERE name LIKE %?%
StartingWithfindByNameStartingWithWHERE name LIKE ?%
EndingWithfindByNameEndingWithWHERE name LIKE %?
IsNullfindByEmailIsNullWHERE email IS NULL
IsNotNullfindByEmailIsNotNullWHERE email IS NOT NULL
InfindByRoleInWHERE role IN (?,?)
NotInfindByRoleNotInWHERE role NOT IN (?)
True / FalsefindByActiveTrueWHERE active = true
OrderByfindByNameOrderByAgeAscORDER BY age ASC
Top / FirstfindTop3ByActiveLIMIT 3
DistinctfindDistinctByNameSELECT DISTINCT ...

9. Custom Queries

JPQL (Java Persistence Query Language)

java
// JPQL uses entity class names and field names (not table/column names)
@Query("SELECT u FROM User u WHERE u.email = :email")
Optional<User> findByEmail(@Param("email") String email);

@Query("SELECT u FROM User u WHERE u.active = true ORDER BY u.name ASC")
List<User> findAllActiveUsers();

// JPQL with JOIN
@Query("SELECT u FROM User u JOIN FETCH u.orders WHERE u.id = :id")
Optional<User> findByIdWithOrders(@Param("id") Long id);

Native SQL Query

java
@Query(value = "SELECT * FROM users WHERE email = :email", nativeQuery = true)
Optional<User> findByEmailNative(@Param("email") String email);

// Native with pagination
@Query(
    value = "SELECT * FROM users WHERE active = true",
    countQuery = "SELECT COUNT(*) FROM users WHERE active = true",
    nativeQuery = true
)
Page<User> findActiveUsers(Pageable pageable);

Modifying Queries (UPDATE / DELETE)

java
@Modifying
@Transactional
@Query("UPDATE User u SET u.active = false WHERE u.id = :id")
int deactivateUser(@Param("id") Long id);

@Modifying
@Transactional
@Query("DELETE FROM User u WHERE u.active = false")
void deleteInactiveUsers();

10. Pagination & Sorting

java
// Repository method
Page<User> findByActive(boolean active, Pageable pageable);
List<User> findByRole(Role role, Sort sort);
java
// Service usage
public Page<User> getUsers(int page, int size) {
    Pageable pageable = PageRequest.of(page, size, Sort.by("name").ascending());
    return userRepository.findByActive(true, pageable);
}

Page Object Contents

java
Page<User> result = userRepository.findAll(pageable);

result.getContent();          // List of users on this page
result.getTotalElements();    // Total records in DB
result.getTotalPages();       // Total pages
result.getNumber();           // Current page number (0-based)
result.getSize();             // Page size
result.isFirst();             // Is this the first page?
result.isLast();              // Is this the last page?
result.hasNext();             // Has next page?

Sort Examples

java
Sort.by("name").ascending()
Sort.by("name").descending()
Sort.by("name").ascending().and(Sort.by("createdAt").descending())

// In Pageable
PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, "createdAt"))

11. Specifications (Dynamic Queries)

Use when query conditions are determined at runtime (filter APIs).

java
// Add to repository
public interface UserRepository extends JpaRepository<User, Long>,
                                         JpaSpecificationExecutor<User> { }
java
// Define Specifications
public class UserSpecifications {

    public static Specification<User> hasName(String name) {
        return (root, query, cb) ->
            name == null ? null : cb.equal(root.get("name"), name);
    }

    public static Specification<User> isActive(Boolean active) {
        return (root, query, cb) ->
            active == null ? null : cb.equal(root.get("active"), active);
    }

    public static Specification<User> hasRole(Role role) {
        return (root, query, cb) ->
            role == null ? null : cb.equal(root.get("role"), role);
    }
}
java
// Service usage — combine dynamically
public List<User> searchUsers(String name, Boolean active, Role role) {
    Specification<User> spec = Specification
        .where(UserSpecifications.hasName(name))
        .and(UserSpecifications.isActive(active))
        .and(UserSpecifications.hasRole(role));

    return userRepository.findAll(spec);
}

12. Transaction Management

@Transactional Attributes

java
@Transactional(
    propagation = Propagation.REQUIRED,   // default
    isolation = Isolation.DEFAULT,         // default
    readOnly = false,
    rollbackFor = Exception.class,
    timeout = 30
)
public void processOrder(Order order) {
    // All DB operations here run in one transaction
}

Propagation Types

PropagationBehaviour
REQUIREDJoin existing or create new (default)
REQUIRES_NEWAlways create new, suspend existing
NESTEDNested inside existing transaction
SUPPORTSJoin if exists, else run without
NOT_SUPPORTEDSuspend existing, run without transaction
MANDATORYMust run in existing, else throw exception
NEVERMust NOT run in transaction, else throw

Isolation Levels

IsolationPrevents
READ_UNCOMMITTEDNothing (dirty reads allowed)
READ_COMMITTEDDirty reads
REPEATABLE_READDirty reads + non-repeatable reads
SERIALIZABLEAll concurrency issues (slowest)
java
// Read-only for SELECT queries (performance optimization)
@Transactional(readOnly = true)
public List<User> getAllUsers() {
    return userRepository.findAll();
}

13. Auditing with Spring Data JPA

java
// Enable auditing in main class or config
@SpringBootApplication
@EnableJpaAuditing(auditorAwareRef = "auditorProvider")
public class MyApp { }
java
// Auditable base entity
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;

    @CreatedBy
    @Column(updatable = false)
    private String createdBy;

    @LastModifiedBy
    private String updatedBy;
}

// Extend in your entities
@Entity
public class User extends BaseEntity {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}
java
// Provide current user
@Component
public class AuditorProvider implements AuditorAware<String> {
    @Override
    public Optional<String> getCurrentAuditor() {
        return Optional.of(
            SecurityContextHolder.getContext()
                .getAuthentication().getName()
        );
    }
}

14. Projections (Partial Data Fetching)

Interface Projection

java
public interface UserSummary {
    String getName();
    String getEmail();
}

// Repository
List<UserSummary> findByActive(boolean active);

Class-based (DTO) Projection

java
public record UserDTO(String name, String email) { }

// Repository
@Query("SELECT new com.example.dto.UserDTO(u.name, u.email) FROM User u WHERE u.active = true")
List<UserDTO> findActiveUserDTOs();

Dynamic Projection

java
<T> List<T> findByActive(boolean active, Class<T> type);

// Usage
List<UserSummary> summaries = repo.findByActive(true, UserSummary.class);
List<User> full = repo.findByActive(true, User.class);

15. Entity Lifecycle Callbacks

java
@Entity
public class User {

    @PrePersist
    public void prePersist() {
        System.out.println("Before insert");
    }

    @PostPersist
    public void postPersist() {
        System.out.println("After insert");
    }

    @PreUpdate
    public void preUpdate() {
        System.out.println("Before update");
    }

    @PostUpdate
    public void postUpdate() {
        System.out.println("After update");
    }

    @PreRemove
    public void preRemove() {
        System.out.println("Before delete");
    }

    @PostRemove
    public void postRemove() {
        System.out.println("After delete");
    }

    @PostLoad
    public void postLoad() {
        System.out.println("After load from DB");
    }
}

16. Soft Delete Pattern

java
@Entity
public class User {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @Column(name = "deleted")
    private boolean deleted = false;
}
java
@Repository
public interface UserRepository extends JpaRepository<User, Long> {

    // Automatically filter deleted records
    @Query("SELECT u FROM User u WHERE u.deleted = false")
    List<User> findAllActive();

    // Soft delete
    @Modifying
    @Transactional
    @Query("UPDATE User u SET u.deleted = true WHERE u.id = :id")
    void softDelete(@Param("id") Long id);
}

For automatic filtering, use Hibernate's @SQLRestriction (Hibernate 6+):

java
@Entity
@SQLRestriction("deleted = false")
public class User { ... }

17. Common Pitfalls & Best Practices

PitfallFix
N+1 queriesUse JOIN FETCH or @EntityGraph
EAGER fetch on collectionsAlways use LAZY for collections
CascadeType.ALL on @ManyToManyUse only PERSIST + MERGE
Missing @Transactional on @ModifyingAlways add it
Loading full entities for read-only viewsUse projections/DTOs
Using create-drop in productionUse validate or none
Exposing entities directly in RESTUse DTOs
Not using Optional with findByIdAlways unwrap with orElseThrow


Interview Questions — Spring Data JPA


Beginner Level

Q1. What is JPA? What is Hibernate? How do they relate?

JPA (Java Persistence API) is a specification (set of interfaces/rules) for ORM in Java. Hibernate is the most popular implementation of JPA. Spring Boot uses Hibernate as the default JPA provider. Analogy: JPA = JDBC interface, Hibernate = MySQL JDBC Driver.


Q2. What is the difference between CrudRepository, JpaRepository, and PagingAndSortingRepository?

  • CrudRepository — basic CRUD operations
  • PagingAndSortingRepository — adds findAll(Pageable) and findAll(Sort)
  • JpaRepository — extends both + adds JPA-specific methods like flush(), saveAndFlush(), deleteInBatch()

In practice, always extend JpaRepository unless you want to limit the API surface.


Q3. What is the difference between save() and saveAndFlush()?

save() — persists to the persistence context (first-level cache), may not immediately hit DB saveAndFlush() — saves AND immediately synchronizes with DB Use saveAndFlush() when you need the DB to reflect changes right away (e.g., within the same transaction).


Q4. What does @Transactional do?

It wraps the method in a database transaction. If the method completes successfully, the transaction commits. If a RuntimeException is thrown, it rolls back automatically. Spring applies it via AOP (proxy-based), so it only works on public methods called from outside the bean.


Q5. What is Optional and why does findById() return it?

Optional<T> is a container that may or may not contain a value. It forces the caller to explicitly handle the "not found" case, avoiding NullPointerException.

java
User user = userRepository.findById(id)
    .orElseThrow(() -> new EntityNotFoundException("User not found: " + id));

Q6. What is the difference between @Entity and @Table?

@Entity — marks the class as a JPA entity (mandatory) @Table — optional, specifies the actual table name, schema, unique constraints If @Table is omitted, the table name defaults to the class name.


Q7. What is ddl-auto and which value should be used in production?

Controls how Hibernate manages schema creation. Use validate or none in production. Never use create or create-drop in production — they destroy data.


Intermediate Level

Q8. What is the N+1 problem? How do you fix it?

When fetching a list of entities and then accessing their lazy-loaded associations, JPA fires one query for the parent list + N queries (one per parent) for the children.

Fix:

java
// JPQL JOIN FETCH
@Query("SELECT d FROM Department d JOIN FETCH d.employees")
List<Department> findAllWithEmployees();

// or @EntityGraph
@EntityGraph(attributePaths = {"employees"})
List<Department> findAll();

Q9. What is the difference between LAZY and EAGER fetching? Which should you prefer?

LAZY — related data is loaded only when accessed (proxy object until then) EAGER — related data is loaded immediately with the parent

Always prefer LAZY for collections (@OneToMany, @ManyToMany). EAGER causes unnecessary data loading and is the main cause of N+1 and performance issues.


Q10. What is orphanRemoval? How is it different from CascadeType.REMOVE?

  • CascadeType.REMOVE — deletes children when the parent is deleted
  • orphanRemoval = true — deletes children when they are removed from the parent's collection (even if the parent is not deleted)
java
parent.getChildren().remove(child); // triggers DELETE if orphanRemoval=true

Q11. What is the difference between @Query with JPQL and native query?

JPQL — queries against entity model (portable, DB-independent, uses class/field names) Native — raw SQL (DB-specific syntax, nativeQuery = true) Prefer JPQL; use native queries for complex SQL not expressible in JPQL.


Q12. How does @Modifying work? Why is @Transactional required with it?

@Modifying tells Spring Data that the @Query is an UPDATE or DELETE (not a SELECT), so it should return int (affected rows) or void. @Transactional is required because write operations must run inside a transaction. Without it, Spring throws TransactionRequiredException.


Q13. What is the difference between Propagation.REQUIRED and Propagation.REQUIRES_NEW?

REQUIRED — joins existing transaction if one exists; creates new if none REQUIRES_NEW — always creates a new transaction, suspending the outer one. Used when you need the inner operation to commit independently (e.g., audit logging that must persist even if the outer transaction rolls back).


Q14. What is a Specification in Spring Data JPA? When would you use it?

Specification<T> is an interface to build dynamic, composable JPA Criteria queries. Use it when query conditions vary at runtime (e.g., a search API with optional filters). It avoids hardcoding many findByXAndY methods.


Q15. What is a projection? What are the types?

A projection fetches only a subset of entity fields instead of the whole entity. Types:

  1. Interface projection — Spring generates a proxy implementing the interface
  2. Class-based / DTO projection — uses constructor expressions in JPQL
  3. Dynamic projection — caller passes Class<T> to determine return type at runtime

Q16. What is @MappedSuperclass? How is it different from @Inheritance?

@MappedSuperclass — parent class whose fields are mapped into each child entity's own table. The parent itself has no table. @Inheritance — maps an entity class hierarchy to DB tables using strategies (SINGLE_TABLE, TABLE_PER_CLASS, JOINED).


Q17. What is First-Level Cache (L1) and Second-Level Cache (L2)?

L1 (Session Cache) — Hibernate's default per-transaction cache. Within a session/transaction, the same entity is not fetched from DB twice. L2 (Shared Cache) — Optional, spans multiple sessions. Configured with providers like EhCache or Redis.

java
@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)  // L2 cache
public class Product { }

Advanced Level

Q18. How does Spring Data JPA's @Transactional work internally?

Spring wraps the bean in a JDK dynamic proxy (or CGLIB proxy). When a @Transactional method is called from outside the bean, the proxy intercepts the call:

  1. Opens a transaction (or joins existing)
  2. Invokes the actual method
  3. Commits on success, rolls back on RuntimeException

Self-invocation problem: Calling a @Transactional method from within the same class bypasses the proxy, so the transaction is NOT applied.


Q19. What is the difference between EntityManager.persist(), merge(), detach(), and remove()?

MethodState Transition
persist(entity)Transient → Managed (INSERT on flush)
merge(entity)Detached → Managed copy returned (UPDATE on flush)
detach(entity)Managed → Detached (no longer tracked)
remove(entity)Managed → Removed (DELETE on flush)
refresh(entity)Overwrites entity with DB state

Q20. How would you implement soft delete globally using Hibernate?

Use @SQLRestriction (Hibernate 6+) / @Where (older Hibernate) on the entity so all queries automatically filter deleted records:

java
@Entity
@SQLRestriction("deleted = false")
public class User {
    private boolean deleted = false;
}

Override delete in the repository with a custom @Modifying update query. To query deleted records, use nativeQuery = true (which bypasses the restriction).


Q21. What is the Open Session in View anti-pattern?

OSIV keeps the Hibernate session open for the entire HTTP request, allowing lazy loading in the view layer. It is enabled by default in Spring Boot.

Problem: Database connections are held for too long; lazy loading in views leads to unexpected queries.

Best practice: Disable OSIV and use DTOs/projections to load all data in the service layer.

properties
spring.jpa.open-in-view=false

Q22. How do you handle optimistic locking in JPA?

Use @Version on a numeric field. JPA checks this version on UPDATE; if another transaction already changed it, OptimisticLockException is thrown.

java
@Entity
public class Product {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private int stock;

    @Version
    private int version;  // auto-incremented on every update
}

Q23. What is the difference between @NamedQuery and @Query?

@NamedQuery — defined at the entity class level, compiled/validated at startup, slightly faster @Query — defined at the repository method level, more flexible and readable

Spring Data JPA's @Query is preferred in modern applications.


Q24. How do you perform batch inserts in Spring Data JPA?

  1. Set spring.jpa.properties.hibernate.jdbc.batch_size=50
  2. Use SEQUENCE or TABLE ID generation (not IDENTITY — it disables batching in Hibernate)
  3. Use saveAll() and call flush() + clear() periodically
java
@Transactional
public void batchInsert(List<User> users) {
    for (int i = 0; i < users.size(); i++) {
        entityManager.persist(users.get(i));
        if (i % 50 == 0) {
            entityManager.flush();
            entityManager.clear();
        }
    }
}

Q25. When would you use EntityManager directly instead of a repository?

Use EntityManager directly when:

  • Performing complex Criteria API queries
  • Bulk operations with flush()/clear() for batch processing
  • Dynamic query building not suited for Specifications
  • Fine-grained control over entity state (persist, merge, detach)
java
@PersistenceContext
private EntityManager entityManager;

Read. Learn. Build. Repeat.