<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Silver_dev</title>
    <description>The latest articles on DEV Community by Silver_dev (@silver_dev).</description>
    <link>https://dev.to/silver_dev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3781206%2F5c3ab957-423b-4747-ab51-c5e23c2659d9.png</url>
      <title>DEV Community: Silver_dev</title>
      <link>https://dev.to/silver_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/silver_dev"/>
    <language>en</language>
    <item>
      <title>Hibernate Under the Hood. Part 7: Performance and Common Pitfalls</title>
      <dc:creator>Silver_dev</dc:creator>
      <pubDate>Wed, 21 Oct 2026 20:06:49 +0000</pubDate>
      <link>https://dev.to/silver_dev/hibernate-under-the-hood-part-7-performance-and-common-pitfalls-3ofa</link>
      <guid>https://dev.to/silver_dev/hibernate-under-the-hood-part-7-performance-and-common-pitfalls-3ofa</guid>
      <description>&lt;p&gt;Performance issues are rarely the fault of Hibernate itself; more often, they are caused by its misuse. The key to success is identifying these issues before they manifest in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Find Problems Before Production
&lt;/h2&gt;

&lt;p&gt;Performance issues are barely visible on a small test database with a single user, but they manifest catastrophically on real data under load. Therefore, you must start by configuring diagnostics.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. SQL and Hibernate Statistics Logging
&lt;/h3&gt;

&lt;p&gt;Enable SQL query and statistics logging to see exactly what Hibernate is sending to the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="c"&gt;# Show SQL in logs (in dev environment)
&lt;/span&gt;&lt;span class="py"&gt;spring.jpa.show-sql&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;spring.jpa.properties.hibernate.format_sql&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;

&lt;span class="c"&gt;# Collect detailed statistics (number of queries, execution time)
&lt;/span&gt;&lt;span class="py"&gt;hibernate.generate_statistics&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;logging.level.org.hibernate.stat&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;DEBUG&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Statistics will show the total number of queries, their execution time, and the most crucial metric — the number of JDBC calls per session.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Slow Query Logging
&lt;/h3&gt;

&lt;p&gt;Starting from Hibernate 5.4.5, you can specify a threshold, and Hibernate will log only those queries that take longer than that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="c"&gt;# Log queries that take longer than 100 ms
&lt;/span&gt;&lt;span class="py"&gt;hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;100&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helps quickly identify "heavy" (bottleneck) areas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem #1: N+1 (and How to Fix It)
&lt;/h2&gt;

&lt;p&gt;This is the most common cause of performance issues in Java applications using ORM. We have already discussed its mechanism: instead of a single query, Hibernate executes &lt;code&gt;1 + N&lt;/code&gt; queries.&lt;/p&gt;

