<?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%2Fa05fc940-fd2b-4de4-98ae-dcf2354affe1.jpg</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>One Branch to Rule Them All</title>
      <dc:creator>Sergei Cherkasov</dc:creator>
      <pubDate>Wed, 19 Aug 2026 16:09:22 +0000</pubDate>
      <link>https://dev.to/axelix-labs/one-branch-to-rule-them-all-4j8j</link>
      <guid>https://dev.to/axelix-labs/one-branch-to-rule-them-all-4j8j</guid>
      <description>&lt;p&gt;Hey everybody. This is Mikhail, the technical lead of the &lt;a href="https://github.com/axelixlabs/axelix" rel="noopener noreferrer"&gt;Axelix project&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For quite a long time I wanted to write a small article outlining our git branching model, in hope it may be useful to other people.&lt;/p&gt;

&lt;p&gt;There are quite a lot of git branching strategies in the world, e.g.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow" rel="noopener noreferrer"&gt;Gitflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/get-started/using-github/github-flow" rel="noopener noreferrer"&gt;GitHub-flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://trunkbaseddevelopment.com/" rel="noopener noreferrer"&gt;Trunk-Based-Development (TBD)&lt;/a&gt; and so on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Throughout my experience I can tell, that teams usually do not adopt the given strategy in the exact form/shape, rather, they either invent something completely&lt;br&gt;
new themselves, or they just take, for instance, aforementioned GitFlow and modify it for their needs.&lt;/p&gt;

&lt;p&gt;So, throughout already a year of development of &lt;a href="https://github.com/axelixlabs/axelix" rel="noopener noreferrer"&gt;Axelix&lt;/a&gt;, we have adopted a slightly modified Trunk Based Development model, and I think it served us well.&lt;br&gt;
I hope you will find it also useful. This model admittedly has certain downsides, but in general, we're happy with it.&lt;/p&gt;

&lt;p&gt;Before we start, you should be mindful of the requirements we tried to address. At the end of the day everybody must judge for themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Small Prerequisite. Axelix Versioning.
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The two most difficult problems in software are naming and versioning.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is not the exact quote, but it is a rough sense of the joke that &lt;a href="https://thomaswue.dev/" rel="noopener noreferrer"&gt;Thomas Wuerthinger&lt;/a&gt; (GraalVM Project Lead) said on BoF on Devoxx Belgium 2025 (explaining the &lt;a href="https://blogs.oracle.com/java/detaching-graalvm-from-the-java-ecosystem-train" rel="noopener noreferrer"&gt;&lt;em&gt;"Detaching GraalVM from the Java Ecosystem Train"&lt;/em&gt;&lt;/a&gt; article).&lt;/p&gt;

&lt;p&gt;To later understand on why we're doing certain things we're doing, it's going to be useful to realize the versioning scheme we're using.&lt;/p&gt;

&lt;p&gt;At Axelix, we use &lt;a href="https://semver.org/" rel="noopener noreferrer"&gt;semantic versioning&lt;/a&gt; of our components. I'd even say that SemVer is a de-facto standard for versioning the modern world. There are, of course, simple versioning schemes, like for Windows OS, when you&lt;br&gt;
have Windows 7, 8, 10 or 11. There also is software that is released under the different variations &lt;a href="https://calver.org/" rel="noopener noreferrer"&gt;CalVer&lt;/a&gt;. The JetBrains IDEs will be a good example of that,&lt;br&gt;
e.g. 2026.1 or 2026.2.&lt;/p&gt;

&lt;p&gt;Still, the majority of projects do SemVer, and Axelix does it as well. But when we're talking about the modern software - it is often composed of multiple components.&lt;br&gt;
For example, Axelix has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build plugins for Maven/Gradle&lt;/li&gt;
&lt;li&gt;Spring Boot starters&lt;/li&gt;
&lt;li&gt;Master as the separate bootable JAR&lt;/li&gt;
&lt;li&gt;Docker image of Master&lt;/li&gt;
&lt;li&gt;Helm Chart of Master&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, the thing is that each of the points above represent an independent artifact that is generally distributed independently of others, and thus, it has its own coordinates.&lt;/p&gt;

&lt;p&gt;And in our case, we have selected to follow the Lockstep Versioning of the Axelix project. In short, this is not the versioning schema, but a versioning strategy where all&lt;br&gt;
 components of the system share the same version X.Y.Z. It means, that if we release Axelix 1.4.2, all the components above receive the same version - 1.4.2.&lt;/p&gt;

