<?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: Sergei Cherkasov</title>
    <description>The latest articles on DEV Community by Sergei Cherkasov (@iamcherkasov).</description>
    <link>https://dev.to/iamcherkasov</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%2F4009957%2Ffa6b7150-26b1-412f-9d9c-2817e4af8547.png</url>
      <title>DEV Community: Sergei Cherkasov</title>
      <link>https://dev.to/iamcherkasov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/iamcherkasov"/>
    <language>en</language>
    <item>
      <title>Axelix goes GA. A journey of a thousand miles begins with a single step</title>
      <dc:creator>Sergei Cherkasov</dc:creator>
      <pubDate>Tue, 11 Aug 2026 12:39:57 +0000</pubDate>
      <link>https://dev.to/axelix-labs/axelix-goes-ga-a-journey-of-a-thousand-miles-begins-with-a-single-step-5hfj</link>
      <guid>https://dev.to/axelix-labs/axelix-goes-ga-a-journey-of-a-thousand-miles-begins-with-a-single-step-5hfj</guid>
      <description>&lt;p&gt;On behalf of the core Axelix team, and everybody who has contributed to the community, I want to declare: we finally did it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Axelix, finally, &lt;a href="https://github.com/axelixlabs/axelix/releases/tag/v1.0.0" rel="noopener noreferrer"&gt;goes GA (Generally Available)!&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For those who do not know - Axelix is a product with an Open Source core, that allows you to discover the common problems, pitfalls and inefficiencies in Java applications at large scale.&lt;/p&gt;

&lt;p&gt;We're available &lt;a href="https://github.com/axelixlabs/axelix" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt; (btw - give us a star!).&lt;/p&gt;

&lt;p&gt;In this post, I want to share the story and the motivation behind the product overall. I hope you find it interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Story. Big "Why" Behind Axelix
&lt;/h2&gt;

&lt;p&gt;Java is quite an interesting language and ecosystem in general. I think a lot of people will not argue that it is quite old, and it was one of the first so-called "Object-Oriented" languages, that actually gained massive adoption. It both was, and it still is the backbone of modern enterprise.&lt;/p&gt;

&lt;p&gt;For anyone who claims that Java is dead - I recommend checking the &lt;a href="https://devecosystem-2025.jetbrains.com/" rel="noopener noreferrer"&gt;JetBrains State of Developer Ecosystem survey&lt;/a&gt; or even the &lt;a href="https://survey.stackoverflow.co/2025/technology" rel="noopener noreferrer"&gt;Stack Overflow survey for 2025&lt;/a&gt; (and Stack Overflow has, sadly, become a part of history). It is clear that Java as a language and the "ecosystem" around it (including Kotlin) is still relatively popular, and it remains true.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ecosystems around Languages
&lt;/h2&gt;