&lt;p&gt;Here are the primary treatment strategies, from most preferred to auxiliary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strategy 1: &lt;code&gt;JOIN FETCH&lt;/code&gt; (or &lt;code&gt;@EntityGraph&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;This is the most direct and effective approach when you know in advance that the data will be needed. You explicitly instruct Hibernate to load the association in the same SQL query via a &lt;code&gt;JOIN&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In a JPQL query&lt;/span&gt;
&lt;span class="nd"&gt;@Query&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT o FROM Order o JOIN FETCH o.customer WHERE o.status = :status"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findWithCustomerByStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;Status&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;@EntityGraph&lt;/code&gt; does the same thing, but declaratively at the Spring Data repository level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@EntityGraph&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributePaths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"customer"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"items"&lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;
&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findByStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Status&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Pros: A single database query, maximum performance for known scenarios.&lt;/li&gt;
&lt;li&gt;Cons: Works cleanly only for a single collection association per query. Attempting a &lt;code&gt;JOIN FETCH&lt;/code&gt; on two &lt;code&gt;List&lt;/code&gt; collections simultaneously will result in a &lt;code&gt;MultipleBagFetchException&lt;/code&gt; or a Cartesian product.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Strategy 2: Batch Fetching (&lt;code&gt;@BatchSize&lt;/code&gt; / &lt;code&gt;default_batch_fetch_size&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;This is a "safety net" and an excellent option when you cannot (or do not want to) change all queries. Hibernate continues to use lazy loading, but when you access the first entity in a "batch", it collects the IDs of the rest and loads them in a single query with an &lt;code&gt;IN (...)&lt;/code&gt; clause.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="c"&gt;# Global setting: Hibernate will group lazy loads into batches of 50
&lt;/span&gt;&lt;span class="py"&gt;spring.jpa.properties.hibernate.default_batch_fetch_size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;50&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or targeted on a specific collection: &lt;code&gt;@BatchSize(size = 25)&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pros: Does not require query modifications, easy to enable globally, works excellently as a "safety net" against accidental N+1.&lt;/li&gt;
&lt;li&gt;Cons: Does not eliminate N+1 completely, but reduces the number of queries from &lt;code&gt;N&lt;/code&gt; to &lt;code&gt;N / batchSize&lt;/code&gt;. Several database round-trips remain.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Strategy 3: DTO Projections
&lt;/h3&gt;

&lt;p&gt;If you only need a few fields of the entity for reading, do not load the entire entity. Write a query that returns a DTO directly (e.g., a Java &lt;code&gt;record&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Query&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT new com.example.dto.OrderSummary(o.id, o.status, c.name) "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
       &lt;span class="s"&gt;"FROM Order o JOIN o.customer c WHERE o.status = :status"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderSummary&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findSummariesByStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;Status&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Pros: The fastest and safest approach for read-only endpoints. You load exactly the data you need, avoid creating managed entities, and completely eliminate N+1 and dirty checking.&lt;/li&gt;
&lt;li&gt;Cons: Suitable only for reading; update operations require managed entities.&lt;/li&gt;
&lt;li&gt;Selection hierarchy: Ideally, choose the strategy consciously for each access scenario. In practice, a combination is often used: &lt;code&gt;JOIN FETCH&lt;/code&gt; for known "hot" paths and &lt;code&gt;default_batch_fetch_size&lt;/code&gt; as a global safety net.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Problem #2: The &lt;code&gt;Open Session in View&lt;/code&gt; (OSIV) Anti-Pattern
&lt;/h2&gt;

&lt;p&gt;Spring Boot enables &lt;code&gt;spring.jpa.open-in-view=true&lt;/code&gt; by default. This means the Hibernate session remains open for the entire HTTP request, including response rendering or JSON serialization.&lt;/p&gt;

&lt;p&gt;Why this is dangerous:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hides N+1: When Jackson serializes an object and triggers a lazy collection, Hibernate silently executes a query. You might not even suspect this until you check the logs.&lt;/li&gt;
&lt;li&gt;Unpredictable queries: Database queries can be executed during the serialization phase, already after the transaction has been closed.&lt;/li&gt;
&lt;li&gt;Long-held connections: Although OSIV does not hold the physical connection open the entire time, it allows queries to be executed at any point in the request lifecycle, which can lead to unpredictable connection pool usage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Solution: Always disable OSIV in production.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.jpa.open-in-view&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After disabling it, any lazy loading issues will become explicit (&lt;code&gt;LazyInitializationException&lt;/code&gt;), forcing you to solve them correctly — via &lt;code&gt;JOIN FETCH&lt;/code&gt;, &lt;code&gt;@EntityGraph&lt;/code&gt;, or DTOs — rather than sweeping them under the rug.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem #3: Slow Batching (Bulk Inserts/Updates)
&lt;/h2&gt;

&lt;p&gt;If you save 1,000 entities via &lt;code&gt;saveAll()&lt;/code&gt;, Hibernate will execute 1,000 separate &lt;code&gt;INSERT&lt;/code&gt; statements by default, which can take minutes. Batching allows you to group them into a few packets.&lt;/p&gt;

&lt;p&gt;Configuration to enable batching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.jpa.properties.hibernate.jdbc.batch_size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;50&lt;/span&gt;
&lt;span class="py"&gt;spring.jpa.properties.hibernate.order_inserts&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;spring.jpa.properties.hibernate.order_updates&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;spring.jpa.properties.hibernate.jdbc.batch_versioned_data&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;batch_size&lt;/code&gt;: the number of statements in a single batch (optimal is 20–50).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;order_inserts&lt;/code&gt; / &lt;code&gt;order_updates&lt;/code&gt;: group similar operations together, which is necessary for effective batching.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Critical Pitfall: &lt;code&gt;GenerationType.IDENTITY&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If your entity uses &lt;code&gt;@GeneratedValue(strategy = GenerationType.IDENTITY)&lt;/code&gt;, batching will not work. Hibernate is forced to execute each &lt;code&gt;INSERT&lt;/code&gt; individually to immediately obtain the database-generated ID. Use &lt;code&gt;GenerationType.SEQUENCE&lt;/code&gt; instead. Hibernate can pre-allocate a block of IDs from the sequence and then execute all inserts in a single batch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Hibernate performance issues are almost always a consequence of its misuse, not its internal flaws.&lt;/li&gt;
&lt;li&gt;Diagnostics come first. Enable &lt;code&gt;show-sql&lt;/code&gt; and &lt;code&gt;generate_statistics&lt;/code&gt; in the dev environment to see the actual number and execution time of queries.&lt;/li&gt;
&lt;li&gt;N+1 is enemy #1. Treat it with &lt;code&gt;JOIN FETCH&lt;/code&gt; (for known scenarios) or &lt;code&gt;@BatchSize&lt;/code&gt; (as a global safety net). For read-only endpoints, DTO projections are the cleanest and fastest solution.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;open-in-view&lt;/code&gt; is an anti-pattern for REST APIs. Disable it (&lt;code&gt;spring.jpa.open-in-view=false&lt;/code&gt;). This will make lazy loading issues explicit and force you to solve them correctly.&lt;/li&gt;
&lt;li&gt;Batching does not work with &lt;code&gt;IDENTITY&lt;/code&gt;. Use &lt;code&gt;GenerationType.SEQUENCE&lt;/code&gt; for efficient bulk inserts.&lt;/li&gt;
&lt;li&gt;Consciously choose the fetching strategy for each scenario. &lt;code&gt;LAZY&lt;/code&gt; is the correct default, but it requires explicit management via &lt;code&gt;JOIN FETCH&lt;/code&gt;, &lt;code&gt;@EntityGraph&lt;/code&gt;, or DTOs, especially when OSIV is disabled.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>spring</category>
      <category>learning</category>
      <category>java</category>
    </item>
    <item>
      <title>Hibernate Under the Hood. Part 6: Transaction Management and Locking</title>
      <dc:creator>Silver_dev</dc:creator>
      <pubDate>Wed, 21 Oct 2026 19:46:56 +0000</pubDate>
      <link>https://dev.to/silver_dev/hibernate-under-the-hood-part-6-transaction-management-and-locking-3df4</link>
      <guid>https://dev.to/silver_dev/hibernate-under-the-hood-part-6-transaction-management-and-locking-3df4</guid>
      <description>&lt;p&gt;Hibernate and Spring ensure data integrity during concurrent operations. It is crucial to understand two key mechanisms here: transactions, which define the boundaries of a unit of work, and locks, which resolve conflicts during concurrent access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transaction Management in Spring and Hibernate
&lt;/h3&gt;

&lt;p&gt;At the core is the &lt;code&gt;@Transactional&lt;/code&gt; annotation, which wraps a method in a transaction via Spring AOP (proxies) and decides when to commit or roll it back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key point — Rollback Rules:&lt;/strong&gt;&lt;br&gt;
By default, Spring rolls back a transaction only for unchecked exceptions (&lt;code&gt;RuntimeException&lt;/code&gt; and &lt;code&gt;Error&lt;/code&gt;). For checked exceptions (subclasses of &lt;code&gt;Exception&lt;/code&gt;, but not &lt;code&gt;RuntimeException&lt;/code&gt;), the transaction commits by default. This is a frequent source of errors: a developer throws an &lt;code&gt;IOException&lt;/code&gt; and expects a rollback, but the data is partially saved.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Explicit rollback management&lt;/span&gt;
&lt;span class="nd"&gt;@Transactional&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;// rollback for checked exceptions&lt;/span&gt;
    &lt;span class="n"&gt;rollbackFor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;PaymentGatewayException&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt; &lt;span class="o"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// commit for RuntimeException (e.g., to retry later) &lt;/span&gt;
    &lt;span class="n"&gt;noRollbackFor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;OptimisticLockException&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt; 
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;processOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Important: A rollback will only trigger if the exception "escapes" the &lt;code&gt;@Transactional&lt;/code&gt; method. If you catch it inside the method, Spring will not know about it, and the transaction will commit.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Isolation Levels:&lt;/strong&gt;&lt;br&gt;
Spring allows you to explicitly specify the isolation level (&lt;code&gt;Isolation.READ_COMMITTED&lt;/code&gt;, &lt;code&gt;REPEATABLE_READ&lt;/code&gt;, etc.). By default, it uses &lt;code&gt;Isolation.DEFAULT&lt;/code&gt;, which relies on the underlying database's default settings. This can be dangerous if different environments (e.g., staging and production) use different databases with different default levels — the code's behavior will become unpredictable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Propagation:&lt;/strong&gt;&lt;br&gt;
Defines how a method's transaction relates to an existing one. For example, &lt;code&gt;Propagation.REQUIRES_NEW&lt;/code&gt; always creates a new transaction, suspending the outer one. This is useful for operations that must be committed independently (e.g., audit logging).&lt;/p&gt;

&lt;h3&gt;
  
  
  Locking: Conflict Resolution Strategies
&lt;/h3&gt;

&lt;p&gt;Hibernate, like JPA, offers two fundamentally different approaches to locking: optimistic and pessimistic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optimistic Locking&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Based on the assumption that conflicts are rare. Transactions do not lock data during reads, but before committing, they check if anyone else has modified the data since it was read.&lt;/p&gt;

&lt;p&gt;It is implemented via a field annotated with &lt;code&gt;@Version&lt;/code&gt; (a number or &lt;code&gt;Timestamp&lt;/code&gt;). On every update, Hibernate executes SQL like &lt;code&gt;UPDATE ... SET version = version + 1 WHERE id = ? AND version = ?&lt;/code&gt;. If no rows satisfy the &lt;code&gt;WHERE&lt;/code&gt; condition (because the version changed), an &lt;code&gt;OptimisticLockException&lt;/code&gt; is thrown.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When to use: For most web applications and scenarios with rare conflicts. It is a scalable and performant approach.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LockModeType&lt;/code&gt; modes: &lt;code&gt;OPTIMISTIC&lt;/code&gt; (version check on commit) and &lt;code&gt;OPTIMISTIC_FORCE_INCREMENT&lt;/code&gt; (forced version increment even without actual changes).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pessimistic Locking&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Based on the assumption that conflicts are likely. Data is locked at the database level for the duration of the transaction, preventing other transactions from modifying or reading it.&lt;/p&gt;

&lt;p&gt;It is implemented via &lt;code&gt;LockModeType&lt;/code&gt; (e.g., &lt;code&gt;PESSIMISTIC_WRITE&lt;/code&gt;), which translates to SQL &lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When to use: For critical operations with high contention (e.g., deducting money from an account), where rollbacks and retries are undesirable or too costly.&lt;/li&gt;
&lt;li&gt;Risks: Locks are held until the end of the transaction, which can lead to deadlocks and reduced throughput.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Typical Mistakes and Pitfalls
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Misunderstanding rollback rules: Expecting a rollback for a checked exception, or conversely, committing after a &lt;code&gt;RuntimeException&lt;/code&gt; that was caught and swallowed inside the method.&lt;/li&gt;
&lt;li&gt;"Poisoned" session: After an exception (e.g., &lt;code&gt;DataIntegrityViolationException&lt;/code&gt;), the Hibernate session may be marked as &lt;code&gt;rollback-only&lt;/code&gt;. Subsequent operations in this transaction will fail. The solution is to use &lt;code&gt;Propagation.REQUIRES_NEW&lt;/code&gt; to isolate the failing operation.&lt;/li&gt;
&lt;li&gt;Spurious &lt;code&gt;OptimisticLockException&lt;/code&gt;: Can occur when using &lt;code&gt;orphanRemoval&lt;/code&gt; and complex object graphs, where Hibernate attempts to update an already deleted entity. Sometimes &lt;code&gt;em.flush()&lt;/code&gt; followed by &lt;code&gt;em.clear()&lt;/code&gt; helps to synchronize the state.&lt;/li&gt;
&lt;li&gt;Mixing strategies: The presence of &lt;code&gt;@Version&lt;/code&gt; (optimistic locking) does not bypass the version check even when using &lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt; (pessimistic locking). If another transaction modified the data, an &lt;code&gt;OptimisticLockException&lt;/code&gt; will still be thrown upon update.&lt;/li&gt;
&lt;li&gt;Long transactions: Holding a pessimistic lock or an open session during user interaction (e.g., via &lt;code&gt;OpenSessionInView&lt;/code&gt;) is a direct path to connection pool exhaustion and deadlocks.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What to Use and When
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Optimistic Locking&lt;/th&gt;
&lt;th&gt;Pessimistic Locking&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Principle&lt;/td&gt;
&lt;td&gt;Conflicts are rare. Check before commit.&lt;/td&gt;
&lt;td&gt;Conflicts are likely. Lock in the DB.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Implementation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@Version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;LockModeType.PESSIMISTIC_WRITE&lt;/code&gt; (&lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When to use&lt;/td&gt;
&lt;td&gt;Most web apps, CRUD, rare updates.&lt;/td&gt;
&lt;td&gt;Critical operations with high contention (finance, reservations).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Risks&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;OptimisticLockException&lt;/code&gt; on conflict (requires handling/retry).&lt;/td&gt;
&lt;td&gt;Deadlocks, reduced throughput, long-held locks.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Spring rolls back only &lt;code&gt;RuntimeException&lt;/code&gt; and &lt;code&gt;Error&lt;/code&gt; by default. For checked exceptions, rollback must be explicitly specified via &lt;code&gt;rollbackFor&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The exception must "escape" the &lt;code&gt;@Transactional&lt;/code&gt; method for Spring to perform a rollback. Do not swallow exceptions inside the method.&lt;/li&gt;
&lt;li&gt;Optimistic locking (&lt;code&gt;@Version&lt;/code&gt;) is a version check upon update. It does not lock data, but merely reports a conflict (&lt;code&gt;OptimisticLockException&lt;/code&gt;). This is the default choice for most systems.&lt;/li&gt;
&lt;li&gt;Pessimistic locking (&lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt;) is an actual lock at the database level. Use it sparingly, only where its absence would cause a critical data integrity error.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Isolation.DEFAULT&lt;/code&gt; can lead to different behavior across environments. For critical operations, specify the isolation level explicitly.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Propagation.REQUIRES_NEW&lt;/code&gt; is your tool for isolating failing operations (e.g., logging) that should not affect the main transaction's rollback.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>spring</category>
      <category>learning</category>
      <category>java</category>
    </item>
    <item>
      <title>Hibernate Under the Hood. Part 5: Caching</title>
      <dc:creator>Silver_dev</dc:creator>
      <pubDate>Wed, 21 Oct 2026 18:21:51 +0000</pubDate>
      <link>https://dev.to/silver_dev/hibernate-under-the-hood-part-5-caching-5io</link>
      <guid>https://dev.to/silver_dev/hibernate-under-the-hood-part-5-caching-5io</guid>
      <description>&lt;p&gt;Caching in Hibernate is a multi-level system, and understanding its architecture is critical for application performance. The golden rule: caching is disabled by default (except for the first-level cache), and it must be enabled consciously for specific data. The system consists of three independent layers, each with its own area of responsibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  First-Level Cache
&lt;/h2&gt;

&lt;p&gt;This is the foundation that cannot be disabled. It is always active and bound to a specific session (&lt;code&gt;Session&lt;/code&gt;/&lt;code&gt;EntityManager&lt;/code&gt;).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What it is: Essentially, this is the Persistence Context. All entities loaded within a single session are placed into this cache.&lt;/li&gt;
&lt;li&gt;Scope: Only within a single session (usually a single transaction). Once the session is closed, the cache is destroyed.&lt;/li&gt;
&lt;li&gt;Guarantees: It ensures the identity map (a repeated &lt;code&gt;get&lt;/code&gt; for the same ID returns the exact same Java object instance) and dirty checking (Hibernate compares the current state with the snapshot upon &lt;code&gt;flush&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Management: You cannot disable it, but you can manage its contents: &lt;code&gt;session.evict(entity)&lt;/code&gt; removes a specific object, and &lt;code&gt;session.clear()&lt;/code&gt; completely empties the cache. This is highly useful during batch processing of large data volumes to prevent Out-Of-Memory (OOM) errors.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Second-Level Cache
&lt;/h2&gt;

&lt;p&gt;This is a shared cache that lives at the &lt;code&gt;SessionFactory&lt;/code&gt; level and is shared across all sessions. It is designed for data that is rarely updated and frequently read.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scope: Process level (or cluster level, if a distributed provider is used). Data survives session closure.&lt;/li&gt;
&lt;li&gt;What is cached: Entity state (by their ID). It does not cache query results.&lt;/li&gt;
&lt;li&gt;Disabled by default. To enable it, three conditions must be met:

&lt;ul&gt;
&lt;li&gt;Activate the cache in the settings (&lt;code&gt;hibernate.cache.use_second_level_cache=true&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Provide a cache provider (Ehcache, Caffeine, Infinispan, Redis, etc.).&lt;/li&gt;
&lt;li&gt;Explicitly mark each entity with the &lt;code&gt;@Cache&lt;/code&gt; annotation. Global activation does not cache everything indiscriminately — entities must "opt-in" to caching.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Concurrency Strategies (&lt;code&gt;CacheConcurrencyStrategy&lt;/code&gt;):&lt;/strong&gt;&lt;br&gt;
When configuring &lt;code&gt;@Cache&lt;/code&gt;, you must specify a strategy that defines how the cache behaves during concurrent updates.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;When to use&lt;/th&gt;
&lt;th&gt;Characteristics&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;READ_ONLY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Immutable reference data&lt;/td&gt;
&lt;td&gt;Fastest and safest. Throws an exception on update attempts.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NONSTRICT_READ_WRITE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Updates are rare and unlikely&lt;/td&gt;
&lt;td&gt;No locks. Slight risk of reading stale data during an update.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;READ_WRITE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Updates are possible&lt;/td&gt;
&lt;td&gt;Uses "soft" locks to prevent reading stale data during a transaction.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TRANSACTIONAL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Strict consistency required&lt;/td&gt;
&lt;td&gt;Full transactional locks. Requires explicit support from the cache provider.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Management: &lt;code&gt;sessionFactory.evict(Entity.class, id)&lt;/code&gt; allows targeted removal of an entity from the second-level cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  Query Cache
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;This is a specialized cache for query results. It works as an extension to the second-level cache and does not replace it.
What is cached: The result of a query (JPQL/Criteria) with a specific set of parameters. Only the identifiers of the found entities and scalar values are cached, not the state of the entities themselves.&lt;/li&gt;
&lt;li&gt;Dependency: Since the query cache stores only IDs, Hibernate will reach out to the second-level cache to fetch the actual entity data. If the entity is not there, a database query will be executed. Therefore, the query cache is always used in tandem with the second-level cache.&lt;/li&gt;
&lt;li&gt;How to enable:

&lt;ul&gt;
&lt;li&gt;Activate globally: &lt;code&gt;hibernate.cache.use_query_cache=true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;For each specific query, call &lt;code&gt;.setCacheable(true)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Invalidation: The query cache is highly sensitive to changes. Any update to a table involved in the query invalidates the corresponding cached results. Hibernate tracks this via the &lt;code&gt;UpdateTimestampsCache&lt;/code&gt; region.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pitfalls and Common Mistakes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Expecting the second-level cache to work "out of the box". This is the most common mistake. Enabling the global setting is completely useless if the entities lack the &lt;code&gt;@Cache&lt;/code&gt; annotation.&lt;/li&gt;
&lt;li&gt;Caching "volatile" data. If an entity is frequently updated, the cache will constantly invalidate, creating overhead without providing any performance benefit. Cache only "reference" or rarely changing data.&lt;/li&gt;
&lt;li&gt;Cluster consistency issues. When using multiple application instances, a standard second-level cache (e.g., based on local memory) does not synchronize between them. One instance might update data, while another continues to serve stale data from its local cache. The solution is a distributed cache (Redis, Infinispan, Hazelcast) that synchronizes invalidation across nodes.&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;READ_WRITE&lt;/code&gt; without lock support. The &lt;code&gt;READ_WRITE&lt;/code&gt; strategy requires the cache provider to support locking. Not all providers can do this, and the provider's documentation usually contains explicit warnings about it.&lt;/li&gt;
&lt;li&gt;Query cache overhead. The query cache only makes sense for frequently repeated queries with the exact same parameters. For ad-hoc or unique queries, it creates unnecessary invalidation overhead.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;First-level cache is always on, lives within a session, and ensures identity and dirty checking. Manage it via &lt;code&gt;evict&lt;/code&gt;/&lt;code&gt;clear&lt;/code&gt; during batch processing.&lt;/li&gt;
&lt;li&gt;Second-level cache is shared across sessions but off by default. It requires a provider and an explicit &lt;code&gt;@Cache&lt;/code&gt; annotation on each entity. Choose the strategy (&lt;code&gt;READ_ONLY&lt;/code&gt;, &lt;code&gt;READ_WRITE&lt;/code&gt;, etc.) based on the data's update frequency.&lt;/li&gt;
&lt;li&gt;Query cache caches only the IDs of results for repetitive queries. It always works in tandem with the second-level cache; otherwise, it is pointless.&lt;/li&gt;
&lt;li&gt;Caching is a trade-off between read speed and the risk of serving stale data. Do not cache everything indiscriminately; start with &lt;code&gt;READ_ONLY&lt;/code&gt; for reference tables.&lt;/li&gt;
&lt;li&gt;In a cluster, always use a distributed cache (Redis/Infinispan), otherwise data consistency will be compromised.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>spring</category>
      <category>learning</category>
      <category>java</category>
    </item>
    <item>
      <title>Hibernate Under the Hood. Part 4: Querying</title>
      <dc:creator>Silver_dev</dc:creator>
      <pubDate>Wed, 21 Oct 2026 16:48:02 +0000</pubDate>
      <link>https://dev.to/silver_dev/hibernate-under-the-hood-part-4-querying-22bk</link>
      <guid>https://dev.to/silver_dev/hibernate-under-the-hood-part-4-querying-22bk</guid>
      <description>&lt;p&gt;The chapter on querying languages is where Hibernate stops being just "magic" and becomes a manageable tool. Here, it is crucial to understand three approaches and their boundaries: HQL/JPQL (declarative, object-oriented), Criteria API (programmatic, type-safe), and Native SQL (direct database access). In Spring projects, there is an additional layer on top of this: Spring Data JPA, which changes how you write 90% of your queries.&lt;/p&gt;

&lt;h3&gt;
  
  
  HQL and JPQL: Object-Oriented SQL
&lt;/h3&gt;

&lt;p&gt;HQL is Hibernate's proprietary language, while JPQL is the JPA standard inspired by early HQL. It is important to know the relationship: HQL is a superset of JPQL. Any valid JPQL query is a valid HQL query, but not vice versa: HQL supports more features (e.g., &lt;code&gt;insert ... select&lt;/code&gt;, which is absent in JPQL).&lt;/p&gt;

&lt;p&gt;The main difference from SQL is that you operate on entities and their fields, not tables and columns. In HQL, &lt;code&gt;Book&lt;/code&gt; is a Java class, and &lt;code&gt;book.title&lt;/code&gt; is its field. Direct referencing of database table and column names is prohibited. Because of this, HQL is more compact than SQL and portable across different databases.&lt;/p&gt;

&lt;p&gt;Mapping Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- SQL&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Book&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;Publisher&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;publisherId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'Hibernate%'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// HQL / JPQL&lt;/span&gt;
&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pub&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;
&lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="nc"&gt;Book&lt;/span&gt; &lt;span class="no"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;
&lt;span class="no"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;publisher&lt;/span&gt; &lt;span class="no"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;pub&lt;/span&gt;
&lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt; &lt;span class="no"&gt;LIKE&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nc"&gt;Hibernate&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;
&lt;span class="no"&gt;ORDER&lt;/span&gt; &lt;span class="no"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: the join is performed on the association (&lt;code&gt;book.publisher&lt;/code&gt;), not on the foreign key column. Hibernate knows how to resolve it automatically.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key HQL features you need to know:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parameters: Named (&lt;code&gt;:name&lt;/code&gt;) and positional (&lt;code&gt;?1&lt;/code&gt;). Named parameters are preferred for readability, while positional ones are often used in native queries.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;JOIN FETCH&lt;/code&gt;: The primary tool for combating the N+1 query problem. It loads the association immediately in a single query.&lt;/li&gt;
&lt;li&gt;DTO projections: You can return a DTO directly instead of an entity, e.g., &lt;code&gt;new com.example.dto.BookDto(book.title, pub.name)&lt;/code&gt;. This saves memory and avoids creating managed objects.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;update&lt;/code&gt; / &lt;code&gt;delete&lt;/code&gt;: Bulk operations executed as a single SQL statement that bypass the Persistence Context (in-memory entities will not be updated — this is a pitfall).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;HQL Pitfalls:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bulk update/delete does not synchronize with the first-level cache. If you loaded a &lt;code&gt;Book&lt;/code&gt; into the session and then executed &lt;code&gt;update Book set title = ...&lt;/code&gt;, the in-memory object will retain the old title. You need &lt;code&gt;session.clear()&lt;/code&gt; or &lt;code&gt;refresh()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;JOIN FETCH&lt;/code&gt; on two collections simultaneously results in a &lt;code&gt;MultipleBagFetchException&lt;/code&gt; or a Cartesian product. The solution is to use &lt;code&gt;@BatchSize&lt;/code&gt; / &lt;code&gt;SUBSELECT&lt;/code&gt;, or split it into two separate queries.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;JOIN FETCH&lt;/code&gt; + pagination (&lt;code&gt;setMaxResults&lt;/code&gt;) can yield incorrect results: Hibernate applies the limit in memory, not in the SQL query. This is a known limitation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Criteria API: Queries as Code
&lt;/h3&gt;

&lt;p&gt;The Criteria API is a programmatic way to build queries. Instead of a string, you construct the query from objects: &lt;code&gt;CriteriaBuilder&lt;/code&gt;, &lt;code&gt;CriteriaQuery&lt;/code&gt;, &lt;code&gt;Root&lt;/code&gt;, and &lt;code&gt;Predicate&lt;/code&gt;. This provides type safety (the compiler checks field names if you use the metamodel) and flexibility for dynamic conditions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When the Criteria API is indispensable:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search screens with a variable set of filters. For example, a user might specify a price, category, brand — or nothing at all. Concatenating an HQL string with &lt;code&gt;if&lt;/code&gt; statements is a source of bugs; the Criteria API handles this cleanly.&lt;/li&gt;
&lt;li&gt;Dynamic sorting and pagination with type checking.&lt;/li&gt;
&lt;li&gt;Building queries in libraries and frameworks where you do not know the entity structure in advance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example (simplified):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;CriteriaBuilder&lt;/span&gt; &lt;span class="n"&gt;cb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCriteriaBuilder&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;CriteriaQuery&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Book&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createQuery&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Book&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;Root&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Book&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Book&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Predicate&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;predicates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;predicates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;like&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"%"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"%"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minPrice&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;predicates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"price"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="n"&gt;minPrice&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

&lt;span class="n"&gt;cq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;predicates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toArray&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Predicate&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;]));&lt;/span&gt;
&lt;span class="n"&gt;cq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orderBy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;asc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;

&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Book&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createQuery&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cq&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;getResultList&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Drawbacks of the Criteria API:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verbosity. A simple query turns into 10–15 lines of code.&lt;/li&gt;
&lt;li&gt;Poorer readability compared to HQL, especially for complex joins.&lt;/li&gt;
&lt;li&gt;Not for "one-off" queries. If a query is static, HQL is shorter and clearer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Practical rule: static queries → HQL/JPQL; dynamic queries (search, filters) → Criteria API.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Native SQL: When ORM is Powerless
&lt;/h3&gt;

&lt;p&gt;Sometimes you need to drop down to the database level. Native SQL is justified when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need a database-specific feature (window functions, &lt;code&gt;LATERAL JOIN&lt;/code&gt;, &lt;code&gt;hints&lt;/code&gt;, &lt;code&gt;FOR UPDATE SKIP LOCKED&lt;/code&gt;, specific syntax).&lt;/li&gt;
&lt;li&gt;The query is too complex for HQL and easier to write in SQL than to assemble via Criteria.&lt;/li&gt;
&lt;li&gt;You need maximum performance in a bottleneck where HQL generates suboptimal SQL.&lt;/li&gt;
&lt;li&gt;You are working with stored procedures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Book&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createNativeQuery&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"SELECT * FROM books WHERE isbn = ?1"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Book&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setParameter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isbn&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSingleResult&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Native Query Pitfalls:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Flush behavior: Hibernate does not flush by default before executing native queries, because it does not know which tables the query will affect. If there are unsaved changes in the Persistence Context, the native query might return stale data. The solution is an explicit &lt;code&gt;session.flush()&lt;/code&gt; before the query or configuring the &lt;code&gt;FlushMode&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Result mapping: For simple cases (&lt;code&gt;SELECT * matching an entity&lt;/code&gt;), Hibernate infers the mapping automatically. For complex queries (aliases, aggregates, joins), you need @SqlResultSetMapping or manual parsing of &lt;code&gt;Object[]&lt;/code&gt;, which is cumbersome and fragile.&lt;/li&gt;
&lt;li&gt;Portability: Native SQL ties you to a specific database dialect. If you plan to switch databases, use it sparingly.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Spring Data JPA: How It Looks
&lt;/h3&gt;

&lt;p&gt;In Spring Boot, you rarely write session.createQuery() directly. Instead, you use Spring Data JPA repositories, which offer three levels of abstraction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Derived queries (from method names): &lt;code&gt;findByEmailAddressAndLastname(String email, String lastname)&lt;/code&gt; is automatically translated into JPQL: &lt;code&gt;SELECT u FROM User u WHERE u.emailAddress = ?1 AND u.lastname = ?2&lt;/code&gt;. This works for simple queries, but method names become cumbersome with 3+ conditions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@Query&lt;/code&gt; with JPQL/HQL: An explicit query on a repository method. This is the standard for medium-complexity queries:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;   &lt;span class="nd"&gt;@Query&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT b FROM Book b JOIN FETCH b.author WHERE b.title LIKE %:title%"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
   &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Book&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findWithAuthorByTitle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@Query(nativeQuery = true)&lt;/code&gt;: Native SQL when JPQL is insufficient. The &lt;code&gt;nativeQuery&lt;/code&gt; flag explicitly tells Spring Data that this is not JPQL.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Important: Spring Data JPA does not generate JPQL for &lt;code&gt;UPDATE&lt;/code&gt; and &lt;code&gt;DELETE&lt;/code&gt; from method names — only for &lt;code&gt;SELECT&lt;/code&gt;. For modifications, you need &lt;code&gt;@Query&lt;/code&gt; + &lt;code&gt;@Modifying&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  What to Use When
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;When to choose&lt;/th&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HQL/JPQL&lt;/td&gt;
&lt;td&gt;Static queries, 80% of cases&lt;/td&gt;
&lt;td&gt;Compact, portable, readable&lt;/td&gt;
&lt;td&gt;Not for dynamic queries; strings are not checked by the compiler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Criteria API&lt;/td&gt;
&lt;td&gt;Dynamic filters, search, libraries&lt;/td&gt;
&lt;td&gt;Type-safe, flexible&lt;/td&gt;
&lt;td&gt;Verbose, harder to read&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native SQL&lt;/td&gt;
&lt;td&gt;DB specifics, complex queries, performance&lt;/td&gt;
&lt;td&gt;Full control over SQL&lt;/td&gt;
&lt;td&gt;Not portable, requires manual mapping, flush nuances&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spring Data (derived)&lt;/td&gt;
&lt;td&gt;Simple CRUD and selections&lt;/td&gt;
&lt;td&gt;Minimal code&lt;/td&gt;
&lt;td&gt;Cumbersome names, &lt;code&gt;SELECT&lt;/code&gt; only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spring Data &lt;code&gt;@Query&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Medium-complexity queries&lt;/td&gt;
&lt;td&gt;Explicit, readable, located in the repository&lt;/td&gt;
&lt;td&gt;Strings are not checked by the compiler&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  What to Remember
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;HQL is a superset of JPQL. Write in JPQL for portability; use HQL features consciously.&lt;/li&gt;
&lt;li&gt;HQL operates on entities, not tables. This is its strength (compactness, portability) and its limitation (cannot use DB-specific features).&lt;/li&gt;
&lt;li&gt;Criteria API is for dynamic queries, HQL is for static ones. Do not try to build a simple query using Criteria — it is overkill.&lt;/li&gt;
&lt;li&gt;Native SQL is a targeted tool. Always remember about &lt;code&gt;flush&lt;/code&gt; and result mapping.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;JOIN FETCH&lt;/code&gt; is your best friend against N+1, but it has limitations (pagination, two collections).&lt;/li&gt;
&lt;li&gt;Bulk update/delete in HQL does not synchronize with the first-level cache. You need &lt;code&gt;clear()&lt;/code&gt; after them.&lt;/li&gt;
&lt;li&gt;In Spring Data, 90% of queries are &lt;code&gt;@Query&lt;/code&gt; with JPQL. Derived queries are for simple cases, native queries are for exceptions.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>spring</category>
      <category>learning</category>
      <category>java</category>
    </item>
    <item>
      <title>Hibernate Under the Hood. Part 3: Entity Mapping</title>
      <dc:creator>Silver_dev</dc:creator>
      <pubDate>Wed, 21 Oct 2026 15:08:03 +0000</pubDate>
      <link>https://dev.to/silver_dev/hibernate-under-the-hood-part-3-entity-mapping-17e4</link>
      <guid>https://dev.to/silver_dev/hibernate-under-the-hood-part-3-entity-mapping-17e4</guid>
      <description>&lt;p&gt;Entity mapping is the foundation of working with Hibernate/JPA. It defines how Java objects are mapped to database tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic Annotations: Where an Entity Begins
&lt;/h3&gt;

&lt;p&gt;Any mapping starts with declaring a class as an entity and defining its primary key.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@Entity&lt;/code&gt;: Marks the class as an entity that will be mapped to a database table. This is a mandatory annotation.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@Table&lt;/code&gt;: Allows you to explicitly specify the table name. If omitted, Hibernate will default to the class name (e.g., for &lt;code&gt;User&lt;/code&gt;, the table will be &lt;code&gt;User&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@Id&lt;/code&gt;: Defines the field that serves as the primary key.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@GeneratedValue&lt;/code&gt;: Specifies that the primary key value will be generated automatically. The &lt;code&gt;GenerationType.IDENTITY&lt;/code&gt; strategy (database auto-increment) is one of the most common and straightforward.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@Column&lt;/code&gt;: Used for fine-tuning the column: you can specify its name, length, precision, forbid &lt;code&gt;null&lt;/code&gt; values, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example of a simple entity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Entity&lt;/span&gt;
&lt;span class="nd"&gt;@Table&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"users"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="nd"&gt;@GeneratedValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GenerationType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;IDENTITY&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"full_name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// ... getters and setters&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Relationship Types: From One-to-One to Many-to-Many
&lt;/h3&gt;

&lt;p&gt;In JPA, there are four main types of relationships. The key concept to understand is the owning side. It is on the owning side that the relationship is defined in the database (e.g., which table will hold the foreign key or the join table).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@ManyToOne&lt;/code&gt; (Many-to-One): The most common and basic relationship. It maps directly to a foreign key in the table. Important: By default, JPA uses &lt;code&gt;FetchType.EAGER&lt;/code&gt; for this, which is almost always undesirable.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Entity&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Book&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="nd"&gt;@ManyToOne&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FetchType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;LAZY&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Explicitly specify lazy loading!&lt;/span&gt;
    &lt;span class="nd"&gt;@JoinColumn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"publisher_id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Name of the foreign key column&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Publisher&lt;/span&gt; &lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@OneToMany&lt;/code&gt; (One-to-Many): The inverse side of &lt;code&gt;@ManyToOne&lt;/code&gt;. In JPA, this relationship is almost always bidirectional and must be the non-owning (inverse) side. For this, the &lt;code&gt;mappedBy&lt;/code&gt; attribute is used, pointing to the field in the child entity that owns the relationship.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Entity&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Publisher&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="nd"&gt;@OneToMany&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mappedBy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"publisher"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cascade&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CascadeType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ALL&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orphanRemoval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Book&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;@OneToOne&lt;/code&gt; (One-to-One): A relationship where one record in table A corresponds to exactly one record in table B. Like &lt;code&gt;@ManyToOne&lt;/code&gt;, it defaults to &lt;code&gt;EAGER&lt;/code&gt; fetching. To enable lazy loading on the child side (where the foreign key resides), you must explicitly use &lt;code&gt;@JoinColumn&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;@ManyToMany&lt;/code&gt; (Many-to-Many): Requires an intermediate join table. The owning side defines this table using the &lt;code&gt;@JoinTable&lt;/code&gt; annotation.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Owning side&lt;/span&gt;
&lt;span class="nd"&gt;@Entity&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Student&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@ManyToMany&lt;/span&gt;
    &lt;span class="nd"&gt;@JoinTable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"student_course"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;joinColumns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;@JoinColumn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"student_id"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;inverseJoinColumns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;@JoinColumn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"course_id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Course&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;courses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HashSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Inverse (non-owning) side&lt;/span&gt;
&lt;span class="nd"&gt;@Entity&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Course&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@ManyToMany&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mappedBy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"courses"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Student&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;students&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HashSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cascades (&lt;code&gt;CascadeType&lt;/code&gt;) and &lt;code&gt;orphanRemoval&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;These mechanisms determine how operations performed on one entity affect its related entities.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;CascadeType&lt;/code&gt;: Defines which operations (persist, remove, update) should be cascaded to related entities.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CascadeType.PERSIST&lt;/code&gt;: When the parent is saved, new child entities are automatically saved as well.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CascadeType.REMOVE&lt;/code&gt;: When the parent is deleted, all related child entities are also deleted.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CascadeType.ALL&lt;/code&gt;: Applies all operations. Use with caution, only if the child entity cannot exist without the parent.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;orphanRemoval = true&lt;/code&gt;: A more specific and powerful tool. It deletes a child entity if it is "unlinked" from the parent (e.g., removed from the &lt;code&gt;List&amp;lt;Book&amp;gt;&lt;/code&gt; collection in &lt;code&gt;Publisher&lt;/code&gt;), even if the parent entity itself is not deleted. This is ideal for modeling strict "composition" relationships.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Fetch Strategies (&lt;code&gt;FetchType&lt;/code&gt;): &lt;code&gt;LAZY&lt;/code&gt; vs &lt;code&gt;EAGER&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is one of the most critical topics for application performance.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;FetchType.LAZY&lt;/code&gt; (Lazy): Hibernate loads the related entity only upon the first access to it. This is the recommended default approach for all relationships, especially for collections (&lt;code&gt;@OneToMany&lt;/code&gt;, &lt;code&gt;@ManyToMany&lt;/code&gt;), as it prevents the N+1 query problem and excessive data loading.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FetchType.EAGER&lt;/code&gt; (Eager): Hibernate loads the related entity immediately, along with the parent. While this might seem convenient, in practice, it often leads to loading massive amounts of unnecessary data and severe performance issues.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The golden rule&lt;/strong&gt;: Always explicitly specify &lt;code&gt;fetch = FetchType.LAZY&lt;/code&gt; for all &lt;code&gt;@ManyToOne&lt;/code&gt; and &lt;code&gt;@OneToOne&lt;/code&gt; relationships, as &lt;code&gt;EAGER&lt;/code&gt; is their default value.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Practices and Common Pitfalls
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Always use &lt;code&gt;FetchType.LAZY&lt;/code&gt; for &lt;code&gt;@ManyToOne&lt;/code&gt; and &lt;code&gt;@OneToOne&lt;/code&gt;. This is the single most important rule for avoiding performance bottlenecks.&lt;/li&gt;
&lt;li&gt;Define the owning side correctly. For &lt;code&gt;@OneToMany&lt;/code&gt;, use &lt;code&gt;mappedBy&lt;/code&gt; on the "one" side, and &lt;code&gt;@JoinColumn&lt;/code&gt; on the "many" (owning) side.&lt;/li&gt;
&lt;li&gt;Be cautious with &lt;code&gt;CascadeType.ALL&lt;/code&gt;. Apply it only for true compositions, where the child object has no business meaning without the parent.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;orphanRemoval = true&lt;/code&gt; for clean deletion of child entities when they are removed from a collection.&lt;/li&gt;
&lt;li&gt;Don't forget &lt;code&gt;@JsonIgnoreProperties&lt;/code&gt; (or &lt;code&gt;@JsonManagedReference&lt;/code&gt;/&lt;code&gt;@JsonBackReference&lt;/code&gt;) when serializing bidirectional relationships to JSON to avoid infinite recursion.&lt;/li&gt;
&lt;li&gt;To load lazy relationships, use &lt;code&gt;JOIN FETCH&lt;/code&gt; or &lt;code&gt;@EntityGraph&lt;/code&gt;, rather than relying on &lt;code&gt;OpenSessionInView&lt;/code&gt;, which is widely considered an anti-pattern.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>spring</category>
      <category>learning</category>
      <category>java</category>
    </item>
    <item>
      <title>Hibernate Under the Hood. Part 2: Entity Lifecycle and the Persistence Context</title>
      <dc:creator>Silver_dev</dc:creator>
      <pubDate>Tue, 20 Oct 2026 22:27:23 +0000</pubDate>
      <link>https://dev.to/silver_dev/hibernate-under-the-hood-part-2-entity-lifecycle-and-the-persistence-context-43e4</link>
      <guid>https://dev.to/silver_dev/hibernate-under-the-hood-part-2-entity-lifecycle-and-the-persistence-context-43e4</guid>
      <description>&lt;p&gt;This is arguably the most important topic in Hibernate. If you understand entity states and what happens inside the Persistence Context, 80% of Hibernate-related bugs cease to be a mystery.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the Persistence Context
&lt;/h2&gt;

&lt;p&gt;The Persistence Context is a container of managed entities within a &lt;code&gt;Session&lt;/code&gt;/&lt;code&gt;EntityManager&lt;/code&gt;. Its key properties are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It is a first-level cache. All loaded entities live here until the session is closed.&lt;/li&gt;
&lt;li&gt;It guarantees identity. Within a single session, calling &lt;code&gt;get(User.class, 1L)&lt;/code&gt; twice will return the exact same Java object instance (Identity Map pattern).&lt;/li&gt;
&lt;li&gt;It tracks changes. Upon &lt;code&gt;flush&lt;/code&gt;, Hibernate compares the current state of entities with their snapshot and generates an &lt;code&gt;UPDATE&lt;/code&gt; only for the modified fields (dirty checking).&lt;/li&gt;
&lt;li&gt;It implements write-behind. &lt;code&gt;save&lt;/code&gt;/&lt;code&gt;persist&lt;/code&gt; do not write to the DB immediately — changes are accumulated and synchronized upon &lt;code&gt;flush&lt;/code&gt;.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;u1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1L&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;u2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1L&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u1&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;u2&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// true — the exact same object instance&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not "optimization for the sake of optimization". It is a semantic guarantee: within a single unit of work, you always work with the same object instance, preventing the scenario of "two objects, both representing User with id=1, one with name='A' and the other with name='B'".&lt;/p&gt;

&lt;h2&gt;
  
  
  The Four Entity States
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Transient (New / Unmanaged)
&lt;/h3&gt;

&lt;p&gt;The object is created via &lt;code&gt;new&lt;/code&gt;, but Hibernate knows nothing about it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// Transient&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Alice"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a regular Java object created with the &lt;code&gt;new&lt;/code&gt; operator. It is not associated with the database or the current Hibernate session.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database connection: None. No corresponding record exists in the database.&lt;/li&gt;
&lt;li&gt;Identifier (ID): Usually &lt;code&gt;null&lt;/code&gt; (unless set manually, but Hibernate will ignore it).&lt;/li&gt;
&lt;li&gt;Tracking: Hibernate does not track changes to its fields. If you modify a field and do nothing else, nothing goes to the DB.&lt;/li&gt;
&lt;li&gt;Analogy: A document draft written on your desk. It is not yet registered in the archive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How to transition to another state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To Persistent: call &lt;code&gt;session.save()&lt;/code&gt; or &lt;code&gt;session.persist()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;To Detached: (rarely done directly; usually, the object is saved first, and then the session is closed).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Persistent (Managed)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;persist&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// user is now Persistent&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Bob"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;// changes are tracked&lt;/span&gt;
&lt;span class="c1"&gt;// upon flush, Hibernate will generate an UPDATE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The object is managed by the current session. It has a representation in the database (it is already saved there or will be upon &lt;code&gt;flush&lt;/code&gt;).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database connection: Direct. The object corresponds to a row in the table.&lt;/li&gt;
&lt;li&gt;Identifier (ID): Must have a value (the ID from the DB).&lt;/li&gt;
&lt;li&gt;Tracking: Hibernate closely monitors the object (via Dirty Checking). If you modify any field of this object, Hibernate will notice and automatically generate and execute an &lt;code&gt;UPDATE&lt;/code&gt; SQL query on the next &lt;code&gt;flush()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Analogy: A document you pulled from the archive, opened in the system, and are editing. The system sees all your changes and will save them when you click "Save" (flush).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How to get into this state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From Transient: &lt;code&gt;session.save()&lt;/code&gt;, &lt;code&gt;session.persist()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;From Detached: &lt;code&gt;session.update()&lt;/code&gt;, &lt;code&gt;session.saveOrUpdate()&lt;/code&gt;, &lt;code&gt;session.merge()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How to leave this state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To Detached: close the session (&lt;code&gt;session.close()&lt;/code&gt;), clear it (&lt;code&gt;session.clear()&lt;/code&gt;), or evict the object from the session cache (&lt;code&gt;session.evict()&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;To Removed: call &lt;code&gt;session.delete()&lt;/code&gt; or &lt;code&gt;session.remove()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Detached
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// all previously loaded entities are now Detached&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The object was in the Persistent state (it has an ID and exists in the DB), but lost its connection to the current session (the session was closed or the object was evicted from it).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database connection: The object exists in the database.&lt;/li&gt;
&lt;li&gt;Identifier (ID): Must have a value.&lt;/li&gt;
&lt;li&gt;Tracking: Hibernate does not track it. If you modify a Detached object's field, nothing goes to the DB until you "re-attach" it to a session.&lt;/li&gt;
&lt;li&gt;Analogy: A document you physically took from the archive to your desk. You can read and even change it, but the archive system knows nothing about it. To apply changes to the archive, the document must be returned (via merge/update).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How to get into this state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From Persistent: close the session (&lt;code&gt;session.close()&lt;/code&gt;), clear the cache (&lt;code&gt;session.clear()&lt;/code&gt;), or evict the object (&lt;code&gt;session.evict()&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How to transition to another state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To Persistent: call &lt;code&gt;session.update()&lt;/code&gt;, &lt;code&gt;session.saveOrUpdate()&lt;/code&gt;, or &lt;code&gt;session.merge()&lt;/code&gt; (in a newly opened session).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Removed
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;remove&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Removed&lt;/span&gt;
&lt;span class="c1"&gt;// upon flush → DELETE FROM users WHERE id = ?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The object is marked for deletion. It is still associated with the session, but "doomed".&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database connection: The record still exists in the DB but is marked for deletion.&lt;/li&gt;
&lt;li&gt;Tracking: On the next &lt;code&gt;flush()&lt;/code&gt; or &lt;code&gt;commit()&lt;/code&gt;, Hibernate will generate a &lt;code&gt;DELETE&lt;/code&gt; SQL query and remove the row from the database.&lt;/li&gt;
&lt;li&gt;Analogy: A document stamped with a red "DESTROY" mark. It is still in the system, but at the end of the workday (upon commit), it will be sent to the shredder.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How to get into this state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From Persistent: call &lt;code&gt;session.delete()&lt;/code&gt; or &lt;code&gt;session.remove()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key Takeaway&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The most common beginner mistake is attempting to modify a Detached object and expecting it to update in the DB automatically.&lt;/p&gt;

&lt;p&gt;Remember: changes are automatically pushed to the DB only from the Persistent state. If an object becomes Detached (e.g., you fetched it from the DB in one method, but are trying to update it in another where the session is already closed), you must explicitly "re-attach" it via &lt;code&gt;merge()&lt;/code&gt; or &lt;code&gt;update()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;persist()&lt;/code&gt; vs &lt;code&gt;save()&lt;/code&gt;
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;persist()&lt;/code&gt; (JPA)&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;save()&lt;/code&gt; (Hibernate)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Returns&lt;/td&gt;
&lt;td&gt;&lt;code&gt;void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Serializable&lt;/code&gt; (the generated ID)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;INSERT&lt;/code&gt; timing&lt;/td&gt;
&lt;td&gt;Deferred (upon flush)&lt;/td&gt;
&lt;td&gt;Can be immediate (if the ID is needed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Outside a transaction&lt;/td&gt;
&lt;td&gt;Throws an exception&lt;/td&gt;
&lt;td&gt;Works (but it's a bad practice)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;For detached objects&lt;/td&gt;
&lt;td&gt;Throws an exception&lt;/td&gt;
&lt;td&gt;May create a new object (or throw an error if ID exists)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use &lt;code&gt;persist()&lt;/code&gt; if you are working via the JPA API. It is the standard.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;merge()&lt;/code&gt; vs &lt;code&gt;update()&lt;/code&gt;
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;merge()&lt;/code&gt; (JPA)&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;update()&lt;/code&gt; (Hibernate)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What it does&lt;/td&gt;
&lt;td&gt;Loads the current state from the DB, copies changes into a new managed object, and returns it&lt;/td&gt;
&lt;td&gt;"Re-attaches" the passed detached object to the session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Returns&lt;/td&gt;
&lt;td&gt;A managed copy&lt;/td&gt;
&lt;td&gt;&lt;code&gt;void&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Original object&lt;/td&gt;
&lt;td&gt;Remains detached&lt;/td&gt;
&lt;td&gt;Becomes persistent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;If the record was deleted&lt;/td&gt;
&lt;td&gt;Creates a new record&lt;/td&gt;
&lt;td&gt;Throws an exception&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Crucial note: &lt;code&gt;merge()&lt;/code&gt; does not make the passed object managed. It returns a different managed copy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;detached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...;&lt;/span&gt;               &lt;span class="c1"&gt;// Detached&lt;/span&gt;
&lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;managed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;merge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;detached&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// detached — remains Detached&lt;/span&gt;
&lt;span class="c1"&gt;// managed — is now Persistent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;get()&lt;/code&gt; vs &lt;code&gt;load()&lt;/code&gt;
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;&lt;code&gt;get()&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;load()&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DB query&lt;/td&gt;
&lt;td&gt;Immediate&lt;/td&gt;
&lt;td&gt;Lazy (returns a proxy)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;If not found&lt;/td&gt;
&lt;td&gt;Returns &lt;code&gt;null&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Throws &lt;code&gt;EntityNotFoundException&lt;/code&gt; upon access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Returns&lt;/td&gt;
&lt;td&gt;Real object or &lt;code&gt;null&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Proxy or real object&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;load()&lt;/code&gt; returns a proxy — Hibernate provides a subclass that will fetch the actual data upon the first access to any field (except the ID).&lt;/p&gt;

&lt;h3&gt;
  
  
  Other Key Methods
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;refresh()&lt;/code&gt;: Reloads the object's state from the DB, overwriting it in memory. Useful if data might have been changed externally (another process, native SQL).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;refresh&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// user is reloaded from the DB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;evict()&lt;/code&gt; / &lt;code&gt;detach()&lt;/code&gt;: Removes the object from the Persistence Context. The object becomes Detached.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;evict&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// user is no longer managed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;clear()&lt;/code&gt;: Completely clears the Persistence Context. All objects become Detached.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// 1st-level cache is empty&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;flush()&lt;/code&gt;: Synchronizes the Persistence Context with the DB. It does not commit the transaction — it only executes &lt;code&gt;INSERT&lt;/code&gt;/&lt;code&gt;UPDATE&lt;/code&gt;/&lt;code&gt;DELETE&lt;/code&gt; statements.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// SQL is executed, but the transaction is not committed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When does automatic &lt;code&gt;flush&lt;/code&gt; occur?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Before a transaction commit.&lt;/li&gt;
&lt;li&gt;Before executing a query that might affect tables containing "dirty" entities (auto-flush).&lt;/li&gt;
&lt;li&gt;Upon an explicit &lt;code&gt;flush()&lt;/code&gt; call.&lt;/li&gt;
&lt;li&gt;Upon &lt;code&gt;session.close()&lt;/code&gt; (if uncommitted, it triggers a rollback).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Dirty Checking — How Hibernate Detects Changes
&lt;/h3&gt;

&lt;p&gt;When loading an entity, Hibernate saves a snapshot — a copy of all its fields. Upon flush, it compares the current state with the snapshot.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Transactional&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;rename&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;newName&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// snapshot is saved&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newName&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;                    &lt;span class="c1"&gt;// modified in memory&lt;/span&gt;
    &lt;span class="c1"&gt;// flush on commit: snapshot.name != u.name → UPDATE users SET name = ?&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No need to call &lt;code&gt;update()&lt;/code&gt; — changes are picked up automatically.&lt;/li&gt;
&lt;li&gt;Generates an &lt;code&gt;UPDATE&lt;/code&gt; only for modified fields (unless disabled).&lt;/li&gt;
&lt;li&gt;If nothing changed, no &lt;code&gt;UPDATE&lt;/code&gt; is executed at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Dirty checking triggers on &lt;code&gt;flush&lt;/code&gt;, not on &lt;code&gt;set&lt;/code&gt;. If you modify a field and roll back the transaction, the change won't reach the DB (which is correct). However, if you modify a field and don't commit, it remains in the object's memory.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Write-behind — Why &lt;code&gt;save&lt;/code&gt; Doesn't Write Immediately
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;persist()&lt;/code&gt; does not execute an &lt;code&gt;INSERT&lt;/code&gt; immediately. It registers the entity in the Persistence Context, and the SQL is generated upon &lt;code&gt;flush&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Batching: Hibernate can collect several &lt;code&gt;INSERT&lt;/code&gt;s and send them as a single batch (&lt;code&gt;hibernate.jdbc.batch_size&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Operation ordering: Hibernate automatically sequences &lt;code&gt;INSERT&lt;/code&gt;/&lt;code&gt;UPDATE&lt;/code&gt;/&lt;code&gt;DELETE&lt;/code&gt; statements to avoid violating foreign key constraints.&lt;/li&gt;
&lt;li&gt;Efficiency: If the transaction rolls back, the &lt;code&gt;INSERT&lt;/code&gt; was never actually executed against the DB.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Exception&lt;/em&gt;: If the ID generation strategy is &lt;code&gt;IDENTITY&lt;/code&gt; (auto-increment), Hibernate is forced to execute the &lt;code&gt;INSERT&lt;/code&gt; immediately to obtain the generated ID. This breaks batching.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Identity Map — The Uniqueness Guarantee&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;u1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1L&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;u2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1L&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;u1&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;u2&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How it works: The Persistence Context is essentially a &lt;code&gt;Map&amp;lt;EntityKey, Object&amp;gt;&lt;/code&gt;, where the key is &lt;code&gt;(entityClass, id)&lt;/code&gt;. Upon &lt;code&gt;get&lt;/code&gt;, Hibernate first checks the map. If the object is there, it returns it. If not, it loads it from the DB and puts it in the map.&lt;/p&gt;

&lt;p&gt;Why it's needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consistency guarantee: Prevents having two different objects with the same ID in memory.&lt;/li&gt;
&lt;li&gt;Performance: Repeated &lt;code&gt;get&lt;/code&gt; calls do not hit the DB.&lt;/li&gt;
&lt;li&gt;Accurate dirty checking: Hibernate knows exactly which object instance to track.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;The identity map lives only within a session. Across sessions, these are different objects.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Session 1&lt;/span&gt;
&lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;u1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1L&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;session1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Session 2&lt;/span&gt;
&lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;u2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1L&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;u1&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;u2&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// different object instances, both with id=1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Typical Mistakes and Pitfalls
&lt;/h3&gt;

&lt;p&gt;1) &lt;code&gt;LazyInitializationException&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Transactional&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// returns managed&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// session is closed, user is now Detached&lt;/span&gt;

&lt;span class="c1"&gt;// in the controller:&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getOrders&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// LazyInitializationException!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Solution&lt;/em&gt;: Fetch required data within the transaction (&lt;code&gt;JOIN FETCH&lt;/code&gt;, &lt;code&gt;@EntityGraph&lt;/code&gt;), or use DTO projections.&lt;/p&gt;

&lt;p&gt;2) &lt;code&gt;merge()&lt;/code&gt; does not make the original object managed&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;detached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...;&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;merge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;detached&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;detached&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"new"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// NOT tracked! detached is still Detached&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Solution&lt;/em&gt;: Work with the result of &lt;code&gt;merge()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;managed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;merge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;detached&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;managed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"new"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// NOW tracked&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3) Modifying a detached object without &lt;code&gt;merge()&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Transactional&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  &lt;span class="c1"&gt;// user came from outside (detached)&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"new"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// NOTHING happens — user is not managed&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Solution&lt;/em&gt;: Use &lt;code&gt;session.merge(user)&lt;/code&gt; or reload the entity and modify the managed instance.&lt;/p&gt;

&lt;p&gt;4) &lt;code&gt;save()&lt;/code&gt; vs &lt;code&gt;persist()&lt;/code&gt; with an existing ID&lt;br&gt;
If a transient object already has an ID (e.g., assigned manually), &lt;code&gt;save()&lt;/code&gt; might assume the object is detached and execute an &lt;code&gt;UPDATE&lt;/code&gt; instead of an &lt;code&gt;INSERT&lt;/code&gt;. &lt;code&gt;persist()&lt;/code&gt; will throw an exception in this situation, which is safer.&lt;/p&gt;

&lt;p&gt;5) Long session → Memory Leak&lt;br&gt;
The Persistence Context grows with every loaded entity. If a session is kept open for the entire HTTP request (&lt;code&gt;OpenSessionInView&lt;/code&gt;) and loads thousands of objects, memory will leak.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Solution&lt;/em&gt;: Do not keep the session open longer than necessary; periodically &lt;code&gt;flush()&lt;/code&gt; + &lt;code&gt;clear()&lt;/code&gt; during batch processing.&lt;/p&gt;

&lt;p&gt;6) &lt;code&gt;flush()&lt;/code&gt; does not commit&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// SQL is executed, but not committed&lt;/span&gt;
&lt;span class="c1"&gt;// if an exception occurs next — it will roll back&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Solution&lt;/em&gt;: Remember: &lt;code&gt;flush()&lt;/code&gt; is for synchronization, &lt;code&gt;commit()&lt;/code&gt; is for finalizing.&lt;/p&gt;

&lt;p&gt;7) Auto-flush before queries&lt;br&gt;
Hibernate performs a &lt;code&gt;flush&lt;/code&gt; before &lt;code&gt;createQuery()&lt;/code&gt; if the query affects tables containing "dirty" entities. This can lead to unexpected &lt;code&gt;UPDATE&lt;/code&gt;s in the middle of a method.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Solution&lt;/em&gt;: Understand this behavior; if necessary, configure &lt;code&gt;FlushMode.MANUAL&lt;/code&gt; or &lt;code&gt;COMMIT&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: All States in Action
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Transactional&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;demo&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Transient&lt;/span&gt;
    &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Alice"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Transient → Persistent&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;persist&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// upon flush → INSERT&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Persistent (managed)&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Bob"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// dirty checking will pick this up on flush&lt;/span&gt;

    &lt;span class="c1"&gt;// 4. Persistent → Detached&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;evict&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Charlie"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// NOT tracked&lt;/span&gt;

    &lt;span class="c1"&gt;// 5. Detached → Persistent (via merge)&lt;/span&gt;
    &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;managed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;merge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// managed is now Persistent, user remains Detached&lt;/span&gt;

    &lt;span class="c1"&gt;// 6. Persistent → Removed&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;remove&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;managed&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// upon flush → DELETE&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Persistence Context = 1st-level cache + identity map + dirty checking + write-behind. It is the heart of the &lt;code&gt;Session&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Four states: Transient, Persistent, Detached, Removed. Transitions are managed via session methods.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;persist()&lt;/code&gt; ≠ &lt;code&gt;save()&lt;/code&gt;, &lt;code&gt;merge()&lt;/code&gt; ≠ &lt;code&gt;update()&lt;/code&gt;. Understand the difference.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;merge()&lt;/code&gt; returns a copy; the original object remains detached.&lt;/li&gt;
&lt;li&gt;Dirty checking triggers on &lt;code&gt;flush&lt;/code&gt;, not on &lt;code&gt;set&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;flush()&lt;/code&gt; does not commit; it only synchronizes.&lt;/li&gt;
&lt;li&gt;After session closure, objects are Detached → lazy fields are inaccessible.&lt;/li&gt;
&lt;li&gt;The identity map lives only within a session — across sessions, objects are different instances.&lt;/li&gt;
&lt;li&gt;Long session = memory leak. Do not keep it open longer than necessary.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LazyInitializationException&lt;/code&gt; is a symptom of improper state management, not a "Hibernate bug".&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>spring</category>
      <category>learning</category>
      <category>java</category>
    </item>
    <item>
      <title>Hibernate Under the Hood. Part 1: Hibernate Architecture and Key Components: SessionFactory and Session</title>
      <dc:creator>Silver_dev</dc:creator>
      <pubDate>Tue, 20 Oct 2026 19:37:28 +0000</pubDate>
      <link>https://dev.to/silver_dev/hibernate-under-the-hood-part-1-hibernate-architecture-and-key-components-sessionfactory-and-3ip6</link>
      <guid>https://dev.to/silver_dev/hibernate-under-the-hood-part-1-hibernate-architecture-and-key-components-sessionfactory-and-3ip6</guid>
      <description>&lt;p&gt;Hibernate is an ORM framework that sits between your Java code and JDBC. To truly understand its behavior, you need a clear mental model of its architecture and the roles of two central objects: &lt;code&gt;SessionFactory&lt;/code&gt; and &lt;code&gt;Session&lt;/code&gt;. Everything else is derived from them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Overall Architecture (Top to Bottom)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
    ↓
SessionFactory  (heavyweight, one per application)
    ↓
Session         (lightweight, one per unit of work)
    ↓
Persistence Context (first-level cache inside the Session)
    ↓
JDBC Connection / DataSource
    ↓
Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And on the side: Second-Level Cache, Connection Pool, Transaction Manager, Dialect, Interceptor/Listener — all of these reside at the &lt;code&gt;SessionFactory&lt;/code&gt; level.&lt;/p&gt;

&lt;h2&gt;
  
  
  SessionFactory
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;SessionFactory&lt;/code&gt; is an immutable, thread-safe, heavyweight factory that is created exactly once per application (per database). It holds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mapping metadata — all entities, their fields, relationships, and fetch strategies. Built once at startup.&lt;/li&gt;
&lt;li&gt;Second-Level Cache (if enabled).&lt;/li&gt;
&lt;li&gt;Connection settings — DataSource, connection pool, Dialect.&lt;/li&gt;
&lt;li&gt;Compiled SQL queries derived from HQL/JPQL (query plan cache).&lt;/li&gt;
&lt;li&gt;Event listeners — interceptors, callbacks.&lt;/li&gt;
&lt;li&gt;Configurations for query cache, batching, and statistics.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How it is created:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In modern Spring Boot, you don't do this manually — Spring creates the &lt;code&gt;SessionFactory&lt;/code&gt; (more accurately, the &lt;code&gt;EntityManagerFactory&lt;/code&gt;) during context initialization. However, understanding what happens under the hood is crucial.&lt;/p&gt;

&lt;p&gt;In "vanilla" Hibernate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Configuration&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Configuration&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addAnnotatedClass&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;configure&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hibernate.cfg.xml"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;SessionFactory&lt;/span&gt; &lt;span class="n"&gt;sessionFactory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;buildSessionFactory&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;buildSessionFactory()&lt;/code&gt; is an expensive operation. It:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reads all mappings.&lt;/li&gt;
&lt;li&gt;Builds the metamodel.&lt;/li&gt;
&lt;li&gt;Validates relationships.&lt;/li&gt;
&lt;li&gt;Initializes the connection pool.&lt;/li&gt;
&lt;li&gt;Initializes caches.&lt;/li&gt;
&lt;li&gt;Compiles named queries.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At application startup, this takes seconds, and sometimes tens of seconds on large projects with thousands of entities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Characteristics&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Thread-safety&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lifespan&lt;/td&gt;
&lt;td&gt;Entire application (singleton)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Creation cost&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;Immutable after creation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scope&lt;/td&gt;
&lt;td&gt;One per Application × Database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spring equivalent&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LocalContainerEntityManagerFactoryBean&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Typical Mistakes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating a &lt;code&gt;SessionFactory&lt;/code&gt; per request. This is a performance catastrophe: the entire metamodel and connection pool are rebuilt every time.&lt;/li&gt;
&lt;li&gt;Holding multiple &lt;code&gt;SessionFactory&lt;/code&gt; instances without a valid reason. Usually, this is only needed for multi-tenancy or working with multiple databases.&lt;/li&gt;
&lt;li&gt;Treating it as a "connection factory". No, connections are managed by the &lt;code&gt;DataSource&lt;/code&gt; / Connection Pool. &lt;code&gt;SessionFactory&lt;/code&gt; is a factory for sessions, not connections.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Session
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;Session&lt;/code&gt; is a lightweight, short-lived, &lt;strong&gt;NON&lt;/strong&gt; thread-safe object representing a single unit of work with the database. In JPA terminology, it corresponds to the &lt;code&gt;EntityManager&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Inside the &lt;code&gt;Session&lt;/code&gt; lives the Persistence Context — the first-level cache where all loaded and managed entities are stored.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lifespan&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;Session&lt;/code&gt; is created for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A single transaction (transaction-scoped) — the most common scenario in Spring.&lt;/li&gt;
&lt;li&gt;A single HTTP request (request-scoped) — via &lt;code&gt;OpenSessionInViewFilter&lt;/code&gt; (a controversial practice, see below).&lt;/li&gt;
&lt;li&gt;A single user conversation (long session, rare).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Spring Boot, the &lt;code&gt;Session&lt;/code&gt; is opened and closed automatically thanks to &lt;code&gt;@Transactional&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Transactional&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;updateUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// session is open&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// session is closed, changes are flushed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What the Session does
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Provides the API for database operations: &lt;code&gt;save&lt;/code&gt;, &lt;code&gt;persist&lt;/code&gt;, &lt;code&gt;merge&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt;, &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;load&lt;/code&gt;, &lt;code&gt;createQuery&lt;/code&gt;, &lt;code&gt;flush&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Manages the Persistence Context: tracks all loaded entities, their changes, and dirty fields (dirty checking).&lt;/li&gt;
&lt;li&gt;Contains the First-Level Cache: every &lt;code&gt;get&lt;/code&gt;/&lt;code&gt;load&lt;/code&gt; checks the cache first, then the database.&lt;/li&gt;
&lt;li&gt;Manages the transaction: via &lt;code&gt;Transaction&lt;/code&gt; (or &lt;code&gt;EntityTransaction&lt;/code&gt; in JPA).&lt;/li&gt;
&lt;li&gt;Performs flushing: synchronizes in-memory changes to the database.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Key Characteristics&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Thread-safety&lt;/td&gt;
&lt;td&gt;No (cannot be shared across threads)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lifespan&lt;/td&gt;
&lt;td&gt;Single unit of work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Creation cost&lt;/td&gt;
&lt;td&gt;Low (lightweight object)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;Mutable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scope&lt;/td&gt;
&lt;td&gt;One per Transaction / Request / Unit of work&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Typical Mistakes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sharing a &lt;code&gt;Session&lt;/code&gt; across threads. It is not thread-safe and will corrupt the Persistence Context.&lt;/li&gt;
&lt;li&gt;Keeping a &lt;code&gt;Session&lt;/code&gt; open for too long. The first-level cache grows, leading to memory leaks and stale data.&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;OpenSessionInView&lt;/code&gt; for lazy loading in controllers. This is an anti-pattern: it causes N+1 queries, unpredictable behavior, and transaction issues. It is better to explicitly fetch required data via &lt;code&gt;JOIN FETCH&lt;/code&gt; or &lt;code&gt;@EntityGraph&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Persistence Context — The Heart of the Session
&lt;/h2&gt;

&lt;p&gt;This is the most crucial concept to grasp. The &lt;code&gt;Persistence Context&lt;/code&gt; is the first-level cache living inside the &lt;code&gt;Session&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Stores all managed (persistent) entities. Loaded a &lt;code&gt;User&lt;/code&gt;? It's here.&lt;/li&gt;
&lt;li&gt;Guarantees identity. Two calls to &lt;code&gt;session.get(User.class, 1L)&lt;/code&gt; within the same session will return the exact same object instance (Identity Map pattern).&lt;/li&gt;
&lt;li&gt;Dirty checking. Upon &lt;code&gt;flush&lt;/code&gt;, Hibernate compares the current state of entities with their snapshot taken at load time, and generates an &lt;code&gt;UPDATE&lt;/code&gt; only for the modified fields.&lt;/li&gt;
&lt;li&gt;Write-behind. &lt;code&gt;save&lt;/code&gt;/&lt;code&gt;persist&lt;/code&gt; do not write to the DB immediately — they register the entity, and &lt;code&gt;flush&lt;/code&gt; synchronizes everything in batches.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Entity States&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;How to get there&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Transient&lt;/td&gt;
&lt;td&gt;New object, not associated with a session&lt;/td&gt;
&lt;td&gt;&lt;code&gt;new User()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistent&lt;/td&gt;
&lt;td&gt;Managed by the session, inside the Persistence Context&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;session.get()&lt;/code&gt;, &lt;code&gt;session.persist()&lt;/code&gt;, &lt;code&gt;session.merge()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Detached&lt;/td&gt;
&lt;td&gt;Was persistent, but the session is closed or the object was evicted&lt;/td&gt;
&lt;td&gt;After &lt;code&gt;Session&lt;/code&gt; closure, &lt;code&gt;session.evict()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Removed&lt;/td&gt;
&lt;td&gt;Marked for deletion&lt;/td&gt;
&lt;td&gt;&lt;code&gt;session.remove()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Dirty Checking Example&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Transactional&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;doWork&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1L&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// loaded, snapshot saved&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Bob"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;                      &lt;span class="c1"&gt;// modified in memory&lt;/span&gt;
    &lt;span class="c1"&gt;// flush on commit will compare with snapshot and execute UPDATE&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No explicit &lt;code&gt;session.update()&lt;/code&gt; is needed. This is the main magic of Hibernate.&lt;/p&gt;

&lt;h2&gt;
  
  
  How They Interact
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application → SessionFactory.openSession() → Session
                  ↑                              ↓
                  │                     Persistence Context (1st-level cache)
                  │                              ↓
            (created once)                 JDBC Connection (from pool)
                                                 ↓
                                          Transaction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typical Workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;App startup: &lt;code&gt;SessionFactory&lt;/code&gt; is created (once).&lt;/li&gt;
&lt;li&gt;User request: &lt;code&gt;Session&lt;/code&gt; is opened (for the duration of the request or transaction).&lt;/li&gt;
&lt;li&gt;Work: &lt;code&gt;Session&lt;/code&gt; loads entities into the Persistence Context, tracking changes.&lt;/li&gt;
&lt;li&gt;Flush: Upon commit or explicitly, changes are synchronized to the DB.&lt;/li&gt;
&lt;li&gt;Closure: &lt;code&gt;Session&lt;/code&gt; is closed, Persistence Context is destroyed. Entities become Detached.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Spring Specifics&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In Spring Boot, things work slightly differently:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;SessionFactory&lt;/code&gt; → created by Spring as an &lt;code&gt;EntityManagerFactory&lt;/code&gt; (usually &lt;code&gt;LocalContainerEntityManagerFactoryBean&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Session&lt;/code&gt; → wrapped in an &lt;code&gt;EntityManager&lt;/code&gt; and bound to the current thread via &lt;code&gt;EntityManagerHolder&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@Transactional&lt;/code&gt; → manages the opening/closing of the session and transaction.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OpenEntityManagerInView&lt;/code&gt; → keeps the session open until the end of the HTTP request.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Useful Configuration Settings&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="c"&gt;# Logical? No — the physical connection is acquired from the pool
&lt;/span&gt;&lt;span class="py"&gt;spring.jpa.properties.hibernate.connection.provider_disables_autocommit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;

&lt;span class="c"&gt;# Batching — critical for performance
&lt;/span&gt;&lt;span class="py"&gt;spring.jpa.properties.hibernate.jdbc.batch_size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;50&lt;/span&gt;
&lt;span class="py"&gt;spring.jpa.properties.hibernate.order_inserts&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;spring.jpa.properties.hibernate.order_updates&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;

&lt;span class="c"&gt;# Prevent Hibernate from modifying the DB at startup (use Flyway/Liquibase instead)
&lt;/span&gt;&lt;span class="py"&gt;spring.jpa.hibernate.ddl-auto&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;validate&lt;/span&gt;

&lt;span class="c"&gt;# Open session in view — do not use in production
&lt;/span&gt;&lt;span class="py"&gt;spring.jpa.open-in-view&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;&lt;code&gt;SessionFactory&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;Session&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Weight&lt;/td&gt;
&lt;td&gt;Heavyweight&lt;/td&gt;
&lt;td&gt;Lightweight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thread-safety&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lifespan&lt;/td&gt;
&lt;td&gt;Application&lt;/td&gt;
&lt;td&gt;Transaction / Request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Creation&lt;/td&gt;
&lt;td&gt;Expensive&lt;/td&gt;
&lt;td&gt;Cheap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;Immutable&lt;/td&gt;
&lt;td&gt;Mutable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contains&lt;/td&gt;
&lt;td&gt;Metadata, 2nd-level cache, pool&lt;/td&gt;
&lt;td&gt;Persistence Context, 1st-level cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spring equivalent&lt;/td&gt;
&lt;td&gt;&lt;code&gt;EntityManagerFactory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;EntityManager&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Key Takeaways:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;SessionFactory&lt;/code&gt; — one per application, heavyweight, thread-safe. Created at startup, lives until shutdown.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Session&lt;/code&gt; — one per unit of work, lightweight, not thread-safe. Opened and closed per transaction.&lt;/li&gt;
&lt;li&gt;Persistence Context — the first-level cache inside the &lt;code&gt;Session&lt;/code&gt;. Provides identity, dirty checking, and write-behind.&lt;/li&gt;
&lt;li&gt;Never share a &lt;code&gt;Session&lt;/code&gt; across threads. Ever.&lt;/li&gt;
&lt;li&gt;Never create a &lt;code&gt;SessionFactory&lt;/code&gt; per request. Ever.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OpenSessionInView&lt;/code&gt; is an anti-pattern. Disable it in production.&lt;/li&gt;
&lt;li&gt;After a &lt;code&gt;Session&lt;/code&gt; is closed, entities become Detached. Attempting lazy loading will result in a &lt;code&gt;LazyInitializationException&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>spring</category>
      <category>learning</category>
      <category>java</category>
    </item>
    <item>
      <title>Spring AI: First Steps</title>
      <dc:creator>Silver_dev</dc:creator>
      <pubDate>Mon, 19 Oct 2026 21:50:50 +0000</pubDate>
      <link>https://dev.to/silver_dev/spring-ai-first-steps-54il</link>
      <guid>https://dev.to/silver_dev/spring-ai-first-steps-54il</guid>
      <description>&lt;h2&gt;
  
  
  Preparation
&lt;/h2&gt;

&lt;p&gt;Ensure that Ollama is running and the model is downloaded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull qwen2.5:3b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.ai.ollama.chat.options.model&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;qwen2.5:3b&lt;/span&gt;
&lt;span class="py"&gt;spring.ai.ollama.base-url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;http://localhost:11434&lt;/span&gt;

&lt;span class="c"&gt;# Optional: temperature (0.0 - deterministic, 1.0 - creative)
&lt;/span&gt;&lt;span class="py"&gt;spring.ai.ollama.chat.temperature&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0.7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can use any other model — the name must exactly match the output of ollama list.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. AI Hello World
&lt;/h2&gt;

&lt;p&gt;Create a controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.example.demo&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.ai.chat.client.ChatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.web.bind.annotation.GetMapping&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.web.bind.annotation.RestController&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AiController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;AiController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;chatClientBuilder&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatClientBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/ai/hello"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;aiHello&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Formulate the prompt&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Say 'Hello, World!' and add one short, interesting fact about the Java language."&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Call the model and get the response&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// .content() extracts just the response string from the ChatResponse object&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;a href="http://localhost:8080/ai/hello" rel="noopener noreferrer"&gt;http://localhost:8080/ai/hello&lt;/a&gt; in your browser.&lt;/p&gt;

&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first request may take 2–5 seconds — the model is loading into memory.&lt;/li&gt;
&lt;li&gt;Error 404 Model not found — check the exact model name in &lt;code&gt;ollama list&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.call().content()&lt;/code&gt; is a synchronous call. For a chat with a typing effect, use &lt;code&gt;.stream()&lt;/code&gt; (see section 5).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Structured Output (JSON → Java Record)
&lt;/h2&gt;

&lt;p&gt;Spring AI can return not just plain text, but a ready-made Java object. Create a record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.example.demo&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;RoutingIntent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;targetService&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;priority&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add an endpoint to &lt;code&gt;AiController.java&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/ai/route"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;RoutingIntent&lt;/span&gt; &lt;span class="nf"&gt;routeRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;systemPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""
        You are a smart API gateway. Analyze the user's request 
        and extract data for routing. 
        If a field is unknown, leave it empty (null or 0).
        """&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;systemPrompt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;entity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RoutingIntent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example: &lt;a href="http://localhost:8080/ai/route?text=Disable" rel="noopener noreferrer"&gt;http://localhost:8080/ai/route?text=Disable&lt;/a&gt; notifications for user user-999&lt;/p&gt;

&lt;p&gt;Response: A ready-made JSON, serialized by Spring. No manual parsing required.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Function Calling (LLM calls Java code)
&lt;/h2&gt;

&lt;p&gt;Give the model "tools" — Java methods annotated with &lt;code&gt;@Tool&lt;/code&gt;. If the user asks, "What is the status of the payment service?", the LLM won't hallucinate an answer. Instead, it tells Spring AI: "Hey, call the &lt;code&gt;getServiceStatus("payment")&lt;/code&gt; method, and I will use its result."&lt;/p&gt;

&lt;p&gt;Create the &lt;code&gt;SystemTools.java&lt;/code&gt; class. The &lt;code&gt;@Tool&lt;/code&gt; annotation makes the method visible to the LLM.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.example.demo&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.stereotype.Service&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.ai.tool.annotation.Tool&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.ai.tool.annotation.ToolParam&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SystemTools&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Tool&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Checks the current status and latency (in ms) of the specified microservice"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getServiceStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@ToolParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Name of the service, e.g., 'payment', 'auth', 'database'"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;serviceName&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// A real call to Prometheus, ClickHouse, or your proxy logger could go here&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;gt;&amp;gt;&amp;gt; LLM called Java method for service: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;serviceName&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;switch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serviceName&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toLowerCase&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"payment"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Status: OK, average latency 45ms."&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"auth"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Status: DEGRADED, latency 300ms, experiencing timeouts."&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Status: Unknown service or no data available."&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;};&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update &lt;code&gt;AiController.java&lt;/code&gt; by adding a dependency on &lt;code&gt;SystemTools&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.example.demo&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.ai.chat.client.ChatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.web.bind.annotation.GetMapping&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.web.bind.annotation.RequestParam&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.web.bind.annotation.RestController&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AiController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Inject our tools&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;AiController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;chatClientBuilder&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SystemTools&lt;/span&gt; &lt;span class="n"&gt;systemTools&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatClientBuilder&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultTools&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;systemTools&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;--- this is the key line&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/ai/tool"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;askSystem&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Request: &lt;a href="http://localhost:8080/ai/tool?question=What" rel="noopener noreferrer"&gt;http://localhost:8080/ai/tool?question=What&lt;/a&gt; is the current status of the payment and auth services?&lt;/p&gt;

&lt;p&gt;The LLM will decide to call &lt;code&gt;getServiceStatus("payment")&lt;/code&gt;, Spring AI will execute the method, and the model will formulate the final response based on the result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens "under the hood":&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The LLM sees the question and realizes it doesn't know the answer.&lt;/li&gt;
&lt;li&gt;It sees your &lt;code&gt;@Tool&lt;/code&gt; and generates a special request: "Call &lt;code&gt;getServiceStatus&lt;/code&gt; with the argument &lt;code&gt;payment&lt;/code&gt;".&lt;/li&gt;
&lt;li&gt;Spring AI intercepts this and calls your actual Java method.&lt;/li&gt;
&lt;li&gt;In the console, you will see: &lt;code&gt;&amp;gt;&amp;gt;&amp;gt; LLM called Java method for service: payment&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The LLM receives the result ("Status: OK...") and formulates the final human-readable response.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  4. Prompt Templates (Dynamic Prompts)
&lt;/h2&gt;

&lt;p&gt;You need to formulate prompts dynamically, substituting data from the context (user name, request parameters, database results). Hardcoding prompts is a bad practice. Spring AI allows you to create prompt templates with variables, similar to Thymeleaf or Mustache.&lt;/p&gt;

&lt;p&gt;Add a new endpoint to &lt;code&gt;AiController.java&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.example.demo&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.ai.chat.client.ChatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.ai.chat.prompt.PromptTemplate&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.web.bind.annotation.GetMapping&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.web.bind.annotation.RequestParam&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.web.bind.annotation.RestController&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Map&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AiController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;SystemTools&lt;/span&gt; &lt;span class="n"&gt;systemTools&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;AiController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;chatClientBuilder&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SystemTools&lt;/span&gt; &lt;span class="n"&gt;systemTools&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatClientBuilder&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultTools&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;systemTools&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ... your previous /ai/hello, /ai/route, /ai/tool methods ...&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/ai/template"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;generateWithTemplate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;userName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;serviceName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Create a prompt template with variables&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;templateText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""
            You are a system administrator. User {userName} wants to perform the action '{action}' 
            on the {serviceName} service. 
            Provide a brief instruction on how to do this safely.
            """&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Populate the variables&lt;/span&gt;
        &lt;span class="nc"&gt;PromptTemplate&lt;/span&gt; &lt;span class="n"&gt;promptTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PromptTemplate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;templateText&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;finalPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;promptTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;render&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"userName"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;"serviceName"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;serviceName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;"action"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;
        &lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="c1"&gt;// Send the finalized prompt to the model&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;finalPrompt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example: &lt;a href="http://localhost:8080/ai/template?userName=John&amp;amp;serviceName=payment&amp;amp;action=restart" rel="noopener noreferrer"&gt;http://localhost:8080/ai/template?userName=John&amp;amp;serviceName=payment&amp;amp;action=restart&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;John, to safely restart the payment service, follow these steps:
1. Ensure there are no active transactions...
2. Execute the restart command...
3. Check the service logs...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Templates can be stored in separate files (&lt;code&gt;.txt&lt;/code&gt;, &lt;code&gt;.prompt&lt;/code&gt;) and versioned separately from the codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Streaming (Real-time Response Streaming)
&lt;/h2&gt;

&lt;p&gt;If your proxy communicates with the user in real time (e.g., a chatbot or voice assistant), waiting 2–3 seconds for a full response is bad UX. Streaming allows delivering the response token by token as soon as they are generated. For chat interfaces, use &lt;code&gt;.stream()&lt;/code&gt; and &lt;code&gt;Flux&amp;lt;String&amp;gt;&lt;/code&gt;. Spring Boot automatically converts this to Server-Sent Events (SSE).&lt;/p&gt;

&lt;p&gt;Add the dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-starter-webflux&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;resources/static/stream-test.html&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;AI Stream Test&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Arial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;800px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50px&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;#question&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;70%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;#response&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#ccc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;150px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f9f9f9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;white-space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pre-wrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;.cursor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inline-block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;18px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;blink&lt;/span&gt; &lt;span class="m"&gt;0.7s&lt;/span&gt; &lt;span class="n"&gt;infinite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;vertical-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;text-bottom&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="nb"&gt;blink&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="err"&gt;50&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;🤖 AI Streaming Chat&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"question"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Enter a question..."&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Tell me a short fact about Java"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"startStream()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Send&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"response"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
        &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;startStream&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;question&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;question&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responseDiv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;response&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// Clear previous response&lt;/span&gt;
            &lt;span class="nx"&gt;responseDiv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;span class="cursor"&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="c1"&gt;// Use fetch + ReadableStream (more reliable than EventSource)&lt;/span&gt;
            &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/ai/stream?question=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;question&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextDecoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                &lt;span class="c1"&gt;// Remove cursor when the stream ends&lt;/span&gt;
                                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;responseDiv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.cursor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                                &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                                &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                            &lt;span class="p"&gt;}&lt;/span&gt;

                            &lt;span class="c1"&gt;// Decode bytes to text&lt;/span&gt;
                            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;decoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

                            &lt;span class="c1"&gt;// Parse SSE: each "data:..." line&lt;/span&gt;
                            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                            &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                            &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                    &lt;span class="c1"&gt;// Remove "data:" prefix and append to text&lt;/span&gt;
                                    &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                                &lt;span class="p"&gt;}&lt;/span&gt;
                            &lt;span class="p"&gt;}&lt;/span&gt;

                            &lt;span class="c1"&gt;// Insert text before the cursor&lt;/span&gt;
                            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;responseDiv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.cursor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                &lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertAdjacentText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;beforebegin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                &lt;span class="nx"&gt;responseDiv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                            &lt;span class="p"&gt;}&lt;/span&gt;

                            &lt;span class="c1"&gt;// Read the next chunk&lt;/span&gt;
                            &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                        &lt;span class="p"&gt;});&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;

                    &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="p"&gt;})&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nx"&gt;responseDiv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the streaming endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.http.MediaType&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;reactor.core.publisher.Flux&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ... inside AiController ...&lt;/span&gt;

