Back to Master Spring Data JPA
    Topic 2

    JPA Repository Interface & Entity Mapping

    Master JpaRepository, Entity Mapping annotations, Relationships, and Fetching Strategies

    Part 1: JpaRepository Interface

    1. What is JpaRepository?

    The JpaRepository interface is part of the Spring Data JPA framework and extends bothCrudRepository and PagingAndSortingRepository. It adds JPA-specific methods for database operations and provides a wide range of pre-built methods for CRUD functionality.

    JpaRepository Signature
    publicinterfaceJpaRepository<T, ID>extendsPagingAndSortingRepository<T, ID>{// Methods for CRUD operations}
    • T - The entity type that will be managed
    • ID - The type of the entity's identifier (usually Long or Integer)

    By extending JpaRepository, you can directly call its methods to manage data in your database, without writing any SQL queries or implementing complex logic.

    2. Common Methods in JpaRepository

    save()

    Used to either create a new entity or update an existing one:

    Java Example
    Book savedBook = bookRepository.save(newBook("Spring Boot","John Doe",29.99));// If id is null → INSERT, otherwise → UPDATE

    findById()

    Retrieves an entity by its primary key (returns an Optional):

    Java Example
    Optional<Book> book = bookRepository.findById(1L);
    book.ifPresent(b ->System.out.println(b.getTitle()));

    findAll()

    Fetches all entities from the database:

    Java Example
    List<Book> books = bookRepository.findAll();

    delete() / deleteById()

    Removes an entity from the database:

    Java Example
    bookRepository.deleteById(1L);// Or pass the entire entity object

    3. Advanced Operations

    Pagination and Sorting

    Pagination
    // Fetch first 10 records (page index starts from 0)Page<Book> page = bookRepository.findAll(PageRequest.of(0,10));
    Sorting
    // Sort by title ascendingList<Book> sortedBooks = bookRepository.findAll(Sort.by("title").ascending());
    Combined Pagination & Sorting
    // First 5 records sorted by price descendingPage<Book> sortedPage = bookRepository.findAll(PageRequest.of(0,5,Sort.by("price").descending()));

    Custom Query Methods

    Define custom queries by following naming conventions or using @Query:

    Method Name Queries
    List<Book>findByAuthor(String author);List<Book>findByTitleContaining(String keyword);// Spring automatically translates these to SQL!
    Custom JPQL Queries
    @Query("SELECT b FROM Book b WHERE b.price > :price")List<Book>findBooksCostlierThan(@Param("price")double price);

    4. Complete CRUD Application Example

    Book.java (Entity)
    @Getter@Setter@EntitypublicclassBook{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;privateString title;privateString author;privatedouble price;}
    BookRepository.java
    publicinterfaceBookRepositoryextendsJpaRepository<Book,Long>{}
    BookService.java
    @ServicepublicclassBookService{@AutowiredprivateBookRepository bookRepository;publicList<Book>getAllBooks(){return bookRepository.findAll();}publicBookgetBookById(Long id){return bookRepository.findById(id).orElseThrow(()->newRuntimeException("Book not found"));}publicBookcreateOrUpdateBook(Book book){return bookRepository.save(book);}publicvoiddeleteBook(Long id){
    bookRepository.deleteById(id);}}
    BookController.java
    @RestController@RequestMapping("/books")publicclassBookController{@AutowiredprivateBookService bookService;@GetMappingpublicList<Book>getAllBooks(){return bookService.getAllBooks();}@GetMapping("/{id}")publicBookgetBookById(@PathVariableLong id){return bookService.getBookById(id);}@PostMappingpublicBookcreateBook(@RequestBodyBook book){return bookService.createOrUpdateBook(book);}@PutMapping("/{id}")publicBookupdateBook(@PathVariableLong id,@RequestBodyBook book){
    book.setId(id);return bookService.createOrUpdateBook(book);}@DeleteMapping("/{id}")publicvoiddeleteBook(@PathVariableLong id){
    bookService.deleteBook(id);}}

    Part 2: Entity Mapping

    5. What is Entity Mapping?

    Entity mapping is the process of mapping a Java object (an entity) to a corresponding database table using annotations. Each field in the entity class is mapped to a column in the database, and relationships between entities are mapped to reflect foreign keys, join tables, etc.

    JPA provides a set of annotations that simplify this mapping process, eliminating the need to write SQL queries manually.

    6. Common JPA Annotations

    @Entity

    Marks a class as a JPA entity. JPA automatically maps it to a table (class name used by default).

    Java Example
    @EntitypublicclassBook{// Fields and methods}

    @Id and @GeneratedValue

    @Id marks the primary key. @GeneratedValue auto-generates the ID.

    Java Example
    @EntitypublicclassBook{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;privateString title;privateString author;}

    @Column

    Customizes column properties like name, length, and nullability.

    Java Example
    @EntitypublicclassBook{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;@Column(name ="book_title", length =100, nullable =false)privateString title;@Column(nullable =false)privateString author;}

    @Table

    Specifies a custom table name (different from the entity class name).

    Java Example
    @Entity@Table(name ="library_books")publicclassBook{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;// ... fields}

    7. Defining Relationships Between Entities

    @OneToOne

    Maps one entity to another (e.g., each Author has one Address).

    Java Example
    @EntitypublicclassAuthor{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;privateString name;@OneToOneprivateAddress address;}

    @OneToMany

    One entity can have multiple related entities (e.g., one Author writes many Books).

    Java Example
    @EntitypublicclassAuthor{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;privateString name;@OneToMany(mappedBy ="author")privateList<Book> books;}

    @ManyToOne

    Inverse of @OneToMany. Many Books can belong to a single Author.

    Java Example
    @EntitypublicclassBook{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;@ManyToOneprivateAuthor author;}

    @ManyToMany

    Multiple entities associated with multiple others (e.g., Books ↔ Categories).

    Java Example
    @EntitypublicclassBook{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;@ManyToMany@JoinTable(
    name ="book_category",
    joinColumns =@JoinColumn(name ="book_id"),
    inverseJoinColumns =@JoinColumn(name ="category_id"))privateSet<Category> categories;}

    8. Complete Entity Mapping Example

    Author.java
    @EntitypublicclassAuthor{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;@Column(nullable =false)privateString name;@OneToMany(mappedBy ="author", cascade =CascadeType.ALL)privateList<Book> books;}
    Book.java
    @EntitypublicclassBook{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;@Column(nullable =false)privateString title;@ManyToOne@JoinColumn(name ="author_id")privateAuthor author;@ManyToMany@JoinTable(
    name ="book_category",
    joinColumns =@JoinColumn(name ="book_id"),
    inverseJoinColumns =@JoinColumn(name ="category_id"))privateSet<Category> categories;}
    Category.java
    @EntitypublicclassCategory{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;@Column(nullable =false)privateString name;@ManyToMany(mappedBy ="categories")privateSet<Book> books;}

    9. Understanding CascadeType.ALL

    Cascading allows you to propagate certain operations (like saving, deleting, or updating) from a parent entity to its related entities automatically.

    Cascade TypeDescription
    PERSISTWhen parent is saved, related entities are also saved
    MERGEWhen parent is updated, related entities are updated
    REMOVEWhen parent is deleted, related entities are deleted
    REFRESHRefreshes related entities when parent is refreshed
    DETACHDetaches related entities when parent is detached
    ALLAll of the above operations are applied

    ⚠️ Warning: Use cascading carefully! CascadeType.REMOVE will delete all related entities when deleting the parent, which may not always be desirable.

    10. The @Transient Annotation

    The @Transient annotation is used to mark a field that should NOT be persisted to the database. JPA will ignore this field during entity persistence operations.

    💡 When to Use @Transient?

    • Calculated fields - Values derived from other fields (e.g., fullName from firstName + lastName)
    • Temporary data - Data needed only during runtime but not stored
    • Sensitive data - Data that should never be saved to database
    • Helper fields - Fields used for business logic that don't need persistence
    @Transient Example
    @EntitypublicclassEmployee{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;privateString firstName;privateString lastName;privatedouble salary;// This field will NOT be saved to database@TransientprivateString fullName;// This field will NOT be saved to database@Transientprivatedouble bonus;// Getter that calculates fullName dynamicallypublicStringgetFullName(){return firstName +" "+ lastName;}// Calculate bonus based on salary (not persisted)publicdoublegetBonus(){return salary *0.10;}}

    ✅ Benefits

    • • Keeps database schema clean
    • • Reduces storage overhead
    • • Perfect for computed properties

    ⚠️ Important Note

    @Transient fields are null when entity is loaded from database. Use getter methods to calculate values dynamically.

    11. @JsonManagedReference & @JsonBackReference

    When using bidirectional relationships in JPA entities, you may encounter infinite recursion issues during JSON serialization (e.g., when returning entities from REST APIs). Jackson provides two annotations to solve this problem.

    ❌ The Problem: Infinite Recursion

    Without these annotations, serializing an Author with Books causes:

    Author → Books → Author → Books → Author → ... (StackOverflowError)

    @JsonManagedReference

    • • Applied on the "forward" part of reference
    • • Usually on the parent side (OneToMany)
    • • This reference will be serialized

    @JsonBackReference

    • • Applied on the "back" part of reference
    • • Usually on the child side (ManyToOne)
    • • This reference will NOT be serialized
    Author.java - Parent Entity
    @EntitypublicclassAuthor{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;privateString name;// Forward reference - WILL be serialized@OneToMany(mappedBy ="author", cascade =CascadeType.ALL)@JsonManagedReferenceprivateList<Book> books;}
    Book.java - Child Entity
    @EntitypublicclassBook{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;privateString title;// Back reference - will NOT be serialized (prevents recursion)@ManyToOne@JoinColumn(name ="author_id")@JsonBackReferenceprivateAuthor author;}

    ✅ Result: Clean JSON Output

    Json Example
    {"id":1,"name":"John Doe","books":[{"id":1,"title":"Spring Boot Guide"},{"id":2,"title":"JPA Mastery"}]}// Note: The "author" field inside books is NOT included (no recursion!)

    Alternative: @JsonIgnore

    A simpler alternative is @JsonIgnore, which completely excludes a field from JSON serialization:

    Java Example
    @ManyToOne@JoinColumn(name ="author_id")@JsonIgnore// This field will never appear in JSONprivateAuthor author;

    💡 When to Use Which?

    • • Use @JsonManagedReference/@JsonBackReference when you need the relationship visible on one side
    • • Use @JsonIgnore when you never want the field in JSON
    • • Consider using DTOs for more control over API responses

    Part 3: Fetching Strategies

    12. Fetching Strategies in JPA

    Fetching strategies determine how and when related entities are loaded from the database. These strategies are essential for managing performance, particularly with large datasets and complex relationships.

    EAGER Fetching

    Loads related entities immediately with the parent.

    • ✅ Reduces queries (single fetch)
    • ❌ Can cause overhead if data not always needed

    LAZY Fetching

    Loads related entities on-demand when accessed.

    • ✅ Improves performance (load only when needed)
    • ❌ Can cause N+1 select problem

    13. Fetching Strategy Examples

    Eager Fetching - OneToMany
    @EntitypublicclassAuthor{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;privateString name;// Always fetches books when fetching author@OneToMany(fetch =FetchType.EAGER)privateList<Book> books;}
    Lazy Fetching - OneToMany
    @EntitypublicclassAuthor{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;privateString name;// Fetches books only when accessed@OneToMany(fetch =FetchType.LAZY)privateList<Book> books;}
    Eager Fetching - ManyToOne
    @EntitypublicclassEmployee{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;privateString name;// Department fetched eagerly with Employee@ManyToOne(fetch =FetchType.EAGER)privateDepartment department;}

    ⚠️ N+1 Select Problem

    Lazy loading can trigger multiple queries when accessing related entities in a loop:

    Java Example
    // This triggers N+1 queries!List<Author> authors = entityManager.createQuery("SELECT a FROM Author a").getResultList();for(Author author : authors){System.out.println(author.getBooks());// Separate query for each author!}

    14. Choosing the Right Fetching Strategy

    Use EAGER when you always need the associated entities (e.g., OneToOne relationships)

    Use LAZY when associated entities are not always required (e.g., large OneToMany collections)

    Use JOIN FETCH in JPQL to avoid N+1 problem with lazy loading

    Default strategies: @OneToMany and @ManyToMany are LAZY by default; @OneToOne and @ManyToOne are EAGER

    💬 Comments & Discussion