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.
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
LongorInteger)
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:
Book savedBook = bookRepository.save(newBook("Spring Boot","John Doe",29.99));// If id is null → INSERT, otherwise → UPDATEfindById()
Retrieves an entity by its primary key (returns an Optional):
Optional<Book> book = bookRepository.findById(1L);
book.ifPresent(b ->System.out.println(b.getTitle()));findAll()
Fetches all entities from the database:
List<Book> books = bookRepository.findAll();delete() / deleteById()
Removes an entity from the database:
bookRepository.deleteById(1L);// Or pass the entire entity object3. Advanced Operations
Pagination and Sorting
// Fetch first 10 records (page index starts from 0)Page<Book> page = bookRepository.findAll(PageRequest.of(0,10));// Sort by title ascendingList<Book> sortedBooks = bookRepository.findAll(Sort.by("title").ascending());// 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:
List<Book>findByAuthor(String author);List<Book>findByTitleContaining(String keyword);// Spring automatically translates these to SQL!@Query("SELECT b FROM Book b WHERE b.price > :price")List<Book>findBooksCostlierThan(@Param("price")double price);4. Complete CRUD Application Example
@Getter@Setter@EntitypublicclassBook{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;privateString title;privateString author;privatedouble price;}publicinterfaceBookRepositoryextendsJpaRepository<Book,Long>{}@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);}}@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).
@EntitypublicclassBook{// Fields and methods}@Id and @GeneratedValue
@Id marks the primary key. @GeneratedValue auto-generates the ID.
@EntitypublicclassBook{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;privateString title;privateString author;}@Column
Customizes column properties like name, length, and nullability.
@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).
@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).
@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).
@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.
@EntitypublicclassBook{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;@ManyToOneprivateAuthor author;}@ManyToMany
Multiple entities associated with multiple others (e.g., Books ↔ Categories).
@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
@EntitypublicclassAuthor{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;@Column(nullable =false)privateString name;@OneToMany(mappedBy ="author", cascade =CascadeType.ALL)privateList<Book> books;}@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;}@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 Type | Description |
|---|---|
| PERSIST | When parent is saved, related entities are also saved |
| MERGE | When parent is updated, related entities are updated |
| REMOVE | When parent is deleted, related entities are deleted |
| REFRESH | Refreshes related entities when parent is refreshed |
| DETACH | Detaches related entities when parent is detached |
| ALL | All 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
@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
@EntitypublicclassAuthor{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;privateString name;// Forward reference - WILL be serialized@OneToMany(mappedBy ="author", cascade =CascadeType.ALL)@JsonManagedReferenceprivateList<Book> books;}@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
{"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:
@ManyToOne@JoinColumn(name ="author_id")@JsonIgnore// This field will never appear in JSONprivateAuthor author;💡 When to Use Which?
- • Use
@JsonManagedReference/@JsonBackReferencewhen you need the relationship visible on one side - • Use
@JsonIgnorewhen 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
@EntitypublicclassAuthor{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;privateString name;// Always fetches books when fetching author@OneToMany(fetch =FetchType.EAGER)privateList<Book> books;}@EntitypublicclassAuthor{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;privateString name;// Fetches books only when accessed@OneToMany(fetch =FetchType.LAZY)privateList<Book> books;}@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:
// 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