&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/ai/stream"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;produces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MediaType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;TEXT_EVENT_STREAM_VALUE&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;";charset=UTF-8"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Flux&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;streamResponse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open in your browser: &lt;a href="http://localhost:8080/stream-test.html" rel="noopener noreferrer"&gt;http://localhost:8080/stream-test.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The response will appear gradually, word by word, just like in ChatGPT. This works via SSE (Server-Sent Events) — the browser receives a continuous data stream and renders it in real time.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tutorial</category>
      <category>spring</category>
    </item>
    <item>
      <title>The finale of the Vector Database Showroom series.</title>
      <dc:creator>Silver_dev</dc:creator>
      <pubDate>Fri, 09 Oct 2026 19:21:26 +0000</pubDate>
      <link>https://dev.to/silver_dev/the-finale-of-the-vector-database-showroom-series-2e7g</link>
      <guid>https://dev.to/silver_dev/the-finale-of-the-vector-database-showroom-series-2e7g</guid>
      <description>&lt;p&gt;The finale of the Vector Database Showroom series.&lt;/p&gt;

&lt;p&gt;Six parts ago, this showroom made you a promise: we won't tell you which database to take — we'll give you a method to figure out what YOU should take. Today the showroom closes: one table, one tree, one checklist.&lt;/p&gt;

