6 Gotchas of Hibernate You Wish You Knew Earlier
This post is about the confusions of a programmer familiar with many other ORMs was asked to use Hibernate for the first time.
Before you ask “I thought you are a backend developer in working primarily on Java, how come you did not use Hibernate?” Yes, I am a Java developer. I write Java code for a living daily and no, I don’t use Hibernate often. I create REST APIs as the part of my daily job, but I don’t use Spring or Hibernate often. My Company uses JDBi extensively so I never had the chance to fully learn how Hibernate works. I’m well aware of how Postgres and JDBC work, so on the first day when I jumped ship to learn Hibernate for fun, I was up for a shock!
You need a private/protected default constructor
Hibernate uses reflection to read and write classes to and from database rows. It uses proxy classes to facilitate lazy loading. For this, it needs to instantiate the class using a parameter-less constructor which must be protected or private. Without a parameterless constructor, you will meet a weird error message. IDEA has a warning, if enabled, will help you prevent these mistakes.
Quoting table names
Create an Entity called User, connect it with a Spring DaoUserDetailsProvider
watch the world burn.
Because apparently, Hibernate doesn’t quote the table names while issuing queries when you try authenticating to your Spring application, your code will fail with org.hibernate.exception.SQLGrammarException: could not extract ResultSet
. This means that your database gave a reply Hibernate didn’t expect. If you dig down the stack trace you will find the root exception — something similar to column user0.id doesn't exist
.
This is cryptic indeed but the fix is simple, whenever you use a column or table that uses some SQL restricted keywords, don’t forget to quote it manually. For Postgres we use double quotes(“”). So go ahead a add a @Table("\"user\"")
and everything will work!
You have to create your own migrations
Java has tools that will help you to run migrations but you must author them yourself. While I believe this is the safest approach as framework generated code may be sub-optimal, devoid of aesthetics and sometimes outright wrong (more on this later). If you have a dozen of new entities with dozen of new fields, authoring migrations manually quickly becomes a hassle.
There is no fix for it. Playing around with JPA properties and forcing Hibernate to dump the crude SQL to a file is a good start, you have to modify them to suite to your tastes. Add the following code to automatically dump what Hibernate is thinking to execute to update your schema. This file serves as a good starting point, and you will definitely want to review/edit these files and put it with a migration library such as Flyway.
spring.jpa.properties.javax.persistence.schema-generation.scripts.create-target=src/main/resources/db/migrations/schema.sql
spring.jpa.properties.javax.persistence.schema-generation.scripts.action=update
Understand how two-way relationships behave
If you are using a bidirectional one-to-many/many-to-one relationship to model your data, you must be careful. A bi-directional relationship is traditionally modeled as a field on the one-to-many side — called the owning side. The model with many-to-one relationship is called inverse-side. However, the database only stores data at one of the sides and the other side is populated by the ORM layer. Making changes on the owning side will not automatically make changes in the inverse side until you flush the data.
Database ID generation is different
Hibernate uses its own ID generator by default which uses a value from a table and computes IDs for each entity you insert. This means that your ID ranges will not be sequential or continuous. This is not an issue that affect applications (I use a UUID anyway), but is surprising. You can make hibernate to use your database’s native method of generating IDs though by specifying a different GenerationStrategy
.
Automatic code-generation for JPA repositories
For JPA repositories, Hibernate can automatically generate SQL for you just from the function name and type parameters.
For example, if you have a method called Article findByCreatedBy(User u)
Hibernate will generate a query like, SELECT * FROM article WHERE article.created_by_id = :user_id
But, as with any smart system that does things “automagically”, it may generate sub-par queries. Make sure to enable query logging to see what queries does Hibernate generate for you. If it is very suboptimal, write queries manually.
These are the few things that surprised me when I started using Hibernate. Hope this helps to remove some “surprises” from your experience with it.