<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Spring Data JPA Tutorial — Complete Guide on Devops Monk</title><link>https://devops-monk.com/tutorials/spring-data-jpa/</link><description>Recent content in Spring Data JPA Tutorial — Complete Guide on Devops Monk</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Mon, 04 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://devops-monk.com/tutorials/spring-data-jpa/index.xml" rel="self" type="application/rss+xml"/><item><title>@MappedSuperclass: Sharing Fields Without Inheritance Tables</title><link>https://devops-monk.com/tutorials/spring-data-jpa/mapped-superclass/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/mapped-superclass/</guid><description>Introduction Almost every entity in a real application shares a few common fields: id, createdAt, updatedAt, and perhaps version. Writing these in every entity class is repetitive and error-prone. @MappedSuperclass lets you define them once in a base class that all entities extend — without creating a parent table or any inheritance mapping in the database.
What Is @MappedSuperclass? @MappedSuperclass marks a class whose field mappings are inherited by subclass entities.</description></item><item><title>@Transactional in Depth: Propagation, Isolation, and Rollback</title><link>https://devops-monk.com/tutorials/spring-data-jpa/transactional-propagation-isolation/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/transactional-propagation-isolation/</guid><description>What @Transactional Does @Transactional tells Spring to wrap the annotated method in a database transaction. Spring opens the transaction before the method starts and commits (or rolls back) when it ends. The annotation works via an AOP proxy — understanding this proxy model is essential for avoiding the most common @Transactional bugs.
The AOP Proxy Model Spring creates a proxy around your bean. When code outside the bean calls a @Transactional method, the call goes through the proxy, which manages the transaction:</description></item><item><title>Auditing: @CreatedDate, @LastModifiedBy, and Hibernate Envers</title><link>https://devops-monk.com/tutorials/spring-data-jpa/auditing/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/auditing/</guid><description>Why Auditing Matters Production systems must answer: &amp;ldquo;Who changed this? When? What did it look like before?&amp;rdquo; Without auditing infrastructure, you add created_at, updated_at, created_by, updated_by columns manually to every entity — dozens of lines of boilerplate repeated everywhere.
Spring Data JPA auditing fills these fields automatically. Hibernate Envers goes further: it captures every revision of every entity in separate audit tables.
Spring Data JPA Auditing Enable Auditing @Configuration @EnableJpaAuditing(auditorAwareRef = &amp;#34;auditorProvider&amp;#34;) public class JpaAuditingConfig { @Bean public AuditorAware&amp;lt;String&amp;gt; auditorProvider() { return () -&amp;gt; Optional.</description></item><item><title>Basic Entity Mapping: @Entity, @Table, @Id, @Column</title><link>https://devops-monk.com/tutorials/spring-data-jpa/basic-entity-mapping/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/basic-entity-mapping/</guid><description>Introduction Entity mapping is the process of telling Hibernate how your Java class corresponds to a database table, and how each field maps to a column. JPA provides a rich set of annotations for this — from the minimal @Entity and @Id to the detailed @Column with constraints.
This article covers every annotation and attribute you need to map entities precisely and confidently.
@Entity @Entity marks a class as a JPA entity — a Java object that maps to a database table.</description></item><item><title>Cascade Types and Orphan Removal: Managing Lifecycle Propagation</title><link>https://devops-monk.com/tutorials/spring-data-jpa/cascade-types-orphan-removal/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/cascade-types-orphan-removal/</guid><description>Introduction Cascade types control which persistence operations (save, merge, delete, refresh) propagate from a parent entity to its associated children. orphanRemoval controls what happens when a child is removed from the parent&amp;rsquo;s collection. Getting cascade wrong is one of the most common causes of unexpected deletes and data integrity issues in JPA applications.
The Six Cascade Types cascade = CascadeType.PERSIST // propagate persist (save new entity) cascade = CascadeType.MERGE // propagate merge (re-attach detached entity) cascade = CascadeType.</description></item><item><title>Custom Queries with @Query: JPQL and Native SQL</title><link>https://devops-monk.com/tutorials/spring-data-jpa/custom-queries-jpql-native-sql/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/custom-queries-jpql-native-sql/</guid><description>Why @Query Exists Derived query methods (Article 17) are great for simple conditions, but they break down fast. A method like findByOrderStatusAndCustomerCityAndPriceBetweenOrderByCreatedAtDesc is unreadable and still cannot express a JOIN or aggregation.
@Query lets you write the exact JPQL or SQL you need, attached directly to a repository method — keeping your repository clean while giving you full query control.
JPQL vs Native SQL JPQL Native SQL Language Entity/field names Table/column names Database portable Yes No JOIN FETCH Yes No Window functions No Yes Full-text search No Yes Complex analytics Limited Full power Start with JPQL.</description></item><item><title>Custom Type Conversions: AttributeConverter and Enums</title><link>https://devops-monk.com/tutorials/spring-data-jpa/custom-type-conversions/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/custom-type-conversions/</guid><description>Introduction JPA handles most Java types automatically — String, Integer, LocalDate, BigDecimal. But what about a Java enum? A List&amp;lt;String&amp;gt; stored as JSON? A custom Money type? JPA&amp;rsquo;s AttributeConverter and @Enumerated let you control exactly how any Java type maps to a database column.
Mapping Enums with @Enumerated Java enums are common in domain models. JPA maps them with @Enumerated:
public enum OrderStatus { PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED } public enum ProductStatus { ACTIVE, INACTIVE, DISCONTINUED } @Column(length = 20, nullable = false) @Enumerated(EnumType.</description></item><item><title>Dirty Checking, Flush Modes, and the First-Level Cache</title><link>https://devops-monk.com/tutorials/spring-data-jpa/dirty-checking-flush-modes/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/dirty-checking-flush-modes/</guid><description>The Persistence Context Revisited Article 3 introduced the persistence context — the in-memory cache of managed entities. This article goes deeper: how does the persistence context detect changes? When does it flush them to the database? And how can you tune this behaviour?
Dirty Checking When you modify a managed entity, you don&amp;rsquo;t call save() or update(). You simply change the field:
@Transactional public void giveDiscount(Long productId, BigDecimal discount) { Product product = productRepository.</description></item><item><title>Embedded Types and Value Objects: @Embeddable and @Embedded</title><link>https://devops-monk.com/tutorials/spring-data-jpa/embedded-types-value-objects/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/embedded-types-value-objects/</guid><description>Introduction Not every concept in your domain deserves its own table. An Address — street, city, state, postal code — is a value object: it has no identity of its own, it belongs to an entity. JPA&amp;rsquo;s @Embeddable lets you model this as a separate Java class while storing it in the owning entity&amp;rsquo;s table.
What Is an Embeddable? An @Embeddable class is a Java class whose fields are mapped to columns in the owning entity&amp;rsquo;s table — not a separate table.</description></item><item><title>Entity Graphs and Batch Loading: Precision Fetching</title><link>https://devops-monk.com/tutorials/spring-data-jpa/entity-graphs-batch-loading/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/entity-graphs-batch-loading/</guid><description>The Problem @EntityGraph Solves JOIN FETCH in @Query solves N+1 but creates inflexibility: the fetch plan is baked into the query. Two different use cases — an order detail page (needs items + customer) and an order list page (needs only customer) — require two different queries with different JOIN FETCHes.
@EntityGraph separates the fetch plan from the query. You define what to load at the call site, and Spring Data JPA generates the appropriate JOIN.</description></item><item><title>Entity Lifecycle States: Transient, Managed, Detached, Removed</title><link>https://devops-monk.com/tutorials/spring-data-jpa/entity-lifecycle-states/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/entity-lifecycle-states/</guid><description>Introduction Every JPA entity is always in one of four states. The state determines whether Hibernate is tracking the entity, whether changes are detected automatically, and what operations are valid. Understanding these states explains a large class of JPA bugs — especially the dreaded LazyInitializationException and detached entity errors.
The Four States new Customer() │ │ persist / save() ▼ TRANSIENT ──────────────────────► MANAGED (not tracked) (tracked by persistence context) │ │ evict / clear / close ▼ DETACHED (no longer tracked) │ │ merge() ▼ MANAGED (re-attached) MANAGED ─── remove / delete() ──► REMOVED (delete scheduled, still in context) 1.</description></item><item><title>Fetch Types: EAGER vs LAZY Loading</title><link>https://devops-monk.com/tutorials/spring-data-jpa/fetch-types-eager-vs-lazy/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/fetch-types-eager-vs-lazy/</guid><description>Introduction Every relationship in JPA has a fetch type: either EAGER (load immediately with the parent) or LAZY (load only when accessed). The defaults are counterintuitive, and getting fetch types wrong is one of the top causes of performance problems and LazyInitializationException in JPA applications.
Default Fetch Types Annotation Default fetch type Performance risk @ManyToOne EAGER Loads related entity on every query — can be expensive @OneToOne EAGER Same — loads profile/address on every customer load @OneToMany LAZY Correct default — collections are only loaded when needed @ManyToMany LAZY Correct default The defaults for @ManyToOne and @OneToOne are EAGER — a poor choice that the JPA spec made for historical reasons.</description></item><item><title>Inheritance Strategies: SINGLE_TABLE, JOINED, TABLE_PER_CLASS</title><link>https://devops-monk.com/tutorials/spring-data-jpa/inheritance-strategies/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/inheritance-strategies/</guid><description>Introduction Object-oriented code uses inheritance to share behaviour. Relational databases have no concept of inheritance. JPA bridges this gap with three strategies for mapping a class hierarchy to tables. Understanding when each is appropriate prevents schema headaches and performance problems.
The Domain Example An e-commerce system has different types of discount:
Discount (abstract) ├── PercentageDiscount (e.g., 10% off) └── FixedAmountDiscount (e.g., $5 off) All discounts share: id, name, validFrom, validUntil. PercentageDiscount adds: percentage (e.</description></item><item><title>Introduction to JPA, Hibernate, and Spring Data JPA</title><link>https://devops-monk.com/tutorials/spring-data-jpa/introduction-jpa-hibernate-spring-data/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/introduction-jpa-hibernate-spring-data/</guid><description>Introduction Every Spring Boot application that touches a relational database eventually encounters three terms used almost interchangeably: JPA, Hibernate, and Spring Data JPA. They are related but distinct, and understanding the difference is essential before writing a single line of mapping code.
This article explains what each one is, how they fit together, and why this stack is the dominant approach to Java database access.
The Problem: Object-Relational Impedance Mismatch Java applications work with objects: classes, inheritance, collections, references.</description></item><item><title>Many-to-Many Relationships: @ManyToMany and Join Tables</title><link>https://devops-monk.com/tutorials/spring-data-jpa/many-to-many-relationships/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/many-to-many-relationships/</guid><description>Introduction A many-to-many relationship means records in table A can relate to many records in table B, and vice versa. Products have many tags; tags apply to many products. In SQL this requires a join table. In JPA, @ManyToMany and @JoinTable handle this automatically — but when you need extra data on the join (like a creation date or a relevance score), you need a different approach.
Simple @ManyToMany: Product and Tag products (*) ──── product_tags ──── (*) tags The product_tags join table has two columns: product_id and tag_id.</description></item><item><title>One-to-Many and Many-to-One: The Most Common Relationship</title><link>https://devops-monk.com/tutorials/spring-data-jpa/one-to-many-many-to-one/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/one-to-many-many-to-one/</guid><description>Introduction The one-to-many relationship is the most common in any domain model. An Order has many OrderItems. A Category has many Products. A Customer has many Orders. Understanding @OneToMany and @ManyToOne well — especially bidirectional mapping, collection types, and cascade configuration — is foundational to any JPA application.
The Domain Example orders (1) ──────────────── (*) order_items An order has many order items. Each order item belongs to exactly one order.</description></item><item><title>One-to-One Relationships: @OneToOne in Depth</title><link>https://devops-monk.com/tutorials/spring-data-jpa/one-to-one-relationships/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/one-to-one-relationships/</guid><description>Introduction A one-to-one relationship means one record in table A corresponds to exactly one record in table B. In JPA this is modelled with @OneToOne. Understanding where the foreign key lives, which side &amp;ldquo;owns&amp;rdquo; the relationship, and how cascade operations work is essential before moving to the more complex @OneToMany and @ManyToMany.
The Domain Example In the e-commerce system, a Customer has one CustomerProfile containing their preferences and biography. A profile belongs to exactly one customer.</description></item><item><title>Optimistic and Pessimistic Locking: Handling Concurrent Updates</title><link>https://devops-monk.com/tutorials/spring-data-jpa/optimistic-pessimistic-locking/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/optimistic-pessimistic-locking/</guid><description>The Lost Update Problem Two users edit the same product simultaneously:
Time User A User B T1 Load product (price=999) Load product (price=999) T2 Change price to 899 T3 Change price to 799 T4 Save → UPDATE price=899 T5 Save → UPDATE price=799 User A&amp;rsquo;s change is silently overwritten. This is the lost update problem. Both JPA locking strategies prevent it — in different ways.
Optimistic Locking with @Version Optimistic locking assumes conflicts are rare.</description></item><item><title>Pagination and Sorting with Pageable, Page, and Slice</title><link>https://devops-monk.com/tutorials/spring-data-jpa/pagination-sorting/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/pagination-sorting/</guid><description>Why Pagination Matters Returning all rows from a database table is one of the most common production incidents. A query that works in development with 100 rows silently becomes a 10-second, 2 GB memory spike when production has 2 million rows. Pagination limits how many rows travel from the database to the application at a time.
Spring Data JPA makes pagination a first-class feature — pass a Pageable to any repository method and get a Page&amp;lt;T&amp;gt; back.</description></item><item><title>Primary Keys and Generated Values: IDENTITY, SEQUENCE, UUID</title><link>https://devops-monk.com/tutorials/spring-data-jpa/primary-keys-generated-values/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/primary-keys-generated-values/</guid><description>Introduction Primary key choice affects performance, scalability, and application design. This article covers every strategy JPA supports — from the simple AUTO_INCREMENT to UUID and composite keys — with the trade-offs of each.
IDENTITY Strategy (MySQL AUTO_INCREMENT) GenerationType.IDENTITY delegates key generation to the database column&amp;rsquo;s auto-increment feature. This is the standard for MySQL.
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; Generated DDL (when ddl-auto=create):
id BIGINT NOT NULL AUTO_INCREMENT How IDENTITY works with Hibernate IDENTITY has one important behaviour: Hibernate cannot batch INSERT statements when using IDENTITY.</description></item><item><title>Projections and DTOs: Fetching Only What You Need</title><link>https://devops-monk.com/tutorials/spring-data-jpa/projections-dtos/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/projections-dtos/</guid><description>The Over-Fetching Problem By default, findAll() loads every column for every entity. A Product entity with 20 fields loads all 20 columns — even when a dropdown menu only needs id and name. This wastes bandwidth, memory, and query time.
Projections let you define what shape the result should take, and Spring Data JPA generates a query that fetches only those columns.
Interface Projections The simplest projection: declare an interface with getter methods matching entity field names.</description></item><item><title>Second-Level Cache and Query Cache with Hibernate</title><link>https://devops-monk.com/tutorials/spring-data-jpa/second-level-cache/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/second-level-cache/</guid><description>Cache Layers in Hibernate Hibernate has two cache levels:
Level Scope Lifetime Shared? First-level cache One Session (one transaction) Transaction No — per session Second-level cache SessionFactory Application lifetime Yes — across sessions The first-level cache (Article 24) prevents repeated reads within one transaction. The second-level cache prevents repeated reads across transactions — once an entity is loaded, it stays in the shared cache until evicted.
When to Use the Second-Level Cache Good candidates:</description></item><item><title>Setting Up Spring Boot with Spring Data JPA and MySQL</title><link>https://devops-monk.com/tutorials/spring-data-jpa/setup-spring-boot-spring-data-jpa/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/setup-spring-boot-spring-data-jpa/</guid><description>Introduction This article builds the project foundation used throughout the entire series. By the end, you will have a running Spring Boot 3.3 application connected to MySQL 8.x with Hibernate 6, a proper connection pool, schema management via Flyway, and SQL logging configured so you can see exactly what Hibernate sends to the database.
Project Setup Maven pom.xml &amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt; &amp;lt;project xmlns=&amp;#34;http://maven.apache.org/POM/4.0.0&amp;#34; xmlns:xsi=&amp;#34;http://www.w3.org/2001/XMLSchema-instance&amp;#34; xsi:schemaLocation=&amp;#34;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&amp;#34;&amp;gt; &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt; &amp;lt;parent&amp;gt; &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;spring-boot-starter-parent&amp;lt;/artifactId&amp;gt; &amp;lt;version&amp;gt;3.</description></item><item><title>Specifications and the Criteria API: Dynamic Queries</title><link>https://devops-monk.com/tutorials/spring-data-jpa/specifications-criteria-api/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/specifications-criteria-api/</guid><description>Why Specifications Derived query methods and @Query annotations work for fixed queries. But what happens when the user can filter by any combination of 10 fields — some optional, some not?
// Wrong approach — combinatorial explosion List&amp;lt;Product&amp;gt; findByName(String name); List&amp;lt;Product&amp;gt; findByNameAndPrice(String name, BigDecimal price); List&amp;lt;Product&amp;gt; findByNameAndPriceAndCategory(...); // ... you&amp;#39;d need 2^10 = 1024 methods The Specification pattern solves this. Each filter condition is a reusable Specification&amp;lt;T&amp;gt; object. You compose them with and(), or(), and not() at runtime, and Spring Data JPA generates the correct query.</description></item><item><title>Testing Spring Data JPA: @DataJpaTest, Testcontainers, and Best Practices</title><link>https://devops-monk.com/tutorials/spring-data-jpa/testing-spring-data-jpa/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/testing-spring-data-jpa/</guid><description>Why Test JPA Code? Unit tests with mocked repositories verify your service logic but nothing about the database. They won&amp;rsquo;t catch:
Wrong column mapping Constraint violations N+1 queries introduced by a new lazy association Transactions that silently don&amp;rsquo;t roll back Queries that fail on the real database but pass on H2 Testing against a real database — or at minimum a database-compatible in-memory store — is necessary. Spring Boot&amp;rsquo;s @DataJpaTest and Testcontainers make this practical.</description></item><item><title>The N+1 Problem: Detection, Root Cause, and All Solutions</title><link>https://devops-monk.com/tutorials/spring-data-jpa/n-plus-one-problem-solutions/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/n-plus-one-problem-solutions/</guid><description>What Is the N+1 Problem? The N+1 problem occurs when loading a list of N entities triggers N additional queries to load their associations — one query per entity.
Example: load 50 orders, then access each order&amp;rsquo;s customer:
SELECT * FROM orders; -- 1 query SELECT * FROM customers WHERE id = 1; -- query for order 1&amp;#39;s customer SELECT * FROM customers WHERE id = 2; -- query for order 2&amp;#39;s customer SELECT * FROM customers WHERE id = 3; -- query for order 3&amp;#39;s customer .</description></item><item><title>The Persistence Context: How JPA Tracks Your Entities</title><link>https://devops-monk.com/tutorials/spring-data-jpa/persistence-context-entity-lifecycle/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/persistence-context-entity-lifecycle/</guid><description>Introduction The persistence context is the single most important concept in JPA. Everything else — lazy loading, dirty checking, transaction scope, detached entity exceptions — only makes sense once you understand what the persistence context is and how it works.
Many JPA bugs come from developers not understanding this concept. Understand it well and the rest of JPA becomes predictable.
What Is the Persistence Context? The persistence context is an in-memory map of entities managed by the current EntityManager.</description></item><item><title>Transaction Boundaries and Common Pitfalls</title><link>https://devops-monk.com/tutorials/spring-data-jpa/transaction-boundaries-pitfalls/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-data-jpa/transaction-boundaries-pitfalls/</guid><description>Transaction Boundaries A transaction boundary is the point where a transaction starts and where it ends. In Spring, boundaries are defined by @Transactional on service methods.
HTTP Request └── Controller (no transaction) └── @Transactional Service method ← transaction OPENS here ├── Repository call 1 ├── Repository call 2 └── method returns ← transaction COMMITS here Everything inside the @Transactional method runs in the same database transaction. The persistence context (first-level cache) lives for the duration of that transaction.</description></item></channel></rss>