&lt;p&gt;The roster, for the record: 🚙 pgvector, 🛴 Chroma, 🏎️ Qdrant, 🚐 Weaviate, 🚕 Pinecone, 🚄 Milvus — the station wagon, the scooter, the hatchback, the crossover, the taxi, and the train.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cost-of-ownership table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Vehicle&lt;/th&gt;
&lt;th&gt;License / self-host&lt;/th&gt;
&lt;th&gt;Sweet spot&lt;/th&gt;
&lt;th&gt;Signature feature&lt;/th&gt;
&lt;th&gt;Main pain&lt;/th&gt;
&lt;th&gt;Ops burden&lt;/th&gt;
&lt;th&gt;Managed by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;🚙 &lt;a href="https://dev.to/silver_dev/vector-database-showroom-part-1-pgvector-the-station-wagon-already-in-your-garage-21of"&gt;pgvector&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;PostgreSQL license, self-host anywhere&lt;/td&gt;
&lt;td&gt;~1M comfortable, ~10M tuned&lt;/td&gt;
&lt;td&gt;ACID + joins — vectors live with your data&lt;/td&gt;
&lt;td&gt;Vertical ceiling; RAM for the HNSW graph&lt;/td&gt;
&lt;td&gt;Your usual Postgres routine&lt;/td&gt;
&lt;td&gt;RDS, Aurora, Cloud SQL, AlloyDB, Azure, Supabase, Neon&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🛴 &lt;a href="https://dev.to/silver_dev/vector-database-showroom-part-2-chroma-the-e-scooter-that-starts-in-90-second-23gn"&gt;Chroma&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Apache 2.0, embedded or server&lt;/td&gt;
&lt;td&gt;Prototypes; single-digit millions&lt;/td&gt;
&lt;td&gt;First search in 90 seconds, embeddings included&lt;/td&gt;
&lt;td&gt;Single node by design; prototype-grade durability&lt;/td&gt;
&lt;td&gt;None — until it's suddenly too many&lt;/td&gt;
&lt;td&gt;Chroma Cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🏎️ &lt;a href="https://dev.to/silver_dev/vector-database-showroom-part-3-qdrant-the-hot-hatchback-that-carries-more-than-it-looks-2ocm"&gt;Qdrant&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Apache 2.0, single binary → cluster&lt;/td&gt;
&lt;td&gt;Millions → hundreds of millions (quantized)&lt;/td&gt;
&lt;td&gt;Filterable HNSW — filters live inside the index&lt;/td&gt;
&lt;td&gt;Manual snapshots; a tuning culture&lt;/td&gt;
&lt;td&gt;Light-to-medium DevOps&lt;/td&gt;
&lt;td&gt;Qdrant Cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🚐 &lt;a href="https://dev.to/silver_dev/vector-database-showroom-part-4-weaviate-the-crossover-that-ships-fully-loaded-4dnf"&gt;Weaviate&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;BSD-3-Clause, containers&lt;/td&gt;
&lt;td&gt;Multi-tenant SaaS; scale via distributed mode&lt;/td&gt;
&lt;td&gt;Factory vectorization + built-in BM25F hybrid + native tenancy&lt;/td&gt;
&lt;td&gt;A service bay for all those options; the v3/v4 client split&lt;/td&gt;
&lt;td&gt;Medium DevOps&lt;/td&gt;
&lt;td&gt;Weaviate Cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🚕 &lt;a href="https://dev.to/silver_dev/vector-database-showroom-part-4-pinecone-the-car-by-subscription-4fnk"&gt;Pinecone&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Proprietary — cloud only&lt;/td&gt;
&lt;td&gt;Prototype → billions, if the wallet agrees&lt;/td&gt;
&lt;td&gt;True zero ops + serverless&lt;/td&gt;
&lt;td&gt;A closed box, and the meter (read units)&lt;/td&gt;
&lt;td&gt;None — the dealer's problem&lt;/td&gt;
&lt;td&gt;Pinecone only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🚄 &lt;a href="https://dev.to/silver_dev/vector-database-showroom-part-6-milvus-the-high-speed-freight-train-536o"&gt;Milvus&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Apache 2.0 — Lite / Standalone / Distributed&lt;/td&gt;
&lt;td&gt;Hundreds of millions → billions&lt;/td&gt;
&lt;td&gt;Capacity scales with object storage; GPU_CAGRA; DiskANN&lt;/td&gt;
&lt;td&gt;The rails: etcd, Pulsar/Kafka, MinIO/S3&lt;/td&gt;
&lt;td&gt;Real ops muscle&lt;/td&gt;
&lt;td&gt;Zilliz Cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two synthesis notes the table doesn't fit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hybrid search out of the box: yes — Qdrant (dense + sparse, RRF/DBSF fusion), Weaviate (BM25F built in), Milvus (built-in BM25). No — pgvector, Chroma. Pinecone: you bring the sparse vectors yourself (the Qdrant model, not the built-in-BM25 model of Weaviate/Milvus) — check the docs for the current state before betting on it.&lt;/li&gt;
&lt;li&gt;Multi-tenancy, natively: Weaviate (tenant = shard), Pinecone (namespaces), Milvus (partition keys / separate databases). pgvector covers it with RLS; Qdrant expects payload filters; Chroma assembles nothing for you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The recurring math of the series:&lt;/strong&gt; 1M × 1536-dim × 4 bytes ≈ 6 GB of raw vectors — and an HNSW graph stores a copy of every vector, so roughly double it before picking instance sizes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Honorable mentions, three lines:&lt;/strong&gt; Elasticsearch/OpenSearch — if it's already your stack and the vector requirements are modest. Redis — when you need sub-millisecond and already run it. FAISS — an engine, not a car: blazingly fast, but no garage, no service, no keys — you build the car around it.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    START["You need vector search"] --&amp;gt; Q1{"Prototyping, learning,\nor a personal tool?"}
    Q1 -- "Yes" --&amp;gt; CHROMA["🛴 Chroma"]
    Q1 -- "No" --&amp;gt; Q2{"Data already in Postgres\nand under ~10M vectors?"}
    Q2 -- "Yes" --&amp;gt; PGVECTOR["🚙 pgvector"]
    Q2 -- "No" --&amp;gt; Q3{"Anyone on the team\nwants to own infrastructure?"}
    Q3 -- "Yes — wants to own infra" --&amp;gt; Q4
    Q3 -- "Must self-host (compliance)" --&amp;gt; Q4
    Q4{"Hundreds of millions\nto billions of vectors?"}
    Q4 -- "Yes" --&amp;gt; MILVUS["🚄 Milvus — or rent Zilliz Cloud"]
    Q4 -- "No" --&amp;gt; Q5{"Many tenants, want\nvectorization + hybrid\nfrom the factory?"}
    Q5 -- "Yes" --&amp;gt; WEAVIATE["🚐 Weaviate"]
    Q5 -- "No" --&amp;gt; QDRANT["🏎️ Qdrant"]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Two honest notes on the tree:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No Postgres yet? The wagon is still an option — managed Postgres is one click away (Part 1). The garage is not a prerequisite.&lt;/li&gt;