&lt;p&gt;Yes, we know that Lockstep versioning is generally &lt;em&gt;a highly debatable subject for a couple of reasons&lt;/em&gt;. And I would say, that team should generally think twice before adopting it.&lt;br&gt;
I should probably write another article outlining why we choose semver with lockstep, and why it &lt;em&gt;may be&lt;/em&gt; a good idea for &lt;em&gt;some&lt;/em&gt; of you as well. But this is an off-top for this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;So, you're starting a new project, or you're about to revisit the git branching model in your team for various reasons. When you try to implement any technical solution (let's say, you want to choose a git branching model for the project), it is generally a good idea to take a step back and answer the question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What exactly is the problem that we're solving?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Indeed, before explaining anything, we should first realize what are the specific requirements that we want to address with any git branching model. And these requirements&lt;br&gt;
will be different for different kinds of software that has different release cadence. In the case of Axelix, we wanted a branching model that would:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Give us the ability to do patches
&lt;/h3&gt;

&lt;p&gt;We want to be able to deliver patches quickly.&lt;/p&gt;

&lt;p&gt;Moreover, the important thing is that &lt;em&gt;we do not know the amount of patch releases in the given minor in advance&lt;/em&gt;. The general&lt;br&gt;
requirement is to be able to go to the &lt;em&gt;arbitrary minor release&lt;/em&gt;, and apply &lt;em&gt;an arbitrary number of patches&lt;/em&gt; for it (for example, to fix the CVE).&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Avoid the merge hell.
&lt;/h3&gt;

&lt;p&gt;A lot of git branching strategies easily end-up in the merge hell, e.g. GitFlow is infamous for ending up in such conundrums.&lt;/p&gt;

&lt;p&gt;So, with that in mind, let's finally discuss what branching model we actually adopt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Axelix Git Branching Model
&lt;/h2&gt;

&lt;p&gt;At Axelix, we have the monorepo. And in this monorepo we have the main trunk, called 'master' trunk (the thing we borrowed from TBD). Some commits that do&lt;br&gt;
polishing or basic housekeeping happen directly in the master trunk. For features, we create short-lived branches (directly from master) that gets merged&lt;br&gt;
back into the master trunk:&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%2Fnw5ly82eci3ciju7amhx.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%2Fnw5ly82eci3ciju7amhx.png" alt="Short-lived feature branches created from and merged back into the master trunk" width="800" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In some extreme cases, adepts of TBD advocate for no feature branches at all with the risk of them being long-lived, and I understand the rationale behind it.&lt;br&gt;
However, for OSS projects such as Axelix, we cannot allow for that because of external contributors and for various other reasons.&lt;/p&gt;

&lt;p&gt;Nevertheless, &lt;em&gt;feature branches ideally need to die fast&lt;/em&gt;. To facilitate "the death" of feature branches, we impose a couple of rules on the feature&lt;br&gt;
branches, and famously, we introduce a cap of 500-lines PR. In other words, feature branch cannot contribute more than 500 lines of code into the main trunk (there are some&lt;br&gt;
exceptions to that rule, but it does not matter right now). This cap ensures that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The PR is small enough to be carefully reviewed.&lt;/li&gt;
&lt;li&gt;The feature branch will indeed be short-lived, since the smaller PR the faster it will be ready for review.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It is generally well-known that &lt;a href="https://www.cubic.dev/blog/does-pr-size-actually-matter" rel="noopener noreferrer"&gt;for small PRs the overall TTM (time-to-merge) is smaller&lt;/a&gt;,&lt;br&gt;
and the cap ensures feature branches die fast and they do not evolve into the long-lived branches. So here, we're perfectly aligned with the TBD.&lt;/p&gt;

&lt;p&gt;When we want to perform a minor/major release, we create a tag on the master trunk of our monorepo and execute the release pipeline (very simplified):&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%2Fu0m8xxpfoxetj98ggw47.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%2Fu0m8xxpfoxetj98ggw47.png" alt="Tagging a commit on the master trunk to trigger a minor/major release" width="800" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since we have the monorepo with the lockstep versioning, this tagged commit is the one from which all the release artifacts are built. And the value of the&lt;br&gt;
tag is the version that Axelix release gets.&lt;/p&gt;

&lt;p&gt;If we want to do the patch release, we create the branch from the git tag, e.g. a branch named 1.0.x. That is the long lived branch that is going to live by itself.&lt;br&gt;
Once we have created necessary patches in the 1.0.x branch, we create a tag in that long-lived patch branch, call this tag v1.0.1 e.g, and release it then:&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%2Fq88knqups4q67nc7d8js.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%2Fq88knqups4q67nc7d8js.png" alt="A long-lived 1.0.x patch branch created from a tag, with its own patch release tag" width="799" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here the pivot point where we significantly and deliberately depart from TBD. In TBD, it is forbidden to have long-lived branches apart from the main trunk.&lt;br&gt;
But I want to take a step back and explain not what is forbidden, but why TBD advocates against it, and why it is okay in our use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  TBD: Why patch-branches are okay.
&lt;/h2&gt;

&lt;p&gt;TBD is a well-known and proven git branching strategy. It &lt;a href="https://trunkbaseddevelopment.com/game-changers/#google-revealing-their-monorepo-trunk-2016" rel="noopener noreferrer"&gt;is adopted by Google, Facebook and other companies&lt;/a&gt;.&lt;br&gt;
And it does not allow long-lived (apart from the main trunk) branches for a variety of reasons. One of them, and, probably, the most notorious is the merging problem. Merging the long-lived branches is quite challenging and time-consuming,&lt;br&gt;
error-prone, bug-welcome and so on.&lt;/p&gt;

&lt;p&gt;This problem becomes real in scenarios, when we develop certain functionality, &lt;em&gt;that is intended to be later merged with something else, like a main trunk&lt;/em&gt;. But in case&lt;br&gt;
of such patch branches, we do not intend to merge them ever. &lt;em&gt;They are not supposed to be merged&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;According to SemVer, the changes that go into the patch releases are (very broadly):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backwards compatible bugfixes&lt;/li&gt;
&lt;li&gt;Backwards compatible perf. improvements&lt;/li&gt;
&lt;li&gt;Backports of CVE patches&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course, we can discuss all day what exactly backward compatibility means, whether it accounts for "behavioral" backwards compatibility or source-level backwards compatibility and so on.&lt;br&gt;
Let's put that aside.&lt;/p&gt;

&lt;p&gt;The core idea is that &lt;em&gt;patch branches will never have any kind of features developed in them&lt;/em&gt;, that are going to be merged back into master trunk. That just won't happen.&lt;/p&gt;

&lt;p&gt;In our case, for example, the changes that end up in the patch branches are the very specific commits that just got cherry-picked from the main master trunk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges &amp;amp; Problems
&lt;/h2&gt;

&lt;p&gt;Of course, such a model as above has challenges. There are a couple of them. Let me enumerate them.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Minor/Major Dilemma
&lt;/h3&gt;

&lt;p&gt;One of the most important challenges of such architecture is its Minor/Major inflexibility.&lt;/p&gt;

&lt;p&gt;So, what do I mean by that?&lt;/p&gt;

&lt;p&gt;Well, for example, let's say, that you're developing version 1.7 of your software. And now, you're thinking about releasing a new major version, 2.0. If you're&lt;br&gt;
making that decision, you're effectively starting to merge all the changes that you wanted to end up in the new major (e.g. non-backward-compatible ones, more on them&lt;br&gt;
later) into the master trunk. Well, once you've started doing that, pivoting back and saying stuff like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Oh, no, we changed our mind, we just want to release 1.8&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It becomes really hard, in some cases infeasible. That is the inflexibility I'm talking about.&lt;/p&gt;