&lt;p&gt;The experienced developer knows that today's ecosystems that evolve around languages are typically very diverse. For example, let's talk about JavaScript. If we decide to run JavaScript on the server, then we're probably going to work with a database of some sort. Therefore, we're also going to need a framework, a library to work with the database, e.g. an ORM (I know that we may work without it but let's leave that aside).&lt;/p&gt;

&lt;p&gt;And in JavaScript, we have quite a lot of options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prisma&lt;/li&gt;
&lt;li&gt;TypeORM&lt;/li&gt;
&lt;li&gt;DrizzleORM&lt;/li&gt;
&lt;li&gt;Kysely and so on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can pretty safely state that &lt;a href="https://www.prisma.io/blog/how-prisma-orm-became-the-most-downloaded-orm-for-node-js" rel="noopener noreferrer"&gt;Prisma ORM is &lt;em&gt;probably&lt;/em&gt; the most used ORM on JavaScript&lt;/a&gt;. But notice that it is far from being the definitive JavaScript ORM.&lt;/p&gt;

&lt;p&gt;It is not like Prisma is the default choice and is by far the most popular ORM - it is just not the case. I want to emphasize - even by Prisma's own assessment of its popularity, it is not far ahead of its competitors. They are all very close. &lt;strong&gt;So, the way people work with the database in JavaScript may differ heavily from team-to-team.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The defining trait of Java Ecosystem
&lt;/h2&gt;

&lt;p&gt;In Java, if we're going to take the same problem (working with the database), then we will quickly realize, that the industry is heavily concentrated.&lt;/p&gt;

&lt;p&gt;We generally can state that Spring Data JPA as a solution around Hibernate occupies above 50% of all the data access solutions, let alone ORMs. I remember some time ago Josh Long, the much-loved Developer Advocate for Spring, posted a survey on X (formerly Twitter) about the data access technologies usage:&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1528655715933597697-592" src="https://platform.twitter.com/embed/Tweet.html?id=1528655715933597697"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1528655715933597697-592');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1528655715933597697&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;We must of course acknowledge that there are fast-growing alternative solutions to JPA that are explored, such as jOOQ, but Spring Data JPA dominates the field. So, the way we work with the database in Java (at least on the server side) is relatively well-defined and revolves around Spring Data JPA.&lt;/p&gt;

&lt;p&gt;But there is more to it, it is not just the ORMs or data-access libraries. It's something bigger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spring Framework as the Phenomenon
&lt;/h2&gt;

&lt;p&gt;We cannot talk about Java ecosystem and not talk about Spring Framework. If we're going to look in the &lt;a href="https://devecosystem-2025.jetbrains.com/" rel="noopener noreferrer"&gt;aforementioned Developer Ecosystem State report from JetBrains&lt;/a&gt;, we're going to get the following insights. I have assembled them carefully for you - share them freely as you want. Also note that the respondents had multiple choices:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Framework&lt;/th&gt;
&lt;th&gt;Users&lt;/th&gt;
&lt;th&gt;Share of framework users&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Spring Framework&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3,042&lt;/td&gt;
&lt;td&gt;87.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ktor&lt;/td&gt;
&lt;td&gt;362&lt;/td&gt;
&lt;td&gt;10.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quarkus&lt;/td&gt;
&lt;td&gt;287&lt;/td&gt;
&lt;td&gt;8.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Other&lt;/td&gt;
&lt;td&gt;210&lt;/td&gt;
&lt;td&gt;6.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vaadin&lt;/td&gt;
&lt;td&gt;104&lt;/td&gt;
&lt;td&gt;3.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Micronaut&lt;/td&gt;
&lt;td&gt;95&lt;/td&gt;
&lt;td&gt;2.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grails&lt;/td&gt;
&lt;td&gt;61&lt;/td&gt;
&lt;td&gt;1.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Helidon&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;1.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is (at least AFAIK) one of the most recent and most trusted data we have. Many thanks to JetBrains for publishing it.&lt;/p&gt;

&lt;p&gt;So, yes, although other ecosystems are also growing, e.g. Quarkus along with its Quarkiverse, but they still have a very, &lt;em&gt;very&lt;/em&gt; long way to catch up with Spring Framework. So at the end of 2025 (I wager that even today) we can safely state:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The most common Java app, which &lt;strong&gt;by far&lt;/strong&gt; represents more than half of Java server side deployments is a Spring Framework app.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Again, it is just data, you can download it and check yourself.&lt;/p&gt;

&lt;p&gt;But even if we consider different ecosystems: Spring Framework, Quarkus or Micronaut etc - we're going to quickly realize that these are not just "libraries" or "frameworks" in a traditional sense. &lt;strong&gt;They are themselves large frameworks that build an entire sub-ecosystem around them&lt;/strong&gt;. For example, are you working with Data? Well, you have&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spring Data in Spring&lt;/li&gt;
&lt;li&gt;Jakarta Data in Quarkus&lt;/li&gt;
&lt;li&gt;Micronaut Data in Micronaut&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Oh, you want to send an HTTP request? The most common thing on the backend! Well, we have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spring Cloud Exchange/OpenFeign in Spring&lt;/li&gt;
&lt;li&gt;RestClients in Quarkus&lt;/li&gt;
&lt;li&gt;Declarative HTTP Clients in Micronaut&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice that every framework has its own solution.&lt;/p&gt;

&lt;p&gt;In other ecosystems, e.g. in JavaScript on the server side, we may use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ExpressJS for web server&lt;/li&gt;
&lt;li&gt;Axios for sending requests&lt;/li&gt;
&lt;li&gt;Prisma as an ORM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And these are all just individual choices made by the team. We may use Axios or we may use Got. We may use Prisma or we may use TypeORM. Some solutions are more popular than others, but they are all independent - they exist outside of a single umbrella, like Spring Framework, for example.&lt;/p&gt;

&lt;p&gt;To put it simply:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In the Java world, if we have any kind of problem, most likely, we &lt;strong&gt;do NOT&lt;/strong&gt; need to seek the solution on GitHub, find some fork that does what you want (which is a common JavaScript practice btw). There is almost 100% chance that Spring Framework has a solution for us already.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why am I telling you about this?
&lt;/h2&gt;

&lt;p&gt;Because it is important to understand, that since Java Ecosystem is so solidified around the Spring Framework technological stack, &lt;strong&gt;it often suffers the same problems in production&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What are these problems? Well, there are a lot of them. And we know all of them with our very own butts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Our connection pool leaks&lt;/li&gt;
&lt;li&gt;Our API endpoints are performing slow (maybe the problem is in the database?)&lt;/li&gt;
&lt;li&gt;We accidentally expose &lt;code&gt;/actuator/env&lt;/code&gt; in production and leak secrets&lt;/li&gt;
&lt;li&gt;Our applications consume too much memory and start very slowly...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And so on. And the industry, throughout decades of Java experience in production, has developed a set of recommendations, best-practices, "solutions", guidelines. And these guidelines exist on different levels of the application, e.g:&lt;/p&gt;

&lt;p&gt;Guidelines for Hibernate/ORM&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://thorben-janssen.com/entity-mappings-introduction-jpa-fetchtypes/" rel="noopener noreferrer"&gt;Do not use &lt;code&gt;EAGER&lt;/code&gt; fetch, ever!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thorben-janssen.com/avoid-cascadetype-delete-many-assocations/" rel="noopener noreferrer"&gt;Do not use &lt;code&gt;CascadeType.ALL&lt;/code&gt; or &lt;code&gt;CascadeType.REMOVE&lt;/code&gt; for "XToMany"&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thorben-janssen.com/best-practices-for-many-to-many-associations-with-hibernate-and-jpa/" rel="noopener noreferrer"&gt;Do not use &lt;code&gt;List&lt;/code&gt; for &lt;code&gt;@ManyToMany&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thorben-janssen.com/hibernate-tips-unidirectional-one-to-many-association-without-junction-table/" rel="noopener noreferrer"&gt;Do not use one-directional &lt;code&gt;@OneToMany&lt;/code&gt;!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and so on. Guidelines for Spring performance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.reddit.com/r/java/comments/1hcgyc4/the_opensessioninview_pattern_of_spring_boot_a/" rel="noopener noreferrer"&gt;Disable OSIV&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Use &lt;a href="https://rieckpil.de/achieve-faster-build-times-with-the-spring-test-profiler/" rel="noopener noreferrer"&gt;Spring Test Profiler!&lt;/a&gt; (many thanks to Philip Riecks for such a tool)&lt;/li&gt;
&lt;li&gt;Use &lt;a href="https://github.com/seregamorph/spring-test-smart-context" rel="noopener noreferrer"&gt;Spring smart context caching!&lt;/a&gt; (Sergey Chernov, a highly experienced Engineer and a buddy of mine, developed the tool)!&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vladmihalcea.com/log-sql-spring-boot/" rel="noopener noreferrer"&gt;Do not set &lt;code&gt;spring.jpa.show-sql=true&lt;/code&gt; or other dangerous properties in production!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and so on. Guidelines for even our beloved Java!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For Christ’s sake, &lt;a href="https://openjdk.org/projects/leyden/" rel="noopener noreferrer"&gt;use Project Leyden&lt;/a&gt;!&lt;/li&gt;
&lt;li&gt;Use Compact Object Headers (&lt;a href="https://openjdk.org/projects/lilliput/" rel="noopener noreferrer"&gt;Project Lilliput&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Configure the &lt;a href="https://blog.gceasy.io/java-garbage-collection-best-practices/" rel="noopener noreferrer"&gt;logging of GC&lt;/a&gt; in production!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And so on and on and on. Because we have that solidified ecosystem we generally know &lt;em&gt;"what is right and what is wrong"&lt;/em&gt;. And for the long time there was no such tool.&lt;/p&gt;

&lt;p&gt;But we thought that this is wrong. And we probably should have one. And this tool is called &lt;strong&gt;Axelix&lt;/strong&gt; (&lt;a href="https://github.com/axelixlabs/axelix" rel="noopener noreferrer"&gt;The source code&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;In 1.0.0, we do not support the detection of all the issues that I outlined above (most, but not all). And the above is just an example. There is a lot (really, &lt;em&gt;a lot&lt;/em&gt;) of knowledge that we as a Java community have accumulated throughout these years that we're going to put inside Axelix, so it can serve you well.&lt;/p&gt;

&lt;p&gt;I hope, at that point, the motivation behind the tool is clear, and now, I want to answer a couple of questions that we already have received many times over during Milestones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Q: Is it free?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Yes, it is&lt;/strong&gt;. We're going to follow the Open Core model. This is the Open Source software development model that underpins projects that we all love and care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Grafana&lt;/li&gt;
&lt;li&gt;Keycloak&lt;/li&gt;
&lt;li&gt;Vaadin&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And so on. It is &lt;a href="https://opencoreventures.com/insights/someone-has-to-pay-for-the-domino/" rel="noopener noreferrer"&gt;generally considered to be the most reliable model for the OSS development&lt;/a&gt;, and that is why industry leaders choose, and that is why we choose it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Q: Is the project ready for use and deploy?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Yes&lt;/strong&gt;, it is ready for use free of charge. We have processed the feedback from literally tens of teams that have installed Axelix milestone releases and shared their insights with us.&lt;/p&gt;

&lt;h3&gt;
  
  
  Q: Is it safe to deploy in production?
&lt;/h3&gt;

&lt;p&gt;It is designed for production, so the answer is yes. But since this is the first 1.0.0 GA release, the general recommendation is to first install it on development environments, and then for production.&lt;/p&gt;

&lt;p&gt;That said, we do have teams that deployed Axelix in production already to get insights about their &lt;code&gt;@Transactional&lt;/code&gt; methods behavior and properties resolution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bottom Line:&lt;/strong&gt; The core stays Open Source always, and it always will be free of charge, and it already provides value for the teams.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Are We Headed?
&lt;/h2&gt;

&lt;p&gt;The team behind Axelix has many years of experience writing and optimizing Spring Boot applications. We have a lot of work to do ahead of us, and we still have a lot of things to incorporate into Axelix.&lt;/p&gt;

&lt;p&gt;For those who want to know how the project is installed &amp;amp; configured, please, visit our &lt;a href="https://axelix.io" rel="noopener noreferrer"&gt;website&lt;/a&gt; and also our &lt;a href="https://axelix.io/docs" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;. In case of any questions or difficulties, you can always reach out to the team at: &lt;a href="mailto:hello@axelix.io"&gt;hello@axelix.io&lt;/a&gt; We, the maintainers, are happy to answer your questions and assist you during the installation.&lt;/p&gt;

&lt;p&gt;We're also going to stay committed to Open Source, and committed to our values, which are making sure that your Java applications are as secure, performant and efficient as they can be.&lt;/p&gt;

&lt;p&gt;Thank you all. Mikhail.&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>java</category>
      <category>spring</category>
    </item>
    <item>
      <title>Taming Hibernate in Tests</title>
      <dc:creator>Sergei Cherkasov</dc:creator>
      <pubDate>Sat, 08 Aug 2026 07:51:22 +0000</pubDate>
      <link>https://dev.to/axelix-labs/taming-hibernate-in-tests-hl6</link>
      <guid>https://dev.to/axelix-labs/taming-hibernate-in-tests-hl6</guid>
      <description>&lt;p&gt;Hi everyone! This is Mikhail Polivakha, the technical lead of the &lt;a href="https://github.com/axelixlabs/axelix" rel="noopener noreferrer"&gt;Open Source Axelix project&lt;/a&gt; (a project dedicated to helping you identify common problems in Java server-side applications, including while working with Hibernate).&lt;/p&gt;

&lt;p&gt;In this article, I want to address the question that I think is pretty common amongst the advanced Hibernate users:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Is there a verify Hibernate behavior in tests (like Junit for example)?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here it's really important not to hand you a "fish", but to teach you how to fish - that is, to give you a framework of what's worth keeping in mind when working with Hibernate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Controlling Hibernate during tests. Overview.
&lt;/h2&gt;

&lt;p&gt;If I asked you: &lt;strong&gt;guys, which problems in Hibernate annoy you the most?&lt;/strong&gt; You'd mention things like the sneaky N + 1, In Memory Pagination (when explicitly specifying &lt;code&gt;setMaxResult&lt;/code&gt; or a &lt;code&gt;Pageable&lt;/code&gt;), a Cartesian Product loaded into the application's memory, or, say, some baffling swarm of &lt;code&gt;SELECT&lt;/code&gt;s followed by &lt;code&gt;UPDATE&lt;/code&gt;s/&lt;code&gt;DELETE&lt;/code&gt;s under the hood, and so on.&lt;/p&gt;

&lt;p&gt;Everything I listed above, and really all "problems" with Hibernate, can be split into 2 categories. Let's break them down.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Problems at the ORM level
&lt;/h2&gt;

&lt;p&gt;There are situations that both we (the end users) and Hibernate identify as problems. For example, up until Hibernate 7.4, the query below would perform pagination entirely in memory. This is fairly well known:&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="nd"&gt;@Query&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;"SELECT o FROM Owner o JOIN FETCH o.pets"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;countQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"SELECT COUNT(o) FROM Owner o"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;Page&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Owner&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findAllWithPets&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Pageable&lt;/span&gt; &lt;span class="n"&gt;pageable&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In version 7.4 Hibernate learned to do pagination within a sub-select. But nevertheless, if you, for example, work on Spring Boot 3, you have Hibernate 6, and in the case of queries like the one above there will be a warning in stdout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HHH90003004: firstResult/maxResults specified with collection fetch; applying in memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it's there because Hibernate itself understands that its actions &lt;em&gt;may&lt;/em&gt; blow up your heap. There's even a property that we at Axelix recommend setting for new microservices that don't use 7.4 yet (and there are still many of those):&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.query.fail_on_pagination_over_collection_fetch&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;p&gt;If you set it, Hibernate will simply throw an exception where it would previously do In Memory Pagination.&lt;/p&gt;

&lt;p&gt;In short, what I want to say is that the problem above is understood by Hibernate as a real problem. It recognizes and admits it. That's one layer of problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But oddly enough, these are quite hard to track&lt;/strong&gt;. Hibernate rarely provides any means to hook into it (although there are some nuances, read below). For instance, at Axelix, to detect In Memory Pagination (if it happens), we simply &lt;a href="https://github.com/axelixlabs/axelix/blob/master/sbs/axelix-spring-boot-2-starter/src/main/java/com/axelixlabs/axelix/sbs/spring/core/persistence/hibernate/LogbackInMemoryPaginationAppender.java" rel="noopener noreferrer"&gt;literally catch an &lt;code&gt;ILoggingEvent&lt;/code&gt; and inspect the code within it&lt;/a&gt;. Because there are no other acceptable ways (at least none that we're aware of)!&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Second. Problems at the application level
&lt;/h2&gt;

&lt;p&gt;There's a conceptually different layer of problems. N + 1, for example, belongs to it.&lt;/p&gt;

&lt;p&gt;Let's think for a second: &lt;em&gt;what is N + 1?&lt;/em&gt;. Try right now, without looking anywhere, to clearly formulate the definition in your head. Done? Here's how I'd formulate it (informally):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It's a situation in which you iterate over a collection loaded by Hibernate, sequentially accessing some lazy field within each individual entity of the collection, thereby triggering "N" additional SELECT queries.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is, if you think about it a little, it becomes obvious that for Hibernate this is just Lazy Loading. The notion of N + 1 as a problem exists at the application level, not at the ORM level. &lt;strong&gt;That is, the problem is not lazy loading itself, but the semantics of lazy loading&lt;/strong&gt; (what does &lt;em&gt;this particular&lt;/em&gt; lazy loading mean? Is it just lazy loading, or lazy loading within an N + 1?).&lt;/p&gt;

&lt;p&gt;In such a situation it's obviously pointless to rely on Hibernate, since the N + 1 phenomenon happens at the application level, and the ORM is completely unaware of it — it's not its area of responsibility. You can't get by without fairly strong tooling around Hibernate. &lt;strong&gt;Can it be written, and if so, how?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If we're talking specifically about N + 1, then the answer is generally - yes, you can, and we did it at Axelix in Open Source. Let me tell you how exactly we did it. &lt;a href="https://github.com/axelixlabs/axelix" rel="noopener noreferrer"&gt;The source code is open, take a look if you're interested&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The General Approach to N + 1 Detection
&lt;/h3&gt;

&lt;p&gt;Hibernate doesn't know what N + 1 is, but it does know what a Lazy Loading association is, and that moment can already be caught. For example, here's how we do it in &lt;a href="https://github.com/axelixlabs/axelix/blob/master/sbs/axelix-spring-boot-2-starter/src/main/java/com/axelixlabs/axelix/sbs/spring/core/persistence/hibernate/NPlusOneCollectionLoadListener.java" rel="noopener noreferrer"&gt;&lt;code&gt;NPlusOneCollectionLoadListener&lt;/code&gt;&lt;/a&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;@Override&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;onInitializeCollection&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InitializeCollectionEvent&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;PersistentCollection&lt;/span&gt; &lt;span class="n"&gt;persistentCollection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCollection&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="nc"&gt;EventSource&lt;/span&gt; &lt;span class="n"&gt;eventSource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSession&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="nc"&gt;CollectionPersister&lt;/span&gt; &lt;span class="n"&gt;collectionPersister&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;eventSource&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPersistenceContextInternal&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCollectionEntry&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;persistentCollection&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLoadedPersister&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

            &lt;span class="nc"&gt;LazyLoadingTarget&lt;/span&gt; &lt;span class="n"&gt;lazyLoadingTarget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parseLazyLoadingTarget&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;collectionPersister&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRole&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;lazyLoadingTarget&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="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;transactionAccessor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;recordLazyLoading&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lazyLoadingTarget&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="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ignored&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;span class="c1"&gt;// The general "role" format is expected to look like this: com.example.Order.items&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nd"&gt;@Nullable&lt;/span&gt; &lt;span class="nc"&gt;LazyLoadingTarget&lt;/span&gt; &lt;span class="nf"&gt;parseLazyLoadingTarget&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;role&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&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;separatorIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lastIndexOf&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="nc"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ownerEntityClass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;substring&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;separatorIndex&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;propertyName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;substring&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;separatorIndex&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;LazyLoadingTarget&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ownerEntityClass&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;propertyName&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ClassNotFoundException&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;IndexOutOfBoundsException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;warn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"Unexpected propertyPath format '{}'. Axelix cannot recognize that, so lazy loading and potential N + 1 is not going to be tracked for this property"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// it means that the role format is not the one that we expect&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;Here, through a series of manipulations, we can figure out which association was loaded lazily and on which collection. What do we then do with this information?&lt;/p&gt;

&lt;p&gt;Axelix, for example, currently makes the following decision: &lt;strong&gt;If we noticed that within a transaction there were several lazy loadings of the same association, then we consider this to be an N + 1.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Is that always correct? Well, this is debatable, because, for instance, in this code example, if we assume that &lt;code&gt;Order.items&lt;/code&gt; were loaded lazily — we, &lt;em&gt;seemingly&lt;/em&gt;, don't have an N + 1:&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="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;compareOrders&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;previousId&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;currentId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orderRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;previousId&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;orElseThrow&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orderRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentId&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;orElseThrow&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Two lazy accesses to Order.items, but there's no collection of orders here -&lt;/span&gt;
    &lt;span class="c1"&gt;// we're just comparing two specific versions of the same order&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;previous&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getItems&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="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getItems&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="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;IllegalStateException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The order contents have changed"&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;Or do we? And what if I change this example to something like this:&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="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;compareOrders&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;previousId&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;currentId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orderRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findAllById&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;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;previousId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentId&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&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="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Exactly the same comparison as above, but now within an iteration&lt;/span&gt;
    &lt;span class="c1"&gt;// over a loaded collection of orders: at each step there's again a lazy&lt;/span&gt;
    &lt;span class="c1"&gt;// access to Order.items on both previous and current&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="o"&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;previous&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getItems&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="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getItems&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="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;IllegalStateException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The order contents have changed"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&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;Does it look like N + 1 now? It does.&lt;/p&gt;

&lt;p&gt;So, in short, what I want to say is that here you need to clearly fix the definition of N + 1 at the application level. We at Axelix deliberately decided that this case is nonetheless worth treating as an N + 1, so, in the UI, it will be reported to you:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frp5l1stk8eqmyd15dyhv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frp5l1stk8eqmyd15dyhv.png" alt="Axelix reporting an N + 1 in the UI" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S: Among other things, there's a small nuance here: N + 1, strictly speaking, can also happen &lt;strong&gt;without an open transaction&lt;/strong&gt; when OSIV is enabled. That's a separate case which we're not considering for now, since it would complicate the picture.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Micro-Conclusion
&lt;/h3&gt;

&lt;p&gt;I'd like to draw a small micro-conclusion here. In general, detecting some complex phenomena that occur at the application level while working with Hibernate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;N + 1&lt;/li&gt;
&lt;li&gt;Blocking calls inside transactions, and so on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This can be done. It just requires you to write a fairly large and non-trivial amount of tooling. Using N + 1 as an example, I hope you've roughly understood what such things will look like.&lt;/p&gt;

&lt;h2&gt;
  
  
  General Hibernate Problems
&lt;/h2&gt;

&lt;p&gt;As a separate section, I'd like to address general Hibernate problems. Quite often people have some conditional hot-path for saving or updating data. And devs simply want to make sure that&lt;br&gt;
&lt;strong&gt;Hibernate doesn't "sabotage" the entire process undercover&lt;/strong&gt;. Sounds familiar, right?&lt;/p&gt;

&lt;p&gt;Here you need to take a step back and ask: &lt;strong&gt;and what do we mean by "sabotage"?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Having talked with people, we'll discover that most often people want roughly the following: to have confidence that a person wrote &lt;code&gt;repository.save()&lt;/code&gt;, and that it will execute specifically an &lt;code&gt;INSERT&lt;/code&gt; into the table and nothing more.&lt;/p&gt;

&lt;p&gt;For example, having this piece of code:&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;@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;OwnerService&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;OwnerRepository&lt;/span&gt; &lt;span class="n"&gt;ownerRepository&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;OwnerService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OwnerRepository&lt;/span&gt; &lt;span class="n"&gt;ownerRepository&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;ownerRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ownerRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Transactional&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Owner&lt;/span&gt; &lt;span class="nf"&gt;registerOwner&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;firstName&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;lastName&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;owner&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;Owner&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lastName&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;ownerRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;owner&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;The person wants to be sure that there won't be any hidden &lt;code&gt;SELECT&lt;/code&gt;s and so on there. I think some people recognized themselves. &lt;strong&gt;Can this be done?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The answer: &lt;strong&gt;generally yes, it can&lt;/strong&gt;. Hibernate has a Statistics API, which on the one hand isn't part of the JPA standard, and on the other hand can give us some details about query execution within a session. For example, for the code above you can write a test like this:&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;// Requires hibernate.generate_statistics=true&lt;/span&gt;
&lt;span class="nd"&gt;@SpringBootTest&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OwnerServiceTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;OwnerService&lt;/span&gt; &lt;span class="n"&gt;ownerService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;EntityManagerFactory&lt;/span&gt; &lt;span class="n"&gt;entityManagerFactory&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="nd"&gt;@Transactional&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;registerOwner_issuesExactlyOneInsert&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// given&lt;/span&gt;
        &lt;span class="nc"&gt;Statistics&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entityManagerFactory&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;unwrap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SessionFactory&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;getStatistics&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;statistics&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;// when&lt;/span&gt;
        &lt;span class="n"&gt;ownerService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerOwner&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"John"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Doe"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// then&lt;/span&gt;
        &lt;span class="n"&gt;assertThat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getEntityInsertCount&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;isEqualTo&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;assertThat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getEntityUpdateCount&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;isZero&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;assertThat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getEntityDeleteCount&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;isZero&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;This API lets you verify that only one &lt;code&gt;INSERT&lt;/code&gt; was executed within the method. It works based on the fact that in the test I opened a transaction (Spring Boot Test of course recognizes this and opens a transaction that will have to roll back at the end),&lt;br&gt;
thereby opening a Hibernate &lt;code&gt;Session&lt;/code&gt; - this is the default behavior, so I think there are no surprises here.&lt;/p&gt;

&lt;p&gt;Since the Statistics API collects statistics at the &lt;code&gt;SessionFactory&lt;/code&gt; level, not at the level of individual &lt;code&gt;Session&lt;/code&gt;s, in each test we have to access the &lt;code&gt;SessionFactory&lt;/code&gt; in order to first clear all its statistics.&lt;br&gt;
And only then can we make the checks we're interested in (Strictly speaking, some telemetry is also collected at the &lt;code&gt;Session&lt;/code&gt; level, but it's rather scarce and in practice can help you little).&lt;/p&gt;

&lt;p&gt;Overall, the telemetry collected (the number of &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt; statements, and so on) will be enough for you to make some basic checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;In practice, the answer to the question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Is there a verify Hibernate behavior in tests (like Junit for example)?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Strongly depends on what exactly you want to detect. Some tooling can be written — for example, Axelix can detect both In Memory Pagination, and N + 1, and a number of other problems. In some situations tests will help you a lot.&lt;br&gt;
In general, first of all it's better to ask yourself — &lt;strong&gt;what problem exactly are we trying to solve here&lt;/strong&gt;. And only then build a solution: is it possible (considering what I wrote above) to write an effective test,&lt;br&gt;
is it possible to delegate this to external tooling, or maybe (the ideal case) the problem doesn't need to be solved in the first place.&lt;/p&gt;

&lt;p&gt;Best of luck to everyone!&lt;/p&gt;

</description>
      <category>database</category>
      <category>testing</category>
      <category>springboot</category>
      <category>hibernate</category>
    </item>
  </channel>
</rss>