&lt;li&gt;Data can't leave your perimeter AND nobody wants ops? That's a hiring problem, not a database problem. No car in this showroom fixes it — and pretending otherwise is how two-week adventures begin.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The one-day test drive
&lt;/h2&gt;

&lt;p&gt;Pick 2–3 finalists from the tree — not all six. Then:&lt;/p&gt;

&lt;h3&gt;
  
  
  Before you start
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;One embedding model for all candidates — the honest-comparison rule since Part 3&lt;/li&gt;
&lt;li&gt;Real data: 10–50k real documents and 50–100 real queries from your product. Lorem ipsum measures lorem ipsum&lt;/li&gt;
&lt;li&gt;Deploy the gauge you'll actually ride: if production is a cluster, benchmark the cluster shape — the Milvus lesson&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  (2–3 hours)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create the schema and payload/metadata indexes before loading — the Qdrant and Milvus lessons&lt;/li&gt;
&lt;li&gt;Load the data. Then wait: settled segments, built indexes (benchmark traps #1 and #2). A benchmark on fresh data measures brute force, not your index&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  (2 hours) — the scorecard
&lt;/h3&gt;

&lt;p&gt;Recall@10 vs exact ground truth. Brute-force the same vectors yourself — at 50k rows it takes seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;

&lt;span class="c1"&gt;# vectors: (N, 1536) of your uploaded embeddings, query: (1536,)
# ground truth must use the SAME metric you query with —
# for cosine, normalize first
&lt;/span&gt;&lt;span class="n"&gt;scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vectors&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
&lt;span class="n"&gt;truth_indices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;argsort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:][::&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;   &lt;span class="c1"&gt;# exact top-10 INDICES into `vectors`
&lt;/span&gt;
&lt;span class="c1"&gt;# Option 1: if your DB returns positions in the same order you uploaded,
# compare indices directly
&lt;/span&gt;&lt;span class="n"&gt;recall&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;truth_indices&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;returned_indices&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

&lt;span class="c1"&gt;# Option 2: if your DB returns IDs, map indices to IDs first —
# the upload order defines the mapping, keep it consistent
# ids = ["doc_0", "doc_1", ...]          # index i -&amp;gt; id, fixed at upload time
# truth_ids = {ids[i] for i in truth_indices}
# recall = len(truth_ids &amp;amp; set(returned_ids)) / 10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;p95 latency at your realistic QPS, not the demo QPS&lt;/li&gt;
&lt;li&gt;RAM after indexing — expect roughly 2× the raw vectors (the series rule)&lt;/li&gt;
&lt;li&gt;Filtered search: does "similar AND year=2024" return the full LIMIT? (the Part 1 trap)&lt;/li&gt;
&lt;li&gt;Insert → immediately search: flaky? Now you know before production does (the taxi and the train)&lt;/li&gt;
&lt;li&gt;Kill the process mid-write (self-hosted) or test retry after errors (managed) — then check nothing was lost&lt;/li&gt;
&lt;li&gt;Export everything back out — measure the exit in hours, not promises&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(1 hour) — the bill&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Managed (Pinecone): your real QPS × top_k → read units → dollars. Managed (Qdrant / Weaviate / Zilliz Cloud): RAM + CPU → monthly subscription. Either way, the meter never sleeps&lt;/li&gt;
&lt;li&gt;Self-hosted: the RAM you measured → the smallest instance that fits → × 12 months&lt;/li&gt;
&lt;li&gt;Fill the scorecard, then sleep on it:&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Candidate&lt;/th&gt;
&lt;th&gt;recall@10&lt;/th&gt;
&lt;th&gt;p95, ms&lt;/th&gt;
&lt;th&gt;RAM after index, GB&lt;/th&gt;
&lt;th&gt;$/month at your QPS&lt;/th&gt;
&lt;th&gt;Exit, hours&lt;/th&gt;
&lt;th&gt;Gut feeling&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Yes, "gut feeling" is a column. DX is a cost of ownership too — you'll pay it every working day.&lt;/p&gt;

&lt;p&gt;Next morning: re-run the same queries cold. Numbers that only look good warm are a finding, not a footnote.&lt;/p&gt;

&lt;h2&gt;
  
  
  The method doesn't expire
&lt;/h2&gt;

&lt;p&gt;Everything in this series was true at the time of writing; databases evolve — verify versions against the docs. The method doesn't expire: judge by cost of ownership, verify on your data, buy for the scale you have, re-check at the scale you reach.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>database</category>
      <category>learning</category>
    </item>
    <item>
      <title>Vector database showroom. Part 6 Milvus — The High-Speed Freight Train</title>
      <dc:creator>Silver_dev</dc:creator>
      <pubDate>Fri, 09 Oct 2026 18:58:22 +0000</pubDate>
      <link>https://dev.to/silver_dev/vector-database-showroom-part-6-milvus-the-high-speed-freight-train-536o</link>
      <guid>https://dev.to/silver_dev/vector-database-showroom-part-6-milvus-the-high-speed-freight-train-536o</guid>
      <description>&lt;h2&gt;
  
  
  🚄 Milvus — The High-Speed Freight Train
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Billions of vectors aboard, uncontested on the mainline. But nobody rides before the rails are built.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Under the hood:&lt;/strong&gt; a Go + C++ core, Apache 2.0, a graduated project of the Linux Foundation's LF AI &amp;amp; Data, driven by Zilliz (who also sell the managed ride, Zilliz Cloud). The distributed deployment is a real architecture: proxy, coordinator services, and worker nodes (query, data, index) — all hanging off a central log (Pulsar or Kafka), with etcd for metadata and MinIO/S3 for object storage. Your vectors live in object storage; storage and compute are separated. One train, three gauges: Milvus Lite (embedded, pip install, desk scale), Milvus Standalone (a rail bus — three containers: Milvus itself, etcd for metadata, MinIO for object storage), and Milvus Distributed (the mainline — Helm on Kubernetes).&lt;/p&gt;

&lt;h3&gt;
  
  
  Where it shines:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Billions of vectors is the home stadium:&lt;/strong&gt; capacity scales with your object storage, not your RAM budget; horizontal scale means adding worker nodes. This is the tool that made "vector database" a category&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPU acceleration (since v2.4):&lt;/strong&gt; GPU_CAGRA built on NVIDIA's RAPIDS — for when search is the bottleneck and you have the silicon&lt;/li&gt;
&lt;li&gt;DiskANN (since v2.4): the index lives on SSD — vectors beyond RAM, latency traded for capacity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sparse vectors&lt;/strong&gt; since v2.4, and since v2.5 a built-in &lt;strong&gt;BM25&lt;/strong&gt; full-text search — the factory-grade hybrid, no separate sparse embedder required&lt;/li&gt;
&lt;li&gt;Multi-tenancy with documented patterns (partition keys, separate databases), RBAC, TLS — and Attu, a proper admin GUI. The only vehicle in the showroom that ships with a cockpit display&lt;/li&gt;
&lt;li&gt;One of the largest OSS communities in the category; SDKs for Python/Java/Go/Node; LangChain/LlamaIndex integrations; Zilliz Cloud if you'd rather rent the railway&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where it stalls:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The rails:&lt;/strong&gt; Distributed means etcd + Pulsar/Kafka + MinIO/S3 + the Milvus pods themselves. Helm charts help, but the ops muscle is non-negotiable. (Lite and Standalone genuinely shrink this — see the manual below)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bring your own embeddings&lt;/strong&gt; — a database, not an embedder (same rule as the hatchback and the crossover)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No ACID transactions "like in Postgres":&lt;/strong&gt; batches are not transactions — the station wagon still owns that lane&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tuning is part of the contract:&lt;/strong&gt; M, efConstruction, nprobe/ef per index type — and a consistency level per query&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backups&lt;/strong&gt; run through the dedicated &lt;strong&gt;milvus-backup tool&lt;/strong&gt;: plan it and rehearse it. There's no "it's just my Postgres cluster" simplicity here&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What breaks if you skip the manual:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Bought the mainline for the street corner. A composite story from community chats, details changed: a team runs semantic search over 300k documents on distributed Milvus — a small zoo of pods, of which exactly two kinds accept queries — because a big-tech talk mentioned billions. A junior asks: "Why not Postgres? We already have Postgres." Calculator: 300,000 × 1536 dims × 4 bytes ≈ 1.8 GB. The right gauge existed all along: Milvus Lite for the prototype, Standalone for small production, Distributed for the billions. Milvus isn't the mistake — the scale mismatch is&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Benchmark trap #2&lt;/strong&gt; (the hatchback's cousin): fresh inserts land in growing segments that are searched by brute force until they're sealed and indexed. A naive benchmark right after loading — or on a tiny collection — measures exact search, not your index. Honest numbers: production-like volume, settled segments, warm queries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency levels are per-query&lt;/strong&gt; (default: bounded): strong / bounded / session / eventually. With weaker levels, "insert → immediately search" tests flake exactly like the taxi's eventual consistency. Pick session or strong for the paths that need it&lt;/li&gt;
&lt;li&gt;Metric and dimension are locked at collection creation — same rule as the hatchback and the taxi. The escape hatch here is actually elegant: build the new collection and flip a collection alias — the zero-downtime re-index is a documented pattern&lt;/li&gt;
&lt;li&gt;The partition key (if you use partition-key tenancy) is fixed at creation too — retrofitting tenancy later means re-shipping the data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cost of ownership:&lt;/strong&gt; the train is free (Apache 2.0); the railroad isn't — count the ops salary, not just the license. Zilliz Cloud (managed, serverless tier included) rents you the whole railway. Self-hosted Lite and Standalone genuinely fit a laptop and a small server respectively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mechanics &amp;amp; parts:&lt;/strong&gt; Zilliz drives development; LF AI &amp;amp; Data provides neutral governance; the community is one of the largest in the category. "Milvus engineers" are rarer than Postgres ones — but this is a Kubernetes-native system your DevOps team can actually operate, with vendor support available when the stakes are high.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Take it if:&lt;/strong&gt; you're at hundreds of millions to billions of vectors, need GPU or beyond-RAM capacity, run many tenants, have (or hire) real ops muscle — or use Zilliz Cloud and skip the rails&lt;/p&gt;