&lt;p&gt;Still, that problem (in its various levels of severity) of having no simple return follows &lt;em&gt;a lot&lt;/em&gt; of similar git models, TBD included.&lt;br&gt;
So, in the industry, this problem is generally a consequence of the trade-off of not having merge hells.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Long-Lived Breaking Changes Branches
&lt;/h3&gt;

&lt;p&gt;Well, when I said to you, that we have master trunk and patch branches as the only long-lived branches, I have omitted one thing. One &lt;em&gt;very important thing&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;And that is: our git branching model allows for long-lived &lt;em&gt;feature&lt;/em&gt; branches that contain breaking changes. And since such branches are &lt;em&gt;intended&lt;/em&gt; to be&lt;br&gt;
merged, we have to postpone them until the next major (at very least). And if we combine that with the problem above, then we realize - we're in trouble.&lt;/p&gt;

&lt;p&gt;Well, although we are in trouble, &lt;em&gt;the degree&lt;/em&gt; towards which we're actually in trouble is debatable.&lt;/p&gt;

&lt;p&gt;First of all, it significantly depends on the fact of &lt;em&gt;what changes we consider breaking&lt;/em&gt;. And for every&lt;br&gt;
mature system such changes may be different. In our case, we have a whole documentation page that explains our release cadence and overall breaking changes&lt;br&gt;
impression.&lt;/p&gt;

&lt;p&gt;Another thing is that we can &lt;em&gt;try&lt;/em&gt; to mitigate such problem by constraining the amount of breaking changes subject for the next release. It is not always&lt;br&gt;
feasible/predictable, but tackling that problem not from a technical, but from an administrative PoV may also help here.&lt;/p&gt;

&lt;p&gt;So, admittedly, this is also a challenge that we have to live with. We may try to mitigate that, but to some degree we're going to have it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions. Axelix, Lessons Learned.
&lt;/h2&gt;

&lt;p&gt;So, again, I did not want to say that we have invented some astonishing or mind-blowing git model. Internally, we just felt that it may be worth sharing with&lt;br&gt;
the world the git branching model we adopted and the reasons why.&lt;/p&gt;

&lt;p&gt;The goal of the article was more to outline the way we approached the problem with the hope that you'll learn from it. Axelix has the &lt;a href="https://github.com/axelixlabs/axelix" rel="noopener noreferrer"&gt;Open Source core&lt;/a&gt;,&lt;br&gt;
so if you want to learn more - you're welcome.&lt;/p&gt;

&lt;p&gt;Remember that there're always trade-offs, and sometimes it is okay to diverge from certain well-established practices. Still, you always have to&lt;br&gt;
clearly justify such divergences and, what is also important, realize the weaknesses of your solution as well.&lt;/p&gt;

&lt;p&gt;Take care!&lt;br&gt;
Mikhail&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>opensource</category>
    </item>
    <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-638" src="https://platform.twitter.com/embed/Tweet.html?id=1528655715933597697"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1528655715933597697-638');
  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>
      <category>opensource</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>
