See how you can easily solve the most common issues with Hibernate
Hibernate is probably the most popular JPA implementation on the market and you can see that in many places, like:
- The number of projects in which you’ve used it yourself
- The number of job positions which ask for Hibernate experience and, of course,
- The number of questions about exceptions posted on the internet
At OverOps, the focus is on ensuring overall software quality and on finding and fixing errors, exceptions and slowdowns. In this post, I will focus on the last point in the list and share with you the 5 Hibernate Exceptions I have probably fixed, explained, blogged and complained about the most in the more than 15 years I’ve worked with Hibernate.
And while they didn’t made the cut for the top 10 Exception types, a quick google search told me that I’m not the only one facing these issues.
Before we dive into the different Exceptions, this post is a long one and I summarized the most important points in a free cheat sheet. You can download it at the end of this post. In addition, you can also check out my new book for a deeper dive into these types of issues “Hibernate Tips: More than 70 solutions to common Hibernate problems“.
Hibernate throws a LazyInitializationException if you try to access a not initialized relationship to another entity without an active session. You can see a simple example for this in the following code snippet.
OK, you might now say that you would never do something like that. And while you’re probably right that you will never use the exact same code in your application, you can unintentionally do the same thing pretty easily.
The most popular way to do it is to access a relationship with FetchType.LAZY in your presentation tier which you didn’t initialize in your business tier. You can find a lot of these issues in popular forums with a lot of bad advice on how to fix them.
Please, don’t use the open session in view anti-pattern. It causes more harm then it provides benefits.
The best way to fix a LazyInitializationException is to initialize the required relationship in your business tier. But don’t initialize all relationships just because there might be one client out there who needs one of them. For performance reasons, you should only initialize the relationships you need.
JPA and Hibernate offer different options to initialize lazily fetched relationships. My personal favorite is the @NamedEntityGraph which provides a query independent way to define a graph of entities which will be fetched with the query.
You can see an example of a simple graph in the following code snippet. It fetches the Book relationship for an Author entity.
You can define the @NamedEntityGraph at any file that is available to Hibernate. I prefer to do it at the entity with which I intend to use it.
As you can see, there is not much you need to do to define the graph. You just have to provide a name and an array of @NamedAttributeNode annotations which define the attributes that Hibernate shall fetch from the database. In this example, that’s only the book attribute which maps the relationship to the Book entity.
You can then provide this graph as a hint to Hibernate to define which relationships shall be initialized with a given query. You can see an example for that in the following code snippet.
As you can see, I first call the getEntityGraph(String name) method on the EntityManager to get an instance of the entity graph. In the next step, I create a HashMap with query hints and add the graph as a javax.persistence.fetchgraph.
In the final step, I provide the query hints as an additional parameter to the find method. This tells Hibernate to initialize the relationship to the Book entities, and I can call the getBooks() method without an active Hibernate session.
Another very common exception is the OptimisticLockException. Hibernate throws it when you use optimistic locking and detects a conflicting update of an entity. That most often happens for one of two reasons:
- 2 users try to update the same entity at nearly the same point in time.
- 1 user performs 2 updates of the same entity, and you didn’t refresh the entity representation in the client so that the version value wasn’t updated after the first update.
You can see a test case with 2 concurrent updates in the following code snippet.
As you can see, I use two independent EntityManagers and start a transaction with both of them, get the Author entity with id 1 and update the first name attribute.
This works fine until I try to commit the second transaction and Hibernate checks for concurrent updates of this Author entity. In a real world application, this would, of course, be done by 2 parallel calls of the same method.
If you use OverOps, you can see the state of all variables when the Exception occurred which can be useful to identify the source of the second update call.
You can’t do much to avoid this exception without introducing pessimistic locking which would sacrifice the performance of your application. Just try to update the entity representations in the client as often as possible and to keep the update operations as short as possible. That should avoid most unnecessary OptimisticLockExceptions, and you’ll need to handle the rest of them in the client application.
But if only one user is causing the OptimisticLockException on its own, you found a bug which you can easily fix. If you use optimistic locking, Hibernate uses a version column to keep track of the current version of the entity and to prevent concurrent modifications. You, therefore, need to make sure that your client always updates its representation of the entity after the user triggered any change on the entity. And your client application should also not cache the entity or any value object representing it.
3. org.hibernate.AnnotationException: Unknown Id.generator
This one is caused by a wrong entity mapping, and you might run into it during development. The reason for it is pretty simple, you reference an unknown sequence generator in your @GeneratedValue annotation, like in the following code snippet.
The @GeneratedValue annotation allows you to define a generation strategy for the primary key values. In the previous code snippet, I wanted to use a database sequence and provide “authorSequence” as the name of the generator.
A lot of developers now expect that “authorSequence” will be the name of the database sequence which Hibernate shall use. That’s not the case. It’s the name of the @SequenceGenerator which you can use to provide more information about the database sequence Hibernate shall use.
But the definition of the @SequenceGenerator is missing, and Hibernate, therefore, throws the AnnotationException. To fix it, you have to add a @SequenceGenerator annotation as I did in the following code snippet.
The @SequenceGenerator annotation allows you to provide more information about the database sequence and how Hibernate shall use it. In this code snippet, I’ve set the name of the sequence, which is “author_seq” and 1000 as its initial value.
You can also specify the database schema to which the sequence belongs and the allocation size which Hibernate can use for performance optimizations. You can learn more about ID generators in the following post.
4. QuerySyntaxException: Table is not mapped
This one is another typical mapping error. In most projects, the database schema already exists or defined independently of your entity mapping. And that’s a good thing. Please design the database schema properly and don’t let Hibernate generate it for you!
If you want Hibernate to setup the database at startup, it’s better to provide an SQL script instead of letting Hibernate generate the database schema based on your entity mappings.
Now, back to the QuerySyntaxException. If the database schema is defined independently of your entities, you will often run into a situation where the default table name doesn’t match the name of the existing table or that the table is part of a different database schema.
In that case, you can provide the schema and table name with a @Table annotation as you can see in the following code snippet.
5. org.hibernate.PersistentObjectException: detached entity passed to persist
The last exception in this list can have multiple reasons and all of them are bugs:
- You try to persist a new entity and provide a primary key value, but the entity mapping defines a strategy to generate it.
- You try to persist a new entity and the persistence context already contains an entity with the given id.
- You try to persist a detached entity instead of merging it.
The first one is easy to fix, don’t provide a primary key value or remove the primary key generation strategy 😉
The second one should only happen when you manage the primary key values yourself, and your algorithm creates duplicates. My preferred approach to fixing this issue is to let Hibernate use a database sequence to generate the primary key values instead of implementing my own algorithm.
That’s not always possible and in these cases, you have to test and debug the algorithm you use to generate the primary key values. Depending on the algorithm, this might be an exhausting and time-consuming task.
The third one often happens when you use entities in your client, and the client calls the wrong server method which persists new entities instead of updating the existing ones. The obvious way to fix this error is to fix the call in the client.
In addition, there are things you can do on the server side to avoid these kinds of issues, like using specific value objects for create use cases and not handling create and update use cases in the same server method. This makes it easier for the client developer to find and call the right method and to avoid these kinds of issues.
Summary and cheat sheet
These were my 5 most common Hibernate Exception and how you can fix them. As you’ve seen, the Exceptions and their reasons are very different. Some of them only occur during development and others will hit you in production. So better watch out and make sure that you’re familiar with these kinds of problems. To make that a little easier for you, I prepared a cheat sheet explaining the 5 Exceptions mentioned in this post.
Which ones are yours? Tell me about it in a comment below or on twitter.