&lt;p&gt;❌ &lt;strong&gt;Pass if:&lt;/strong&gt; you're under ~10M vectors on a single box (the hatchback or the wagon covers it), "we plan to grow" is your only argument, or nobody on the team wants to own a Helm chart&lt;/p&gt;

&lt;p&gt;Test drive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Milvus Lite runs embedded — no containers needed (Linux/macOS)
# Production gauges: Standalone (3-container compose) or Distributed (Helm on K8s)
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pymilvus&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MilvusClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DataType&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MilvusClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./milvus_demo.db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# the desk model of the train
&lt;/span&gt;
&lt;span class="c1"&gt;# explicit schema: scalar fields never appear by themselves (Milvus is schema-first,
# no AutoSchema like Weaviate). auto_id=False keeps our own ids — same as Parts 3–5
&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;enable_dynamic_field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DataType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INT64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;is_primary&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vector&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DataType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FLOAT_VECTOR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1536&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DataType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;year&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DataType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INT64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;index_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare_index_params&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;index_params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vector&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AUTOINDEX&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metric_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;COSINE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;collection_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;docs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;index_params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;index_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;collection_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;docs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vector&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Quarterly report 2024&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Quarterly report 2024&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;year&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vector&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Legal contract draft&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Legal contract draft&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;year&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# note: fresh data sits in "growing segments" searched by brute force until they are
# sealed and indexed (benchmark trap #2). flush() seals segments — but in production
# let auto-sealing do its job; frequent manual flushes are an anti-pattern
&lt;/span&gt;
&lt;span class="c1"&gt;# embed() — your embedding function (the same one from Parts 3–5 — honest comparison)
&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;collection_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;docs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;annual financial summary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
    &lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;year == 2024&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;output_fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;year&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;hit&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hit&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hit&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;distance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;hit&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;entity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;year&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The train is magnificent — on the mainline, where it was built to run. Ask it at 300k vectors — Postgres wins. Ask it at 3 billion — the train wins. Buy for the scale you have; re-check at the scale you reach.&lt;/p&gt;

&lt;p&gt;🚦 Final part of the showroom: all six vehicles side by side — the cost-of-ownership table, the decision tree, and a one-day test-drive checklist for the car you pick.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>database</category>
      <category>learning</category>
    </item>
    <item>
      <title>Vector database showroom. Part 5 Pinecone — The Car by Subscription</title>
      <dc:creator>Silver_dev</dc:creator>
      <pubDate>Fri, 09 Oct 2026 17:36:20 +0000</pubDate>
      <link>https://dev.to/silver_dev/vector-database-showroom-part-4-pinecone-the-car-by-subscription-4fnk</link>
      <guid>https://dev.to/silver_dev/vector-database-showroom-part-4-pinecone-the-car-by-subscription-4fnk</guid>
      <description>&lt;h2&gt;
  
  
  🚕 Pinecone — The Car by Subscription
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;— I want a vector database. I don't want: HNSW tuning, container patching, changelog reading, or ever thinking about RAM.&lt;br&gt;
— Have I got a car for you. You won't have to drive.&lt;br&gt;
— What's the catch?&lt;br&gt;
— No catch. It's a subscription: the meter ticks on every ride, and you can never buy the car. Now — the fine print.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For those who'd rather not drive at all. The fine print: the meter runs on every mile.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Under the hood:&lt;/strong&gt; the only fully managed player in this showroom. Closed source, no self-hosting — not a limitation, a product decision. New users get the serverless architecture: storage and compute are decoupled and scale on their own. There's nothing to tune — and that's the advertised feature.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where it shines:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Genuinely zero ops:&lt;/strong&gt; no upgrades, no manual backups, no memory planning. &lt;code&gt;pip install pinecone&lt;/code&gt; → API key → index → production. Of all the cars here, this one starts faster than the scooter&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integrated embedding (optional):&lt;/strong&gt; ship raw text and Pinecone embeds it via hosted models — a feature the scooter from Part 2 envies. &lt;strong&gt;Heads-up:&lt;/strong&gt; an integrated index is a separate index type and doesn't accept your own vectors — one approach per index. The test drive below uses the classic "bring your own vectors" — backend devs prefer it for apples-to-apples comparison (our promise since Part 3)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-tenancy via namespaces:&lt;/strong&gt; tenants isolated inside a single index&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best-in-class DX:&lt;/strong&gt; docs, console, SDKs (Python/JS/Go/Java), LangChain/LlamaIndex integrations — Pinecone is the default of half the RAG tutorials on the internet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise compliance badges:&lt;/strong&gt; cloud and region of your choice (AWS/GCP/Azure). Though your data still lives in their cloud — see fine print&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where it stalls:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A closed black box, no workarounds:&lt;/strong&gt; reading the code, patching it, or moving to your own servers — not possible. If your compliance regime says "data never leaves our perimeter" — walk away, and check this before designing the system&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero knobs:&lt;/strong&gt; no hnsw_ef, no quantization — the server decides everything. Unhappy with recall? Your freedom is top_k. That's it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No joins, no SQL&lt;/strong&gt; — expected; the station wagon is still around&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Costs grow non-linearly:&lt;/strong&gt; high-QPS at large top_k is the plot of those "our Pinecone invoice" posts&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What breaks if you skip the manual:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The meter counts what you read:&lt;/strong&gt; every query burns read units. top_k=100, include_values=true (returning the raw vectors!), and a nightly test run in CI — three shock absorbers burning your budget. Only set include_metadata when you actually need the metadata&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Eventual consistency:&lt;/strong&gt; right after an upsert, a query may not see the fresh vectors. The test "write → immediately read" fails flakily. A pause or retry is the standard pattern&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queries live inside a namespace:&lt;/strong&gt; you uploaded to "2024" but queried without a namespace — empty result. Not a bug: the default namespace is literally the empty string "" — a separate apartment from "2024"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metadata types are strict:&lt;/strong&gt; "2024" (string) and 2024 (number) are different values — a numeric filter won't match strings. Pick the type once, for the whole team&lt;/li&gt;
&lt;li&gt;The metric (cosine/dotproduct/euclidean) and dimension are locked at index creation — same rule as the hatchback, except there's no "rebuild the collection on our own servers" escape hatch: new index + re-upload&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cost of ownership:&lt;/strong&gt; a subscription. Free tier for prototypes; then storage + read/write units. This is the only car in the showroom you cannot buy: there is no self-hosted edition and never will be. You can export your data (fetch, bulk export) — but that's moving your stuff out of a rented apartment, not selling the car.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mechanics &amp;amp; parts:&lt;/strong&gt; Pinecone (New York) behind it; the founder came out of Amazon's AI lab. You can't hire a "Pinecone mechanic" — you can't look under the hood. The upside: nothing for you to fix — if it breaks, the dealer repairs it. There's no grease-monkey community by design: all the wrenches belong to the vendor.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Take it if:&lt;/strong&gt; you have no ops team (and won't), prototype-to-production in one day, spiky traffic, no "data stays in our perimeter" requirement, and a variable bill doesn't scare you&lt;/p&gt;

&lt;p&gt;❌ &lt;strong&gt;Pass if:&lt;/strong&gt; your data can't leave your perimeter; you want to steer recall and cost with your own hands; or you're pushing billions of vectors at sustained high QPS — do the TCO math against the train&lt;/p&gt;

&lt;p&gt;Test drive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# modern SDK — pip install -U pinecone
# (the legacy `pinecone-client` package name is deprecated)
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pinecone&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Pinecone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ServerlessSpec&lt;/span&gt;

&lt;span class="n"&gt;pc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Pinecone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# metric and dimension are locked at creation — same rule as the hatchback
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;docs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list_indexes&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;names&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;docs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;dimension&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1536&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;metric&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cosine&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ServerlessSpec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cloud&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;us-east-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# required in the modern SDK
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;docs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# embed() — your embedding function (the same one from Parts 3–4 — honest comparison)
&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;vectors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;values&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Quarterly report 2024&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;year&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;values&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Legal contract draft&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;year&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# heads-up: right after upsert, a query may not see these vectors yet (eventual consistency)
&lt;/span&gt;
&lt;span class="c1"&gt;# the meter ticks here: top_k and include_metadata cost read units
# include_values=True is the budget-killer from "what breaks" above — only if you truly need raw vectors
&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;annual financial summary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;year&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$eq&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
    &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;include_metadata&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;matches&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;year&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The whole ride without a single thought about servers. The subscription keeps its promise. The only question: how far will you drive — and what the meter reads at the end of the month.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>database</category>
      <category>learning</category>
    </item>
    <item>
      <title>Vector database showroom. Part 4 Weaviate — The Crossover That Ships Fully Loaded</title>
      <dc:creator>Silver_dev</dc:creator>
      <pubDate>Fri, 09 Oct 2026 15:54:32 +0000</pubDate>
      <link>https://dev.to/silver_dev/vector-database-showroom-part-4-weaviate-the-crossover-that-ships-fully-loaded-4dnf</link>
      <guid>https://dev.to/silver_dev/vector-database-showroom-part-4-weaviate-the-crossover-that-ships-fully-loaded-4dnf</guid>
      <description>&lt;h2&gt;
  
  
  🚐 Weaviate — The Crossover That Ships Fully Loaded
&lt;/h2&gt;

&lt;p&gt;Every option already installed: vectorization, hybrid search, multi-tenancy. One question — who services all those options?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Under the hood:&lt;/strong&gt; a Go core under BSD-3-Clause, one container to start. Three protocols: REST, GraphQL, and gRPC, with the v4 client using gRPC as its fast lane. The philosophy: "bring your data, the factory does the rest" — vectorizer modules, hybrid search, and generation are toggled by config, not external code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where it shines:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Factory vectorization:&lt;/strong&gt; attach a module (local transformers, OpenAI, Cohere, multimodal CLIP) and the database embeds on insert. Contrast with the scooter: there the embedder is hardwired; here it's a factory option you choose&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid, factory-grade:&lt;/strong&gt; BM25F (the lexical index is already inside) + vectors in a single query, with the alpha knob balancing semantics vs keywords. No separate sparse embedder needed — unlike the hatchback&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native multi-tenancy — the signature feature:&lt;/strong&gt; tenant = its own shard, isolated search and performance, active/inactive tenants with cold storage. For a SaaS, this is what other databases make you assemble from payload filters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generative search:&lt;/strong&gt; RAG inside the database — a generative module builds the answer right next to the retrieved objects&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-references between objects:&lt;/strong&gt; a mini-graph inside your vector DB (Neo4j ambitions excluded)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Above-average operations:&lt;/strong&gt; built-in backups to S3/GCS/Azure/disk, Prometheus metrics, OIDC auth. The hatchback with its manual snapshots is jealous&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where it stalls:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Factory options need a service bay:&lt;/strong&gt; self-hosted vectorization modules are separate containers — each eats RAM and needs babysitting. API modules put an external-provider dependency inside your database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No transactions "like in Postgres":&lt;/strong&gt; batches are not ACID operations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema changes:&lt;/strong&gt; some apply on the fly, but switching the vectorizer or a property type = re-import. Design the schema before loading&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distributed mode exists, but the tuning is plentiful:&lt;/strong&gt; ef, efConstruction, quantization (scalar/product/binary) trading recall&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What breaks if you skip the manual:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;AutoSchema: by default the schema is inferred from the first objects — "the factory assembles the trim based on your first order." Wrong types in the first batch and you live with them or re-import. In production, define the schema explicitly&lt;/li&gt;
&lt;li&gt;Changing the vectorizer module — or a model deprecation — means a full re-embedding. That's an operation, not a flag&lt;/li&gt;
&lt;li&gt;Multi-tenancy is enabled at collection creation. Once on, queries must pass a tenant: an inactive one returns an activation error — not a search across everything. A feature, but a first-time surprise&lt;/li&gt;
&lt;li&gt;Python clients v3 and v4 are incompatible: half the tutorials online use v3 syntax that won't run on v4 (even the terms changed: classes → collections). Install v4 (&lt;code&gt;pip install -U weaviate-client&lt;/code&gt;) and keep the migration guide handy&lt;/li&gt;
&lt;li&gt;Hybrid alpha: 0 = pure vector, 1 = pure BM25 (keyword). Don't copy the default from tutorials — run evals on your own queries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cost of ownership:&lt;/strong&gt; free under BSD-3-Clause. Weaviate Cloud (serverless/enterprise) — managed, if you'd rather outsource the service bay. Embedded mode for local tests and CI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mechanics &amp;amp; parts:&lt;/strong&gt; Weaviate B.V. (Amsterdam) behind it; excellent docs, an active community. "Weaviate engineers" are as rare on the market as "Qdrant engineers" — but a backend developer can drive this one.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Take it if:&lt;/strong&gt; a SaaS with many tenants, you want a BM25+vector hybrid without rolling your own sparse embedder, or you want vectorization and generation bundled in one database&lt;/p&gt;

&lt;p&gt;❌ &lt;strong&gt;Pass if:&lt;/strong&gt; your logic lives in SQL (the station wagon), you're counting billions with GPUs (the train), or your ops budget is minimal — a fully-loaded crossover needs a service bay&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test drive:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# weaviate-client v4: pip install -U weaviate-client
# embedded mode downloads binaries once; in production — Docker or Weaviate Cloud
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;weaviate&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;weaviate.classes.config&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DataType&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;weaviate.classes.query&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Filter&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;weaviate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect_to_embedded&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Document&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;vectorizer_config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Vectorizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;none&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;  &lt;span class="c1"&gt;# we bring vectors ourselves (hatchback-style)
&lt;/span&gt;    &lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DataType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;year&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DataType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Quarterly report 2024&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;year&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Quarterly report 2024&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# embed() — your embedding function
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# hybrid: BM25F + vectors in one query
# alpha = balance: 0 — vector, 1 — BM25; tune on evals
&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hybrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;annual financial summary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;annual financial summary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;by_property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;year&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A crossover wins neither with the hatchback's speed nor the scooter's simplicity — it wins on equipment: a whole RAG factory inside one database. You don't pay for the steering wheel; you pay for the service bay that keeps the options running.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>database</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
